Categories
c++ c++-faq forward-declaration

When can I use a forward declaration?

665

I am looking for the definition of when I am allowed to do forward declaration of a class in another class’s header file:

Am I allowed to do it for a base class, for a class held as a member, for a class passed to member function by reference, etc. ?

6

  • 19

    I desperately want this to be renamed “when should I”, and the answers updated appropriately…

    – deworde

    Jun 8, 2015 at 7:51

  • 17

    @deworde When you say when “should” you are asking for opinion.

    – AturSams

    Mar 24, 2016 at 14:12

  • @deworde it is my understanding that you want to use forward declarations whenever you can, to improve build time and avoid circular references. The only exception I can think of is when an include file contains typedefs, in which case there’s a tradeoff between re-defining the typedef (and risking it changing) and including an entire file (along with its recursive includes).

    Dec 7, 2016 at 9:29

  • @OhadSchneider From a practical perspective, I’m not a big fan of headers that my. ÷

    – deworde

    Dec 7, 2016 at 10:16

  • basically always require you to include a different header in order to use them (forward decl of constructor parameter is a big culprit here)

    – deworde

    Dec 7, 2016 at 10:23

1062

Put yourself in the compiler’s position: when you forward declare a type, all the compiler knows is that this type exists; it knows nothing about its size, members, or methods. This is why it’s called an incomplete type. Therefore, you cannot use the type to declare a member, or a base class, since the compiler would need to know the layout of the type.

Assuming the following forward declaration.

class X;

Here’s what you can and cannot do.

What you can do with an incomplete type:

  • Declare a member to be a pointer or a reference to the incomplete type:

    class Foo {
        X *p;
        X &r;
    };
    
  • Declare functions or methods which accept/return incomplete types:

    void f1(X);
    X    f2();
    
  • Define functions or methods which accept/return pointers/references to the incomplete type (but without using its members):

    void f3(X*, X&) {}
    X&   f4()       {}
    X*   f5()       {}
    

What you cannot do with an incomplete type:

  • Use it as a base class

    class Foo : X {} // compiler error!
    
  • Use it to declare a member:

    class Foo {
        X m; // compiler error!
    };
    
  • Define functions or methods using this type

    void f1(X x) {} // compiler error!
    X    f2()    {} // compiler error!
    
  • Use its methods or fields, in fact trying to dereference a variable with incomplete type

    class Foo {
        X *m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    

When it comes to templates, there is no absolute rule: whether you can use an incomplete type as a template parameter is dependent on the way the type is used in the template.

For instance, std::vector<T> requires its parameter to be a complete type, while boost::container::vector<T> does not. Sometimes, a complete type is required only if you use certain member functions; this is the case for std::unique_ptr<T>, for example.

A well-documented template should indicate in its documentation all the requirements of its parameters, including whether they need to be complete types or not.

23

  • 7

    Great answer but please see mine below for the engineering point on which I disagree. In short, if you don’t include headers for incomplete types you accept or return, you force an invisible dependency on the consumer of your header having to know which others they need.

    – Andy Dent

    Jul 6, 2013 at 2:55

  • 3

    @AndyDent: True, but the consumer of the header only needs to include the dependencies (s)he actually uses, so this follows the C++ principle of “you only pay for what you use”. But indeed, it can be inconvenient for the user who would expect the header to be standalone.

    Jul 8, 2013 at 11:45

  • 11

    This set of rules ignores one very important case: you need a complete type to instantiate most templates in the standard library. Particular attention needs to be paid to this, because violating the rule results in undefined behavior, and may not cause a compiler error.

    Jul 12, 2013 at 8:11

  • 19

    +1 for the “put yourself in the compiler’s position”. I imagine the “compiler being” having a mustache.

    Sep 1, 2013 at 15:31


  • 3

    @JesusChrist: Exactly: when you pass an object by value, the compiler needs to know its size in order to make the appropriate stack manipulation; when passing a pointer or a reference, the compiler does not need the size or layout of the object, only the size of an address (i.e. the size of a pointer), which does not depend on the type pointed to.

    Nov 24, 2014 at 8:12


52

The main rule is that you can only forward-declare classes whose memory layout (and thus member functions and data members) do not need to be known in the file you forward-declare it.

This would rule out base classes and anything but classes used via references and pointers.

3

  • 8

    Almost. You can also refer to “plain” (i.e. non-pointer/reference) incomplete types as parameters or return types in function prototypes.

    Feb 16, 2009 at 16:04

  • What about classes that I want to use as members of a class that I define in the header file? Can I forward declare them?

    – Igor

    Feb 16, 2009 at 16:15

  • 1

    Yes, but in that case you can only use a reference or a pointer to the forward-declared class. But it does let you have members, nevertheless.

    – Reunanen

    Feb 16, 2009 at 16:21

36

Lakos distinguishes between class usage

  1. in-name-only (for which a forward declaration is sufficient) and
  2. in-size (for which the class definition is needed).

I’ve never seen it pronounced more succinctly 🙂

2

  • 2

    What is in-name-only mean?

    – Boon

    Dec 25, 2014 at 15:58

  • 4

    @Boon: dare I say it…? If you use only the class’ name?

    Jan 1, 2015 at 19:52