// // (C) Porkolab 2003 // // A.8.1. // // Compile-time dependences: In C++ when anything in a header file changes, all code that includes the header (either directly or indirectly) must be recompiled. // x.h: sample header // version 1 #include <iostream> #include <ostream> #include <list> // none of A, B, C, D, E are templates // Only A and C have virtual functions #include "a.h" // class A #include "b.h" // class B #include "c.h" // class C #include "d.h" // class D #include "e.h" // class E class X : public A, private B { public: X( const C&); B f(int, char*); C f(int, C); C& g(B); E h(E); virtual std::ostream& print(std::ostream&) const; private: std::list<C> clist_; D d_; }; inline std::ostream& operator<<( std::ostream& os, const X& x) { return x.print(os); } There are too many headers unneccessary included in x.h. That is bad, because every client that includes x.h is also forced to include them. In large projects that could be painfull. 1. Remove <iostream> People automatically include <iostream>, even if input functions never used. 2. Replace <ostream> with <iosfwd> Parameters and return types only need to be forward declared. Because ostream is basic_ostream<char> template, it is not enough to declare: class ostream; ..so we shall use <iosfwd> But, inline std::ostream& operator<<( std::ostream& os, const X& x) { return x.print(os); } Here we use "os" as parameter to "print". This also don't need definition, only declaration! 3. Replace "e.h" with forward declaration of class E; 4. Leave "a.h" and "b.h": we need a full declaration of the base classes in case of inheritance. The compiler must know the size of bases, whether functions are virtual or not. 5. Leave "c.h" and "d.h": list<C> and D are private data members of X. // x.h: sample header // version 2 #include <iosfwd> #include <list> // none of A, B, C, D, E are templates // Only A and C have virtual functions #include "a.h" // class A #include "b.h" // class B #include "c.h" // class C #include "d.h" // class D class E; class X : public A, private B { public: X( const C&); B f(int, char*); C f(int, C); C& g(B); E h(E); virtual std::ostream& print(std::ostream&) const; private: std::list<C> clist_; D d_; }; inline std::ostream& operator<<( std::ostream& os, const X& x) { return x.print(os); } PIMPL idiom C++ lets us encapsulate the private parts of a class from unathorized access. It can take a little more work to encapsulate dependencies on a class's privates. Originally it was "impl_", the Hungarian notation of pointer made it "pimpl_". Officially it is called "compilation firewall". // file x.h class X { // public and protected members private: // private members: // whenever these change, // all client code must be recompiled }; We replace that with: // file x.h class X { // public and protected members private: // pointer to forward declared class class XImpl *pimpl_; // opaque pointer }; // file x.cpp struct X // not neccessary to declare as "class" { // private members; fully hidden // can be changed at without // recompiling clients }; Advances: 1. Types mentioned only in a class's implementation need no longer be defined for client code, wich eliminate extra #includes and improve compile speed. 2. A class's implementation can be changed - private members can be added or removed - without replacing client code. Costs: 1. Each construction/destruction must allocate/deallocate memory. 2. Each access of a hidden member can require at least one extra indirection. Sometimes from the hidden part we must access members in visible part: that we need another extra indirection. // x.h: sample header // version 3 #include <iosfwd> #include "a.h" // class A #include "b.h" // class B class C; class E; class X : public A, private B { public: X( const C&); B f(int, char*); C f(int, C); C& g(B); E h(E); virtual std::ostream& print(std::ostream&) const; private: // opaque pointer to forward-declared class class XImpl *pimpl_; }; inline std::ostream& operator<<( std::ostream& os, const X& x) { return x.print(os); } // file x.cpp #include "x.h" #include "c.h" // class C #include "d.h" // class D struct XImpl { std::list<C> clist_; D d_; }; Removing Unnecsessary Inheritance "Its not OO without inheritance" is a widely used idea. "Is-a" relationship is a much stronger relationship than "has-a" or "use-a". For the management of dependences composition/membership is much better than inheritance. "Use as strong relationship as neccessary but no stronger" // x.h: sample header // version 3 #include <iosfwd> #include "a.h" // class A class C; class E; class X : public A { public: X( const C&); B f(int, char*); C f(int, C); C& g(B); E h(E); virtual std::ostream& print(std::ostream&) const; private: // opaque pointer to forward-declared class class XImpl *pimpl_; }; inline std::ostream& operator<<( std::ostream& os, const X& x) { return x.print(os); } // file x.cpp #include "x.h" #include "b.h" // class B #include "c.h" // class C #include "d.h" // class D struct XImpl { B b_; std::list<C> clist_; D d_; };