Including related files

C

Connell Gauld

Hi,
I have what feels like a really stupid question and I'm sorry if it is
asked a lot.
Imagine I have two classes cShip and cPassenger. They each have a
definition in their own header cShip.h and cPassenger.h and their
implementation in cShip.cpp and cPassenger.cpp. Now here is the header
file for each:

#ifndef CSHIP_H
#define CSHIP_H

class cShip
{
...
cPassenger * owner;
...
};

#endif

------Next file

#ifndef CPASSENGER_H
#define CPASSENGER_H

class cPassenger
{
...
cShip * current_vehicle;
...
};

#endif

Now the problem I have is how to join these files together with includes
such that they compile. (The idea here is that a ship always has an
owner but that a passenger can be in a ship that they don't own).

Sorry if this is a really mundane question,
Connell
 
J

James Daughtry

An owner may not be a passenger, so it may make more sense to have a
separate cOwner and cPassenger class. But to get circular references to
compile, you can simply use a forward declaration:

#ifndef CSHIP_H
#define CSHIP_H

class cPassenger;

class cShip
{
...
cPassenger * owner;
...
};

#endif

#ifndef CPASSENGER_H
#define CPASSENGER_H

class cShip;

class cPassenger
{
...
cShip * current_vehicle;
...
};

#endif

This could cause you some problems though if any part of the class
declaration relies on deeper knowledge about the forward declared
class. A forward declaration only gives you the name of the class, not
any details about data members or member functions.
 
A

Alf P. Steinbach

* Connell Gauld:
I have what feels like a really stupid question and I'm sorry if it is
asked a lot.

Not at all, it's a good question.

There are a host of very _similar_ questions that all involve
circular dependencies.

In your case the usual solution, forward declarations, is _not_
appropriate.

Imagine I have two classes cShip and cPassenger. They each have a
definition in their own header cShip.h and cPassenger.h and their
implementation in cShip.cpp and cPassenger.cpp. Now here is the header
file for each:

#ifndef CSHIP_H
#define CSHIP_H

class cShip
{
...
cPassenger * owner;
...
};

#endif

------Next file

#ifndef CPASSENGER_H
#define CPASSENGER_H

class cPassenger
{
...
cShip * current_vehicle;
...
};

#endif

Now the problem I have is how to join these files together with includes
such that they compile. (The idea here is that a ship always has an
owner but that a passenger can be in a ship that they don't own).

What you have is a design problem, not (just) a technical circular
dependency problem.

Let the owner of a ship be a cPerson.

Let cPassenger be a class derived from cPerson, and voilà, problem solved;
you then have

cShip depends on CPerson
cPassenger depends on cShip and cPerson

Later on you might consider the case where the owner might be a person _or_
a corporation.

With that twist it gets more interesting... ;-)
 
G

Gianni Mariani

Connell Gauld wrote:
....
Now the problem I have is how to join these files together with includes
such that they compile. (The idea here is that a ship always has an
owner but that a passenger can be in a ship that they don't own).

Ownership (no pun intended) is somthing you need to manage with the
code, not the declaration (if you were using smart pointers you could do
this otherwise).

As for the declaration issues - you need to forward declare the class
you're referring to - see below.

#ifndef CSHIP_H
#define CSHIP_H

class cPassenger; // declare a class cPassenger

class cShip
{
...
cPassenger * owner;
...
};

#endif

------Next file

#ifndef CPASSENGER_H
#define CPASSENGER_H

class cShip; // declare a class cShip

class cPassenger
{
...
cShip * current_vehicle;
...
};
 
V

Victor Bazarov

Alf said:
* Connell Gauld:
I have what feels like a really stupid question and I'm sorry if it is
asked a lot.


Not at all, it's a good question.

There are a host of very _similar_ questions that all involve
circular dependencies.

In your case the usual solution, forward declarations, is _not_
appropriate.
Huh?

[...]
 
A

Alf P. Steinbach

* Victor Bazarov:

See the rest of that message, but, since you ask, some background:

It's generally not a good idea to solve design problems by applying
technical kludges to the _symptoms_, whether the kludges be forward
declarations, 'friend', 'void*', C-style casts, or whatever.

Such "solutions" come back to haunt you, and also they typically
yield more complex, non-maintainable code in the first place, because you
missed out on sensible abstraction opportunities (which simplify things).

Cheers,

- Alf
 
V

Victor Bazarov

Alf said:
* Victor Bazarov:



See the rest of that message, but, since you ask, some background:

It's generally not a good idea to solve design problems by applying
technical kludges to the _symptoms_, whether the kludges be forward
declarations, 'friend', 'void*', C-style casts, or whatever.

What makes you think there *is* a design problem there? The need to use
a "technical kludge" is *not* necessarily an indication of a design flaw.
Just like 'friend' and 'void*' and 'dynamic_cast', forward declarations
are there to be used to _accommodate_ certain design decisions, not to be
avoided at all costs.
Such "solutions" come back to haunt you, and also they typically
yield more complex, non-maintainable code in the first place, because you
missed out on sensible abstraction opportunities (which simplify things).

No! What you did suggest was, in fact, a kludge. If two classes do
depend on each other, the model in question is _fine_, it is perfectly
OK to have a pointer to A in B and a pointer to B in A. And, yes, the
only clean way to resolve the reference "problems" is in the FAQ.

Introducing a non-existent design elements just to avoid using some
language features you might be finding "inappropriate" for whatever
reason, is definitely a mistake.

Design comes first, language-specific implementation comes second.

V
 
A

Alf P. Steinbach

* Victor Bazarov:
Design comes first, language-specific implementation comes second.

Right.

I gather all that about likes and dislikes that I snipped abvoe, a
completely unwarranted speculation about my thinking (I don't think that
way), was really about your earlier posting, posted almost at the same time
as mine, where you failed to notice it was a design problem... ;-)

Check the design again, then tell me it's reasonable that a boat's owner
must be a passenger.
 
K

Karl Heinz Buchegger

Alf P. Steinbach said:
[snip]

Let the owner of a ship be a cPerson.

OK.
(Side note: Every cPerson owns a ship? I don't think so.
So I take it for granted that you wanted to have a cPerson class
and derive a COwner class from it)
Let cPassenger be a class derived from cPerson, and voilà, problem solved;
you then have

cShip depends on CPerson
cPassenger depends on cShip and cPerson

Later on you might consider the case where the owner might be a person _or_
a corporation.

Well. Eventually he will come to the point where his ships actually transport
cPassenger-s.

So this will end up in

cShip depends on COwner and cPassenger
cOwner depends on cShip
cPassenger depends on cShip

Same problem. The design is better, but the circular dependency stays
the same.
 
V

Victor Bazarov

Alf said:
* Victor Bazarov:



Right.

I gather all that about likes and dislikes that I snipped abvoe, a
completely unwarranted speculation about my thinking (I don't think that
way), was really about your earlier posting, posted almost at the same time
as mine, where you failed to notice it was a design problem... ;-)

*What* design problem?
Check the design again, then tell me it's reasonable that a boat's owner
must be a passenger.

You're trying to read more than the OP has ever thought of presenting.
Whether the design is reasonable is _not_ the topic of this discussion.
For all it's worth, the question could be about A having a B* in it and
B having an A*.

*You* read the original post again. It doesn't say "My boat ownership
model demands to have two classes 'cPassenger' and 'cShip' and here is
the relationship between them". It says "IMAGINE I have two classes..."
(emphasis added).

I don't dispute that there can be design flaws. What I am disputing that
the need to use a forward declaration is an automatic indication (symptom)
of a bad design.

Besides, if you want to talk design, perhaps you should invite the OP to
comp.object? He asked for a language-specific solution and he got it.

V
 
A

Alf P. Steinbach

* Victor Bazarov:
It says "IMAGINE I have two classes..." (emphasis added).

So, the example is what one should imagine, then. ;-)

I don't dispute that there can be design flaws.

Good, we're converging toward agreement here.

What I am disputing that the need to use a forward
declaration is an automatic indication (symptom) of a bad design.

There we agree completely! :)

Now who the ... said that?

Give him to me (I've got my hammer ready); I'll bash him!

Besides, if you want to talk design, perhaps you should invite the OP to
comp.object? He asked for a language-specific solution and he got it.

As you wrote earlier,

Design comes first, language-specific implementation comes second.


Using a language correctly or reasonably has very much to do with the design
level. Otherwise we wouldn't frown on 'void*' and the like. Remove the
design level and you could as well use only the 'asm{ ... }' feature.

So, IMO the design level is the first one should focus on except when a
problem is stated without any connection to an application, e.g. like your
A/B example, and I think you agree with that.

Cheers,

- Alf
 
A

Alf P. Steinbach

* Karl Heinz Buchegger:
Alf P. Steinbach said:
[snip]

Let the owner of a ship be a cPerson.

OK.
(Side note: Every cPerson owns a ship? I don't think so.
So I take it for granted that you wanted to have a cPerson class
and derive a COwner class from it)

Nope.

That "nope" follows from the minimalist guideline.

If there isn't a problem, don't solve it (yet), and there isn't a problem:
a ship can have a person as owner without every person being an owner.

A cOwner class is only necessary if an owner has some extra attributes
or different behavior than a person.

So far nothing has indicated that owners do, but in the OP's model
passengers have, compared to persons.

Well. Eventually he will come to the point where his ships actually transport
cPassenger-s.

So this will end up in

cShip depends on COwner and cPassenger
cOwner depends on cShip
cPassenger depends on cShip

Same problem. The design is better, but the circular dependency stays
the same.

No, and yes.

Different but in some respects similar problem: different solution.

Details matter, IMHO.

I think it's interesting that with cPerson (I don't like prefixes!)
one can also more easily see a solution to this new slightly different
problem, where one detail is exemplified by: "what class should contain the
embark() member function?". Should we write ship.embark(passenger) or
should we write passenger.embarkOn(ship), or what?

I think I'd land on the "or what", at least as fundamental implementation;
when thinking about the design level a person isn't forever a passenger,
it's just a transient property, and it can even be an implied property that
shouldn't then be represented explicitly in the cPerson objects except as an
optimization if it turns out that optimization is necessary.

General summary: circular dependencies sometimes arise from bad design,
and generalizing the model to cover more is one possible way to remove such
a dependency (not always applicable, but often enough).

The first generalization, to add the Person concept; the second, to extend
the model to cover cases where persons aren't passengers all the time.
 
V

Victor Bazarov

Alf said:
[...]
So, IMO the design level is the first one should focus on except when a
problem is stated without any connection to an application, e.g. like your
A/B example, and I think you agree with that.

Well, no. You need to remember that C++ is a multi-paradigm language.
OO is not the end of its application. You also need to remember that
we are in a _language_ newsgroup, and while design is important, and it
does come first, when asked a *simple* language question, one shouldn't
instead go off on a tangent and begin discussing design when no design
discussion is requested.

It's all about assumptions. I (and a couple of other folks) assumed
that the OP knows what he's doing, and while there can always be some
not-so-obvious misconceptions somebody might have about the model he is
using, it's up to him to recognize that and ask for comments on it. You
OTOH assumed that the OP does *not* know what he's doing. Now, while it
is up to the OP to decide whose assumption is the correct one (and I am
not asking about it 'coz I really don't care), with all other things
being equal, a plain answer to the question posed is _better_ than any
other off-on-a-tangent speculation.

If you don't think so, fine. If you reluctantly agree, fine. If you
now see the light and admit your off-on-a-tangent-ness, fine. In any
case, I am not interested in discussing this any more, at least here.
I've stated my point of view, explaining it further makes no sense to me.

Thanks for reading.

V
 
A

Alf P. Steinbach

* Victor Bazarov:
Alf said:
[...]
So, IMO the design level is the first one should focus on except when a
problem is stated without any connection to an application, e.g. like your
A/B example, and I think you agree with that.

Well, no. You need to remember that C++ is a multi-paradigm language.
OO is not the end of its application. You also need to remember that
we are in a _language_ newsgroup, and while design is important, and it
does come first, when asked a *simple* language question, one shouldn't
instead go off on a tangent and begin discussing design when no design
discussion is requested.

One should give the best available answer, and here that was design.

That's not going off on a tanget: it's ordaining the best medicine.

This medicine also happens to be a general technique not AFAIK mentioned in
the FAQ, so it's interesting in its own right.

It's all about assumptions. I (and a couple of other folks) assumed
that the OP knows what he's doing, and while there can always be some
not-so-obvious misconceptions somebody might have about the model he is
using, it's up to him to recognize that and ask for comments on it. You
OTOH assumed that the OP does *not* know what he's doing.

Naturally; he or she wouldn't ask if he or she knew.

Do you see that now?

Now, while it
is up to the OP to decide whose assumption is the correct one (and I am
not asking about it 'coz I really don't care), with all other things
being equal, a plain answer to the question posed is _better_ than any
other off-on-a-tangent speculation.

What I gave was the plainest, best answer.

Advicing to use forward declarations, void* pointers or whatever
tecnical kludges that can hide symptoms, but not cure, and that
can and probably will exacerbate the problems, is ungood.

Especially when the cure is so exceedingly simple.

Cheers,

- Alf
 
R

Richard Herring

Alf P. Steinbach said:
It's generally not a good idea to solve design problems by applying
technical kludges to the _symptoms_, whether the kludges be forward
declarations, 'friend', 'void*', C-style casts, or whatever.

Hmm.

Friend's a kludge because it breaks encapsulation.
void* and casts are kludges because they break type safety.

But what does a forward declaration break?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top