Item 26. Minimizing Compile-time Dependencies part 1

I l@ve RuBoard

Item 26. Minimizing Compile-time Dependencies桺art 1

Difficulty: 4

When we talk about dependencies, we usually think of run-time dependencies like class interactions. In this Item, we will focus instead on how to analyze and manage compile-time dependencies. As a first step, try to identify (and root out) unnecessary headers.

Many programmers habitually #include many more headers than necessary. Unfortunately, doing so can seriously degrade build times, especially when a popular header file includes too many other headers.

In the following header file, which #include directives could be immediately removed without ill effect? You may not make any changes other than removing or rewriting #include directives. Note that the comments are important.

//  x.h: original header 
//
#include <iostream>
#include <ostream>
#include <list>
// None of A, B, C, D or 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);
}
  •  
 
I l@ve RuBoard
 
I l@ve RuBoard

Solution

Of the first two standard headers mentioned in x.h, one can be immediately removed because it's not needed at all, and the second can be replaced with a smaller header.

  1. Remove iostream.

    #include <iostream> 
    

    Many programmers #include <iostream> purely out of habit as soon as they see anything resembling a stream nearby. X does make use of streams, that's true; but it doesn't mention anything specifically from iostream. At the most, X needs ostream alone, and even that can be whittled down.

    Guideline

    Never #include unnecessary header files.


  2. Replace ostream with iosfwd.

    #include <ostream> 
    

    Parameter and return types need only to be forward-declared, so instead of the full definition of ostream we really only need its forward declaration.

In the old days, you could just replace "#include <ostream>" with "class ostream;" in this situation, because ostream used to be a class and it wasn't in namespace std. Alas, no more. Writing "class ostream;" is illegal for two reasons.

  1. ostream is now in namespace std, and programmers aren't allowed to declare anything that lives in namespace std.

  2. ostream is now a typedef of a template; specifically, it's typedef'd as basic_ostream<char>. Not only would the basic_ostream template be messy to forward-declare in any case, but you couldn't reliably forward-declare it at all, because library implementations are allowed to do things like add their own extra template parameters (beyond those required by the standard), which, of course, your code wouldn't know about梠ne of the primary reasons for the rule that programmers aren't allowed to write their own declarations for things in namespace std.

    All is not lost, however. The standard library helpfully provides the header iosfwd, which contains forward declarations for all the stream templates (including basic_ostream) and their standard typedefs (including ostream). So all we need to do is replace "#include <ostream>" with "#include <iosfwd>".

    Guideline.

    Prefer to #include <iosfwd> when a forward declaration of a stream will suffice.


    Incidentally, once you see iosfwd, one might think that the same trick would work for other standard library templates, such as list and string. There are, however, no comparable "stringfwd" or "listfwd" standard headers. The iosfwd header was created to give streams special treatment for backward compatibility, to avoid breaking code written in years past for the "old" nontemplated version of the iostreams subsystem.

    There, that was easy. We can…

    What? "Not so fast!" I hear some of you say. "This header does a lot more with ostream than just mention it as a parameter or return type. The inlined operator<< actually uses an ostream object! So it must need ostream's definition, right?"

    That's a reasonable question. Happily, the answer is: No, it doesn't. Consider again the function in question:

    inline std::ostream& operator<<( std::ostream& os, const X& x ) 
    {
    return x.print(os);
    }
    

    This function mentions an ostream& as both a parameter and a return type (which most people know doesn't require a definition), and it passes its ostream& parameter in turn as a parameter to another function (which many people don't know doesn't require a definition either). As long as that's all we're doing with the ostream&, there's no need for a full ostream definition. Of course, we would need the full definition if we tried to call any member functions, for example, but we're not doing anything like that here.

    So, as I was saying, we can get rid of only one of the other headers just yet.

  3. Replace e.h with a forward declaration.

    #include "e.h"  // class E 
    

Class E is just being mentioned as a parameter and as a return type, so no definition is required, and x.h shouldn't be pulling in e.h in the first place. All we need to do is replace "#include "e.h"" with "class E;".

Guideline

Never #include a header when a forward declaration will suffice.


  •  
 
I l@ve RuBoard
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章