By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
457,905 Members | 1,760 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 457,905 IT Pros & Developers. It's quick & easy.

Cyclic includes / forward definitions always solvable?

P: n/a
Hi all,
Regarding those cyclic dependencies of classes (like cases in which
class A refers to class B and class B to class A): can they ALL be
resolved by forward defining classes, and by implementing the actual
functions outside the class definition, OR there are STILL cases which
cannot be solved in this way and could require rewriting your code /
rewriting your class hierarchy?
Jul 22 '05 #1
Share this Question
Share on Google+
9 Replies


P: n/a
On Mon, 01 Mar 2004 13:14:30 -0600, John Doe <jo*****@nowhere.com> wrote:
Hi all,
Regarding those cyclic dependencies of classes (like cases in which
class A refers to class B and class B to class A): can they ALL be
resolved by forward defining classes, and by implementing the actual
functions outside the class definition, OR there are STILL cases which
cannot be solved in this way and could require rewriting your code /
rewriting your class hierarchy?


Resolving cyclic dependencies has nothing to do with where you put the
actual function implementations (including in the case of inline functions,
since they can be defined "ex-situ"); it does have to do with whether or
not the compiler has seen a "complete definition" of the class at the point
where that information is required. Typically, the information of interest
is the storage requirement for instances of that class.

Note that a class definition would be considered complete even if it
includes /pointers to incomplete types/.

As far as I know, it all boils down to this: you cannot write any
declaration or statement that requires the compiler to know how big a class
is before you've provided a complete definition of that class.
-leor

Leor Zolman
BD Software
le**@bdsoft.com
www.bdsoft.com -- On-Site Training in C/C++, Java, Perl & Unix
C++ users: Download BD Software's free STL Error Message
Decryptor at www.bdsoft.com/tools/stlfilt.html
Jul 22 '05 #2

P: n/a
"John Doe" <jo*****@nowhere.com> wrote in message
news:c2**********@news.vanderbilt.edu...
Regarding those cyclic dependencies of classes (like cases in which
class A refers to class B and class B to class A): can they ALL be
resolved by forward defining classes, and by implementing the actual
functions outside the class definition, OR there are STILL cases which
cannot be solved in this way and could require rewriting your code /
rewriting your class hierarchy?


A workaround is always possible, but there are still some
tough cases that require an alteration of the design.
For example, how would you use the following:

struct A {
std::list<B> myBs;
};

struct B {
std::list<A> myAs;
};

For the next C++ standard, I believe that it is being
considered to explicitly allow the the instantiation
of some standard containers (e.g. std::vector)
with incomplete types.
But there is no such guarantee currently.

Another classic problem is: how do you define a function f(void)
that returns a pointer to an f(void) ?

A workaround is to use an intermediate structure:
struct FP {
FP (*fp)(void);
};

FP f(void)
{
FP ans = {f};
return ans;
}

This idiom is actually useful in some implementations
of a state machine.

Regards,
Ivan
--
http://ivan.vecerina.com/contact/?subject=NG_POST <- e-mail contact form
Jul 22 '05 #3

P: n/a
Ivan Vecerina wrote:
"John Doe" <jo*****@nowhere.com> wrote in message
news:c2**********@news.vanderbilt.edu...
Regarding those cyclic dependencies of classes (like cases in which
class A refers to class B and class B to class A): can they ALL be
resolved by forward defining classes, and by implementing the actual
functions outside the class definition, OR there are STILL cases which
cannot be solved in this way and could require rewriting your code /
rewriting your class hierarchy?

A workaround is always possible, but there are still some
tough cases that require an alteration of the design.
For example, how would you use the following:

struct A {
std::list<B> myBs;
};

struct B {
std::list<A> myAs;
};


I don't particularly understand the usefulness of this example.

A is a strcuture that contains a list of B's, which in turns contains a
list of A's, which contains a list of B's, which conains a list of
A's....., you get the picture.

I think it IS a good thing that compilers don't allow such horrible
things...
Jul 22 '05 #4

P: n/a
Jorge Rivera wrote:

For example, how would you use the following:

struct A {
std::list<B> myBs;
};

struct B {
std::list<A> myAs;
};

I don't particularly understand the usefulness of this example.

A is a strcuture that contains a list of B's, which in turns contains a
list of A's, which contains a list of B's, which conains a list of
A's....., you get the picture.


I don't. In this case the list can be empty, which means that this
recursion is not necessarily infinite.

What you are saying is essentially the same as saying that functional
recursion is impossible because it never ends.
I think it IS a good thing that compilers don't allow such horrible
things...


I don't see anything horrible with it. It think you are mistaking this
situation with other situation, which employs direct aggregation

struct A { B b; };
struct B { A a; };
// Impossible and makes no sense

This is not the same as the previous example with 'std::list's, since
'std::list's implement indirect aggregation.

--
Best regards,
Andrey Tarasevich

Jul 22 '05 #5

P: n/a
The following article describes techniques for resolving such includes:

http://www.eventhelix.com/RealtimeMa...dePatterns.htm

Sandeep
--
http://www.EventHelix.com/EventStudio
EventStudio 2.0 - System Architecture Design CASE Tool
Jul 22 '05 #6

P: n/a
> A workaround is always possible, but there are still some
tough cases that require an alteration of the design.
For example, how would you use the following:

struct A {
std::list<B> myBs;
};

struct B {
std::list<A> myAs;
};
This compiles just fine in VC++7.1 ...

For the next C++ standard, I believe that it is being
considered to explicitly allow the the instantiation
of some standard containers (e.g. std::vector)
with incomplete types.
But there is no such guarantee currently.
You mean that they are doing particular cases in the compiler like Java
is doing for Strings? That would be really bad. I hate that: a language
should be just a language and should treat all data types equally.

But anyway I don't think it's the reason for which it compiles. I tried
with my own template instead of std::string and it compiled on the same,
because the std::string basically just uses pointers to the classes, so
it just requires a forward definition of such classes in order to
compile correctly, right?

Another classic problem is: how do you define a function f(void)
that returns a pointer to an f(void) ?

A workaround is to use an intermediate structure:
struct FP {
FP (*fp)(void);
};

FP f(void)
{
FP ans = {f};
return ans;
}


This was GREAT code, unfortunately it does not compile.

I guess it is because in the implementation of f you are putting a
pointer to a global function, while the definition of FP requires a
__thiscall function pointer (a function member of a class, in which
"this" is passed secretely). I'm NOT sure though.

Is there any cast which can make the above code compile? I tried but I
wasn't able to do it.

Thanks
Jul 22 '05 #7

P: n/a
"John Doe" <jo*****@nowhere.com> wrote in message
news:c2**********@news.vanderbilt.edu...
A workaround is always possible, but there are still some
tough cases that require an alteration of the design.
For example, how would you use the following:

struct A {
std::list<B> myBs;
};

struct B {
std::list<A> myAs;
};
This compiles just fine in VC++7.1 ...


But is not currently *guaranteed* to work ( => not legal C++ ).
Container implementations would be allowed, for example,
to store a root element within the class itself,
or may otherwise use the sizeof() of the provided
parameter to select a data member or specialization,
or whatever...

So the code is not (formally) portable, even though
it might work with all your compilers in practice.
For the next C++ standard, I believe that it is being
considered to explicitly allow the the instantiation
of some standard containers (e.g. std::vector)
with incomplete types.
But there is no such guarantee currently.


You mean that they are doing particular cases in the compiler like Java
is doing for Strings? That would be really bad. I hate that: a language
should be just a language and should treat all data types equally.


I did not mean that this would only apply to std::vector.
The fact is: the code above works well with several implementations.
But the standard allows standard library implementations to break it
(e.g. it could be needed to allow some optimizations on some platforms).
So declaring the above code above legal requires a cost/benefit analysis,
and maybe it cannot apply to all containers (e.g. std::string,
which often stores a small buffer as a data member, would not work).
But anyway I don't think it's the reason for which it compiles. I tried
with my own template instead of std::string and it compiled on the same,
because the std::string basically just uses pointers to the classes, so
it just requires a forward definition of such classes in order to
compile correctly, right? Right - in your implementation.
Another classic problem is: how do you define a function f(void)
that returns a pointer to an f(void) ?

A workaround is to use an intermediate structure:
struct FP {
FP (*fp)(void);
};

FP f(void)
{
FP ans = {f};
return ans;
}


This was GREAT code, unfortunately it does not compile.

It compiles as is on Comeau++, which is considered by many as being
a reference in compliance (http://www.comeaucomputing.com/tryitout/).

What is the exact error message you are getting?
I guess it is because in the implementation of f you are putting a
pointer to a global function, while the definition of FP requires a
__thiscall function pointer (a function member of a class, in which
"this" is passed secretely). I'm NOT sure though.
The sample above does not involve any member functions, just
a data member that is of type pointer-to-function.
Is there any cast which can make the above code compile?
I tried but I wasn't able to do it.


Please post the exact code you tried and associated error message.
The snippet is 'kosher' as far as I can tell (although it is not
an example of clarity...).
Cheers,
Ivan
--
http://ivan.vecerina.com/contact/?subject=NG_POST <- e-mail contact form
Jul 22 '05 #8

P: n/a
Ivan Vecerina wrote:
"John Doe" <jo*****@nowhere.com> wrote in message
news:c2**********@news.vanderbilt.edu...
A workaround is always possible, but there are still some
tough cases that require an alteration of the design.
For example, how would you use the following:

struct A {
std::list<B> myBs;
};

struct B {
std::list<A> myAs;
};


This compiles just fine in VC++7.1 ...

But is not currently *guaranteed* to work ( => not legal C++ ).
Container implementations would be allowed, for example,
to store a root element within the class itself,
or may otherwise use the sizeof() of the provided
parameter to select a data member or specialization,
or whatever...


??
In that case it's VERY RIGHT that it cannot compile, because it would
allocate an infinite-size structure due to recursion. Of course this is
off the topic of my initial question: I of course never intended to
compile impossible data structures.
A workaround is to use an intermediate structure:
struct FP {
FP (*fp)(void);
};

FP f(void)
{
FP ans = {f};
return ans;
}
I guess it is because in the implementation of f you are putting a
pointer to a global function, while the definition of FP requires a
__thiscall function pointer (a function member of a class, in which
"this" is passed secretely). I'm NOT sure though.

The sample above does not involve any member functions, just
a data member that is of type pointer-to-function.


Really?
If you do

FP fp;
//....
*(fp.f)();

you think that the f function called would not get a this pointer?
I think it would!

Is there any cast which can make the above code compile?
I tried but I wasn't able to do it.

Please post the exact code you tried and associated error message.
The snippet is 'kosher' as far as I can tell (although it is not
an example of clarity...).

Exactly your code.
The error I get is:

error C2440: 'initializing' : cannot convert from 'FP (__cdecl *)(void)'
to 'FP (__cdecl *)(void)'
Incompatible calling conventions for UDT return value

pointing at the line "FP ans = {f};"

I have seen that error before and it usually means that it cannot
convert a thiscall to a non-thiscall. And I agree with the compiler (see
above)
Jul 22 '05 #9

P: n/a
John Doe wrote:
A workaround is to use an intermediate structure:
struct FP {
FP (*fp)(void);
};

FP f(void)
{
FP ans = {f};
return ans;
}
I guess it is because in the implementation of f you are putting a
pointer to a global function, while the definition of FP requires a
__thiscall function pointer (a function member of a class, in which
"this" is passed secretely). I'm NOT sure though.


The sample above does not involve any member functions, just
a data member that is of type pointer-to-function.


Really?
If you do

FP fp;
//....
*(fp.f)();


This will not compile. Should be

(*fp.fp)();

or simply

fp.fp();
you think that the f function called would not get a this pointer?
I think it would!


No, it wouldn't. 'FP::fp' is declared as a pointer to ordinary
(non-member) function. It has nothing to do with any 'this'
Please post the exact code you tried and associated error message.
The snippet is 'kosher' as far as I can tell (although it is not
an example of clarity...).


Exactly your code.
The error I get is:

error C2440: 'initializing' : cannot convert from 'FP (__cdecl *)(void)'
to 'FP (__cdecl *)(void)'
Incompatible calling conventions for UDT return value

pointing at the line "FP ans = {f};"


Interesting. I don't get anything laike this even though I seem to be
using the same compiler as you.

Seeing that two lines of code that you posted above already contain some
errors, I wouldn't be surprised if it turned out that you tested one
piece of code and thought about another.

--
Best regards,
Andrey Tarasevich

Jul 22 '05 #10

This discussion thread is closed

Replies have been disabled for this discussion.