Quote:Original post by Palejo What confuses me, on the other hand, is the definition of class methods. The book on these pages seems to imply that you'd want to include your class method definitions in your actual .cpp files. Why not simply include these in your .hpp file as well, with your class declaration? By not doing this, don't you have to always 're-define' your class method definitions whenever you #include your .hpp file in a new pgm, thus creating redundant code? |
Imagine you are working on a large project composed of 146 .cpp files, 97 of which
#include "foo.hpp". When you compile your project, it means that the code for you member functions gets compiled 97 times. One each for each cpp file that includes the header. On the other hand, if the member function definitions are in their own cpp file, they only get compiled once.
Now imagine you make a change to one of those member functions. For example, you change a
+ to a
- in some expression. Since the headers are literally included into the cpp files by the preprocessor, it means that all 97 files have now changed and need to be recompiled! If the member function had been in foo.cpp, only that source file would have needed to be recompiled, after which the whole project would only have to be relinked.
By placing functions definitions into a cpp file, you insulate the rest of your project from changes to the function implementations. Furthermore, foo.cpp could #include whatever other header files it needs to compile without any of the 97 files that include foo.hpp inheriting that dependency -- thus further minimizing the chance that they would need to be rebuilt.
Furthermore, foo.cpp could contain a host of non-member functions used to facilitate the implementation of the class members without having to expose any of them to the rest of the project.
[advanced]Some techniques such as the
Bridge (or pImpl) design pattern go even further in that direction.
Of course, there are advantages to having member functions inlined in your header file, and some constructs like templates actually require it, but that discussion is probably best left for later.
Finally, solving the often-encountered problem of circular dependencies (class A needs class B, class B needs class A) generally requires separating the member function definitions from the class definition, since they require the full definition of the other class (because they actually manipulate it) while the class definition can be made to only require a declaration (so long as it doesn't need to know the size of the other class).
Note: A type that has been declared but not defined yes is known as an
incomplete type. They can only be used in limited fashion.
foo.hppclass Bar; // Warn the compiler that yes, there exist a class called Bar.class Foo{ Bar* barptr; // The size of a pointer does not depend on what it points to.public: Bar do_stuff(Bar& b); // Function prototypes work fine with incomplete types.};bar.hppclass Foo;class Bar{ Foo* fooptr;public: Foo do_stuff(Foo& f);};foo.cpp#include "foo.hpp"#include "bar.hpp" // Writing a function that actually create or manipulate an // instance of a type requires a full definition at that point. Bar Foo::do_stuff(Bar& b){ // code goes here}bar.cpp#include "bar.hpp"#include "foo.hpp" Foo Bar::do_stuff(Foo& f){ // code goes here}
[/advanced]
"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