GotW #7a Solution: Minimizing Compile-Time Dependencies, Part 1

Managing dependencies well is an essential part of writing solid code. C++ supports two powerful methods of abstraction: object-oriented programming and generic programming. Both of these are fundamentally tools to help manage dependencies, and therefore manage complexity. It’s telling that all of the common OO/generic buzzwords—including encapsulation, polymorphism, and type independence—along with most design patterns, are really about describing ways to manage complexity within a software system by managing the code’s interdependencies.

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.

Problem

JG Question

1. For a function or a class, what is the difference between a forward declaration and a definition?

Guru Question

2. 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, what #include directives could be immediately removed without ill effect? You may not make any changes other than removing or rewriting (including replacing) #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_;
  };

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

Solution

1. For a function or class, what is the difference between a forward declaration and a definition?

A forward declaration of a (possibly templated) function or class simply introduces a name. For example:

class widget;  // "widget" names a class 

widget* p;     // ok: allocates sizeof(*) space typed as widget*

widget  w;     // error: wait, what? how big is that? does it have a
               //        default constructor?

Again, a forward declaration only introduces a name. It lets you do things that require only the name, such as declaring a pointer to it—all pointers to objects are the same size and have the same set of operations you can perform on them, and ditto for pointers to nonmember functions, so the name is all you need to make a strongly-typed and fully-usable variable that’s a pointer to class or pointer to function.

What a class forward declaration does not do is tell you anything about what you can do with the type itself, such as what constructors or member functions it has or how big it is if you want to allocate space for one. If you try to create a widget w; with only the above code, you’ll get a compile-time error because widget has no definition yet and so the compiler can’t know how much space to allocate or what functions the type has (including whether it has a default constructor).

A class definition has a body and lets you know the class’s size and know the names and types of its members:

class widget { // "{" means definition
    widget();
    // ...
};

widget* p;     // ok: allocs sizeof(ptr) space typed as widget*

widget  w;     // ok: allocs sizeof(widget) space typed as widget
               //     and calls default constructor

2. In the following header file, what #include directives could be immediately removed without ill effect?

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. Class X does make use of streams, that’s true; but it doesn’t mention anything specifically from iostream, which mainly declares the standard stream objects like cout. At the most, X needs ostream alone for its basic_ostream type, and even that can be whittled down as we will see.

Guideline: Never #include unnecessary header files.

2. Replace ostream with iosfwd.

#include <ostream>

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

However, you can’t write the forward declaration yourself using something like class ostream;. First, ostream lives in namespace std in which you can’t redeclare existing standard types and objects. Second, ostream is an alias for basic_ostream<char> which you couldn’t reliably forward-declare even if you were allowed to because library implementations are allowed to do things like add their own extra template parameters beyond those required by the standard that of course your code wouldn’t know about—which is one 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, though: The standard library helpfully provides the header iosfwd, which contains forward declarations for all of the stream templates and their standard aliases, including basic_ostream and 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 like string and list. There are, however, no comparable “stringfwd” or “listfwd” standard headers. The iosfwd header was created to give streams special treatment for backwards compatibility, to avoid breaking code written in years past for the “old” non-templated version of the iostreams subsystem. It is hoped that a real solution will come in a future version of C++ that supports modules, but that’s a topic for a later time.

There, that was easy. We can now move on to…

… 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:

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—it’s the same as if it were a pointer, ostream*, discussed above. As long as that’s all we’re doing with the ostream&, there’s no need for a full ostream definition—we’re not really using an ostream itself at all, such as by calling functions on it, we’re only using a reference to type for which we only need to know the name. 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 now move on to get rid of one of the other headers, but only one 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 in function E h(E), so no definition is required and x.h shouldn’t be pulling in e.h in the first place because the caller couldn’t even be calling this function if he didn’t have the definition of E already, so there’s no point in including it again. (Note this would not be true if E were only a return type, such as if the signature were E h();, because in that case it’s good style to include E’s definition for the caller’s convenience so he can easily write code like auto val = x.h();.) All we need to do is replace #include “e.h” with class E;.

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

That’s it.

You may be wondering why we can’t get rid of the other headers yet. It’s because to define class X means you need to know its size in order to know how much space to allocate for an X object, and to know X’s size you need to know at least the size of every base class and data member. So we need the definitions of A and B because they are base classes, and we need the header definitions of list, C, and D because they are used to define the data members. How we can begin to address some of these is the subject of Part 2…

 

Acknowledgments

Thanks to the following for their feedback to improve this article: Gennaro, Sebastien Redl, Emmanuel Thivierge.

16 thoughts on “GotW #7a Solution: Minimizing Compile-Time Dependencies, Part 1

  1. Catching up…

    @Sebastian: I cover those in the other parts, but you’re right it would be easier to add a short note here. Done.

    @bcs: The point, which I’ve now clarified, is that the caller can’t even invoke E h(E) unless he already has an E object — so he already has its definition.

    @Emmanuel: As above, I’ve improved the text to distinguish between parameter and return types.

    Thanks for the comments.

  2. Sebastians comment is truly excellent, Sutter should definitely consider incorporating these explanations into the solution.

  3. For solution 1, you might want to write

    class widget { // "{" means definition
    public:
        widget();
        // ...
    };
    
    widget* p;     // ok: allocs sizeof(ptr) space typed as widget*
    
    widget  w;     // ok: allocs sizeof(widget) space typed as widget
                   //     and calls default constructor
    

    Otherwise, trying to compile this code will complain about the widge w line. (The linker ends up complaining anyway, later on…)

  4. To me to have public member function that return incomplete type is actually a bad style and a bad habit. Because you will have to include the header for the return type anyway. It makes dependencies explicit where it doesn’t need to be and it doesn’t help compile time because you need to include it anyway at the point of use. I find it really frustrating to include a file and to get compile errors because i need to include more file because of incomplete type. Those dependencies have no really good ways to be documented either and it would not be the good solution.

    I think the good solution is to just include those header for public member and non members functions. Your collegue will thanks you and it should impact the build time anyway.

  5. Yes technically you can replace “e.h” with “class E;” but that leads to the rather obnoxious situation where I’ve included “x.h” and then go try to use X::h and have to go track down which header E is defined in and include it as well.

  6. Given the boatload of wrong answers when you originally posted this question, and the lingering confusion now, it would have been a good idea to go through every use in more detail and document why it does or does not need the full definition. I’ll give it a try here.

    class X : public A, private B {

    This uses A and B as base classes and thus requires the full definition. This is independent of the access specifier on the inheritance.

    X( const C& );

    Reference to C, doesn’t need the full definition. References and pointers never need the full definition until you actually do something interesting with them, and just copying them around doesn’t count.

    B f( int, char* );

    B as a return type. A return type need not be complete until the function is actually called.

    C f( int, C );

    C as a return type and argument type. Argument types need not be complete either until the function is actually called. However, the overloading here might create a situation where the lack of a complete type leads to very unintuitive behavior. I’ll get to that later.

    C& g( B );

    Reference to C as return type, B as argument type. Nothing new here. However, if g was virtual, it would be interesting, because of covariant overriding. Another thing for later.

    E h( E );

    E as return type and argument type. Yawn.

    virtual std::ostream& print( std::ostream& ) const;

    Reference to std::ostream as argument and return type. Not special here either, and print being virtual doesn’t really change that. As I mentioned, derived classes might have to worry about establishing covariance.

    std::list&lt:C> clist;

    Formally, all standard containers require their member type to be complete when their definition is instantiated, so C must be complete here. Most implementations of std::list let you get away with not doing that, but I wouldn’t rely on it. So C needs to be complete.

    D d_;

    Data member of type D, requires a complete type.

    };

    At this point, the class ends, but some special members are not here. The compiler will add declarations for them:
    X(const X&); // copy ctor
    X(X&&); // move ctor
    X& operator =(const X&); // copy assignment
    X& operator =(X&&); // move assignment
    ~X(); // destructor

    Note that the compiler does not actually *define* these (and thus trigger instantiation of std::list’s corresponding
    member functions, which would *definitely* require the definition of C) until they are actually used. In other words, it doesn’t bite you here in the header, it bites the user of the class.

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

    As mentioned, this function needs to be inline to avoid ODR violations. Other than that, it only copies around references, so it doesn't need a full definition of ostream. Note that this would be different if any hierarchy conversions were going on.

    And this brings me to the two points I delayed.

    1) Covariance. If you have a virtual function that returns a reference or pointer to a class T, a derived class's override of the function can return a reference or pointer to a class S : public T instead. However, the compiler needs to actually know the classes are related. So in the above example, let's assume that g is virtual, and C only has a forward declaration (the list member is gone). Then I define a class Y:

    #include "x.h"
    
    class F; // derives from C
    
    class Y : public X {
    public:
      F& g(B) override; // error: compiler cannot establish relation between C and F
    };

    If the code instead includes the header for F (which in turn has to include the header for C, because F derives from it), then the code is fine.

    2) Overloading. There are two versions of f, both taking an int as the first argument. The second argument is char* for one and C for the other. Now imagine C is incomplete. Then I have a class S, which has an implicit conversion to char*, and I call x.f(0, S()). Which version will it call? Well, obviously the char* version, because S can be converted to that, and there is no relationship between C and S …
    … or is there? What if C, if it were fully defined, had a converting constructor from S? Then the second overload would be just as good as the first – and with just a small code modification (make f take a const char*), it could even be better!

    Unfortunately, I just scoured the standard for any indication on whether this is defined or undefined behavior, and as far as I can see, it's perfectly defined: if C is incomplete, the first overload will be called; if it is complete, the second. Only if the call is inside a template, not dependent and the completeness of C changes between the definition and instantiation of the template is the behavior undefined.

    The lesson here is: avoiding redundant includes is a good thing, but in some cases, you might want to include the header for a class even though your own header strictly doesn't need it, simply to avoid nasty surprises for the users of your class.

  7. It’s unnecessary as it’s only used as a parameter and/or return value. Compiler doesn’t need to know their size. Only class members that are of type

    E

    are required to be known, as their size is required to calculate the total size of the class.

  8. It’s unecessary as it’s only used as a parameter and/or return value. Compiler doesn’t need to know their size. Only class members that are of type E are required to be known, as their size is required to calculate the total size of the class.

  9. I didn’t understand, why “e.h” is unnecessary?
    class X{ …..
    E h(E ) ; // here definition of `E` needed or not ? If not , why?

  10. Is it worth avoiding inclusion of standard library headers (ostream in this case) when I can have them all in a precompiled header?

  11. Everything is fine but that doesn’t work that well for typedefs and templates (thankfully enum classes can be now forward declared). From my side I would add that it’s useful to create “x_fwd.hpp” files with all required typedefs. It requires some more work and keeping both files in sync, but it’s feasible. Templates can be nicely divided into two files, where one is a strict declaration, while the other file is the implementation. Impl. part you obviously want to use only in .cpp file.

  12. BC: From where did X suddenly get a member function to_string()? Also, you cannot get completely rid of everything. At the very minimum you need the forward decl to let the compiler know that there actually is a type std::ostream – which is just a typedef:

    typedef basic_ostream<char> ostream;
    

    The problem is the virtual function X::print( std::ostream& ). If you could make a template member function out of that, it’d be fine – but you can’t because it’s virtual. Merely throwing the virtual function out is a crude design alteration and if there are classes that inhereit from x and override it, you’ll have completely unwanted side-effects. It’s not the case that base classes of X declare the virtual function, because would have already been included in A.h.

    If it compiles anyway, it’s likely because the symbol is pulled from somewhere else – or because you simply thew out X::print( std::ostream& ).

    Also, if you have a function that spits out a string representation of the class, why would you need a overload for operator<< ? You can feed the string to any ostream already.

  13. I have a trick to avoid both #include and forward declaration for the operator <<, but I am not sure whether it is good thing to do. Could you give some opinion?

    template<class OS>
    OS& operator<< (OS& os, const X& x)
    {
    	return ( os << x.to_string() );
    }
    

    By letting the compiler deduce the output stream type, both #include and forward declaration are avoided, and the operator is now more generic. I am not sure whether it is a good practice but it works for me anyway.

  14. I’m aware that this isn’t the topic of this GotW, but one minor detail: shouldn’t X(const C&) be declared explicit, since there is no obvious indication that, by design, C should be implicitly convertible to X? Doesn’t that go against the general coding guideline in C++ Coding Standards (Item 40)?

    More in regards to coding conventions, is there any reason why clist doesn’t have a trailing underscore?

Comments are closed.