Why not forward-declare everything? (c++)

Started by
20 comments, last by Nemesis2k2 18 years, 10 months ago
I've found that it's useful to forward declare the types I'm using in my class header files (or at least the ones that aren't allocated on the stack ). This means that I can just put the include in the CPP file and will allow for a quicker compile Also, clients of the class that don't depend on the forward-declared type are not forced to include it. I do this a lot... and lately I've been thinking I should have a file for each package that forward declares everything in said package Is this a good or bad idea?
Advertisement
Just to ensure we're on the same page... you're talking about doing:
class foo;class bar {    foo * baz;};

Instead of:
#include <foo.h>class bar {    foo * baz;};

?

There's pros and cons to both.

For the first, you have:
Pro: Faster compiles, dosn't lead to cyclilic dependancies
Con: Dosn't include the class body in case you want to call them from an inline function, or have said class a member (as in, not a pointer).
Possibly Pro or Con, depending on opinion: Bugs won't be marked in the header if you rename foo to pie - they'll only appear in code directly using that class (e.g. class implementation).

For the second, you have:
Pro: can include multiple types, allows one to specify the class as a member, and otherwise freely use the type(s).
Con: Increases compile time.


Personally, I prefer the later unless I'm:

1) Breaking a cyclilic dependancy
2) Intentionally hiding the definition (for when I have classes like so:)

class foo {    class implementation; //forward decleration!!!    boost::shared_ptr< implementation > impl;public:    //...public methods...};


That way, if implementation has to deal with voodoo magic (unpleasant namespace/definespace pollution) or is OS-specific (e.g. the network lib I last used this pattern in has an implementation of "implementation" for winsock (for windows, obviously) and another for BSD sockets (for linux)) it dosn't end up screwing over the using code.

If something's taking too long to compile, I break it up and find out which bits are taking so long (e.g. large boost::spirit grammars) and toss them into a source file where they belong. Since I use automatic dependancy-based building, this means I only take the hit when I need to recompile that file.
Thanks for the response.

Quote:Original post by MaulingMonkey
Just to ensure we're on the same page... you're talking about doing:
class foo;class bar {    foo * baz;};

Instead of:
#include <foo.h>class bar {    foo * baz;};

?


Yes, except more along the lines of this:

foo.h
#include <math.h>class foo{    vector* vec;    matrix3* matrix;};


math.h
class vector;class matrix3;class matrix4;class quaternion;class vector2;// etc..


Quote:
There's pros and cons to both.

For the first, you have:
Pro: Faster compiles, dosn't lead to cyclilic dependancies
Con: Dosn't include the class body in case you want to call them from an inline function, or have said class a member (as in, not a pointer).


I'm not too worried about this. I can always inline functions later in development, when I need the speed. Plus, if the function ever changes I don't have to re-compile, just re-link.

Quote:
Possibly Pro or Con, depending on opinion: Bugs won't be marked in the header if you rename foo to pie - they'll only appear in code directly using that class (e.g. class implementation).


I don't really consider this a big deal. If I forward declare always, whenever it is possible, then these errors become pretty obvious after the first few.

Quote:
For the second, you have:
Pro: can include multiple types, allows one to specify the class as a member, and otherwise freely use the type(s).
Con: Increases compile time.


When you say include multiple types, do you mean through just a single header? Yeah that is a valid argument.

I think I'm going to try using a common header that forward-declares everything and see how it goes.
Another con that comes to mind is that adding a new class to this file will result in a full recompile. But I'm assuming I won't be adding new classes very often..
I use forward declarations all the time. The time savings in compilation far outweigh any of the cons listed above. They don't really even bother me that much...

There is an annoyance of sometimes being forced to change it to an include if I want to inline a function like he mentioned but it's just a judgment call whether to inline it or keep it in the source file.

For me it's a little more work than just including everything in the header but it just feels more correct.
Having only declarations in headers force you to use pointers instead of value types. This may (Bridge pattern) or may not (dynamic allocations everywhere) be desirable. You've got to judge on a case by case basis.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Be aware that you will also not be able to use most smart pointers to hold forward declared classes as class members. (If you do, your destructor will not be called, but rather a compiler default-constructed one will.) This can be a hard memory leak to find, as well, because it is so easy to think such great things about smart pointers. Fortunately boost::shared_ptr will probably give you at least a warning. std::auto_ptr probably won't, though.

David
(your destructor for the forward declared class, that is)
Quote:Original post by Anonymous Poster
Be aware that you will also not be able to use most smart pointers to hold forward declared classes as class members. (If you do, your destructor will not be called, but rather a compiler default-constructed one will.) This can be a hard memory leak to find, as well, because it is so easy to think such great things about smart pointers. Fortunately boost::shared_ptr will probably give you at least a warning. std::auto_ptr probably won't, though.

David


Eh, are you sure about that?

I've got a forward declared class held in a smart pointer in my app, and the destructor is most definitely being called. Is this another case of Visual Studio 2002 deviating from the standard then?

Actually, from checking the boost::shared_ptr documentation, it says that the type doesn't have to be a complete type when you declare a shared_ptr, surely this wouldn't be the case if something as dangerous as the destructor not being called was happening?
and for a great example of this you don't need to look longer than the C++ Standard Library and the header "iosfwd" :D
HardDrop - hard link shell extension."Tread softly because you tread on my dreams" - Yeats
Oh, I thought you meant like this:

// Doing forward declarationvoid foo;int main() {  foo();}void foo() {}// vs. not doing forward declarationvoid foo() {}int main() {  foo;}


In *that* case I definitely prefer to avoid the forward declarations as much as possible, because it's extra typing you have to maintain. Within the one source file, you should only put them where they're needed due to cyclic dependency - because it then serves as documentation of said dependency.

This topic is closed to new replies.

Advertisement