When can I use a forward declaration?
Asked Answered
P

13

695

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. ?

Polonium answered 16/2, 2009 at 15:31 Comment(6)
I desperately want this to be renamed "when should I", and the answers updated appropriately...Accessory
@Accessory When you say when "should" you are asking for opinion.Mathura
@Accessory 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).Arawak
@OhadSchneider From a practical perspective, I'm not a big fan of headers that my. ÷Accessory
basically always require you to include a different header in order to use them (forward decl of constructor parameter is a big culprit here)Accessory
As a rule circular dependencies are best avoided through better OOP design - exceptions always apply (e.g. visitor); which answers the "when the should I". However exceptions to rules are exceptional cases, so to answer "when can I"; ideally, rarely without justificationBoisleduc
F
1102

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.

Filtrate answered 16/2, 2009 at 16:24 Comment(21)
if we include the class's header file , will we be able to use it as members type and etc right? If we include only the class header file will it be enough ? and thus would then the forward declaration be unnecessary ?Spectator
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.Blamable
@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.Filtrate
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.Jannjanna
What about the new operator? Does it need to know the complete type? e.g. X * myFunc(){ return new X; }Sulfonation
@ontherocks: Yes, the type must be defined.Filtrate
@Sulfonation Just like sizeof, it needs to know the size of the type.Hagood
Why it's ok to work with pointer or reference to forward declared class but not a class itself passed by value? It's because the size of the pointer and reference always known at compile time agnostic to type?Suffumigate
@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.Filtrate
Can I use it to define function which return 'const X*` e.g. const X* do_something( void ) ?Waziristan
@Dilawar: Yes you can, as long as the body of the function does not need the definition of X (it cannot create an X, or access its members for instance).Filtrate
Just a comment on well-documented template types, start by searching their doc for "incomplete", eg in boost::shared_ptr: The class template is parameterized on T, the type of the object pointed to. shared_ptr and most of its member functions place no requirements on T; it is allowed to be an incomplete type, or void.Resa
It would be nice to add @Resa comment to answer. It's exactly what I needed to know.Libel
It might be worth it to note that you can also do this for smart pointers now that those exist.Flinty
Why is it ok to declare function with incomplete return type? if it's returned by value, doesn't the compiler need to know it's memory layout?Eviaevict
@HannaKhalil: The compiler needs to know its memory layout only at the call site and if you store the return value in a variable, in which case you need to include the type definition.Filtrate
I have a question: if forward declaration is used, then all cpp files including this header file should also include the header file of the forward declared one, is it a burden?Vespine
@LucTouraille: What if something includes the header but doesn't call the function? In that case, there's technically no reason for the including file to expect it needs to also include that other file in which the returned type is defined. Does the standard allow a return type to be only forward declared if returned by value?Mccutcheon
@CraigScott If the including file does not call the function, there is indeed no need for the definition of the returned type. The users of a header only need the defintion of the types they actually use, thereby making for faster compilation. An incomplete type can be used as a return type when declaring a function, but not when defining (implementing) it, nor when calling it.Filtrate
Luc: (link) as long as the body of the function does not need the definition My guess is that @Waziristan really meant "declare" when they said "define", while asking about what their own function could do with an incomplete type. In that case, said function's declaration is probably in a header and the definition is probably in a cpp file, so the latter can #include/define the complete type and use it for everything.Croaky
Just by including the header file and without forward declaring the class i can construct the class object as member variable.Rycca
J
54

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.

Jerry answered 16/2, 2009 at 15:35 Comment(3)
Almost. You can also refer to "plain" (i.e. non-pointer/reference) incomplete types as parameters or return types in function prototypes.Indigested
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?Polonium
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.Berlinda
S
38

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

Serge answered 21/7, 2009 at 7:44 Comment(2)
What is in-name-only mean?Pippin
@Boon: dare I say it...? If you use only the class' name?Serge
I
34

As well as pointers and references to incomplete types, you can also declare function prototypes that specify parameters and/or return values that are incomplete types. However, you cannot define a function having a parameter or return type that is incomplete, unless it is a pointer or reference.

Examples:

struct X;              // Forward declaration of X

void f1(X* px) {}      // Legal: can always use a pointer
void f2(X&  x) {}      // Legal: can always use a reference
X f3(int);             // Legal: return value in function prototype
void f4(X);            // Legal: parameter in function prototype
void f5(X) {}          // ILLEGAL: *definitions* require complete types
Indigested answered 16/2, 2009 at 16:3 Comment(0)
T
26

None of the answers so far describes when one can use a forward declaration of a class template. So, here it goes.

A class template can be forwarded declared as:

template <typename> struct X;

Following the structure of the accepted answer,

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 in another class template:

     template <typename T>
     class Foo {
         X<T>* ptr;
         X<T>& ref;
     };
    
  • Declare a member to be a pointer or a reference to one of its incomplete instantiations:

     class Foo {
         X<int>* ptr;
         X<int>& ref;
     };
    
  • Declare function templates or member function templates which accept/return incomplete types:

     template <typename T>
        void      f1(X<T>);
     template <typename T>
        X<T>    f2();
    
  • Declare functions or member functions which accept/return one of its incomplete instantiations:

     void      f1(X<int>);
     X<int>    f2();
    
  • Define function templates or member function templates which accept/return pointers/references to the incomplete type (but without using its members):

     template <typename T>
        void      f3(X<T>*, X<T>&) {}
     template <typename T>
        X<T>&   f4(X<T>& in) { return in; }
     template <typename T>
        X<T>*   f5(X<T>* in) { return in; }
    
  • Define functions or methods which accept/return pointers/references to one of its incomplete instantiations (but without using its members):

     void      f3(X<int>*, X<int>&) {}
     X<int>&   f4(X<int>& in) { return in; }
     X<int>*   f5(X<int>* in) { return in; }
    
  • Use it as a base class of another template class

     template <typename T>
     class Foo : X<T> {} // OK as long as X is defined before
                         // Foo is instantiated.
    
     Foo<int> a1; // Compiler error.
    
     template <typename T> struct X {};
     Foo<int> a2; // OK since X is now defined.
    
  • Use it to declare a member of another class template:

     template <typename T>
     class Foo {
         X<T> m; // OK as long as X is defined before
                 // Foo is instantiated. 
     };
    
     Foo<int> a1; // Compiler error.
    
     template <typename T> struct X {};
     Foo<int> a2; // OK since X is now defined.
    
  • Define function templates or methods using this type

     template <typename T>
       void    f1(X<T> x) {}    // OK if X is defined before calling f1
     template <typename T>
       X<T>    f2(){return X<T>(); }  // OK if X is defined before calling f2
    
     void test1()
     {
        f1(X<int>());  // Compiler error
        f2<int>();     // Compiler error
     }
    
     template <typename T> struct X {};
    
     void test2()
     {
        f1(X<int>());  // OK since X is defined now
        f2<int>();     // OK since X is defined now
     }
    

What you cannot do with an incomplete type:

  • Use one of its instantiations as a base class

     class Foo : X<int> {} // compiler error!
    
  • Use one of its instantiations to declare a member:

     class Foo {
         X<int> m; // compiler error!
     };
    
  • Define functions or methods using one of its instantiations

     void      f1(X<int> x) {}            // compiler error!
     X<int>    f2() {return X<int>(); }   // compiler error!
    
  • Use the methods or fields of one of its instantiations, in fact trying to dereference a variable with incomplete type

     class Foo {
         X<int>* m;            
         void method()            
         {
             m->someMethod();      // compiler error!
             int i = m->someField; // compiler error!
         }
     };
    
  • Create explicit instantiations of the class template

     template struct X<int>;
    
Tryout answered 31/10, 2015 at 5:0 Comment(2)
"None of the answers so far describe when one can the forward declaration of a class template." Isn't that simply because the semantics of X and X<int> are exactly the same, and only the forward-declaring syntax differs in any substantive way, with all but 1 line of your answer amounting to just taking Luc's and s/X/X<int>/g? Is that really needed? Or have I missed a tiny detail that's different? It's possible, but I've visually compared a few times and can't see any...Croaky
Thank you! That edit adds a tonne of valuable info. I'll have to read it several times to fully understand it... or maybe use the often-better tactic of waiting until I get horribly confused in real code and coming back here! I suspect I'll be able to use this to reduce dependencies in various places.Croaky
B
5

I'm writing this as a separate answer rather than just a comment because I disagree with Luc Touraille's answer, not on the grounds of legality but for robust software and the danger of misinterpretation.

Specifically, I have an issue with the implied contract of what you expect users of your interface to have to know.

If you are returning or accepting reference types, then you are just saying they can pass through a pointer or reference which they may in turn have known only through a forward declaration.

When you are returning an incomplete type X f2(); then you are saying your caller must have the full type specification of X. They need it in order to create the LHS or temporary object at the call site.

Similarly, if you accept an incomplete type, the caller has to have constructed the object which is the parameter. Even if that object was returned as another incomplete type from a function, the call site needs the full declaration. i.e.:

class X;  // forward for two legal declarations 
X returnsX();
void XAcceptor(X);

XAcepptor( returnsX() );  // X declaration needs to be known here

I think there's an important principle that a header should supply enough information to use it without a dependency requiring other headers. That means header should be able to be included in a compilation unit without causing a compiler error when you use any functions it declares.

Except

  1. If this external dependency is desired behaviour. Instead of using conditional compilation you could have a well-documented requirement for them to supply their own header declaring X. This is an alternative to using #ifdefs and can be a useful way to introduce mocks or other variants.

  2. The important distinction being some template techniques where you are explicitly NOT expected to instantiate them, mentioned just so someone doesn't get snarky with me.

Blamable answered 6/7, 2013 at 2:54 Comment(4)
"I think there's an important principle that a header should supply enough information to use it without a dependency requiring other headers." - another issue is mentioned in a comment by Adrian McCarthy on Naveen's answer. That provides a sound reason not to follow your "should supply enough information to use" principle even for currently non-templated types.Miso
You are talking about when you should (or shouldn't) use forward declarion. That is totally not the point of this question, though. This is about knowing the technical possibilities when (for example) want to break a circular dependency problem.Pyromagnetic
I disagree with Luc Touraille's answer So write him a comment, including a link to a blog post if you need the length. This doesn't answer the question asked. If everyone thought questions about how X works justified answers disagreeing with X doing that or debating limits within which we should restrain our freedom to use X - we'd have almost no real answers.Croaky
IMHO, this answer depends on the position of a developer. Eg: application developers and library developers might have different opinions.Moonwort
R
4

In file in which you use only Pointer or Reference to a class.And no member/member function should be invoked thought those Pointer/ reference.

with class Foo;//forward declaration

We can declare data members of type Foo* or Foo&.

We can declare (but not define) functions with arguments, and/or return values, of type Foo.

We can declare static data members of type Foo. This is because static data members are defined outside the class definition.

Ras answered 16/2, 2009 at 15:40 Comment(0)
D
3

As long as you don't need the definition (think pointers and references) you can get away with forward declarations. This is why mostly you'd see them in headers while implementation files typically will pull the header for the appropriate definition(s).

Degrading answered 16/2, 2009 at 15:34 Comment(0)
H
3

The general rule I follow is not to include any header file unless I have to. So unless I am storing the object of a class as a member variable of my class I won't include it, I'll just use the forward declaration.

Hildahildagard answered 16/2, 2009 at 16:28 Comment(2)
This breaks encapsulation and makes code brittle. To do this, you need to know if the type is a typedef or a class for a class template with default template parameters, and if the implementation ever changes, you'll need to update ever place you've used a forward declaration.Ninetta
@AdrianMcCarthy is right, and a reasonable solution is to have a forward declaration header that's included by the header whose content it forward declares, which should be owned/maintained/shipped by whomever owns that header too. For example: the iosfwd Standard library header, which contains forward declarations of iostream content.Miso
D
0

You will usually want to use forward declaration in a classes header file when you want to use the other type (class) as a member of the class. You can not use the forward-declared classes methods in the header file because C++ does not know the definition of that class at that point yet. That's logic you have to move into the .cpp-files, but if you are using template-functions you should reduce them to only the part that uses the template and move that function into the header.

Desirae answered 16/2, 2009 at 15:36 Comment(1)
This makes no sense. One cannot have a member of an incomplete type. Any class's declaration must provide everything all users need to know about its size and layout. Its size includes the sizes of all of its non-static members. Forward-declaring a member leaves users with no idea of its size.Croaky
M
0

Take it that forward declaration will get your code to compile (obj is created). Linking however (exe creation) will not be successfull unless the definitions are found.

Mollusc answered 16/2, 2009 at 15:36 Comment(1)
Why ever did 2 people upvote this? You're not talking about what the question is talking about. You mean normal - not forward - declaration of functions. The question is about forward-declaration of classes. As you said "forward declaration will get your code to compile", do me a favour: compile class A; class B { A a; }; int main(){}, and let me know how that goes. Of course it won't compile. All the proper answers here explain why and the precise, limited contexts in which forward-declaration is valid. You instead have written this about something totally different.Croaky
I
0

I just want to add one important thing you can do with a forwarded class not mentioned in the answer of Luc Touraille.

What you can do with an incomplete type:

Define functions or methods which accept/return pointers/references to the incomplete type and forward that pointers/references to another function.

void  f6(X*)       {}
void  f7(X&)       {}
void  f8(X* x_ptr, X& x_ref) { f6(x_ptr); f7(x_ref); }

A module can pass through an object of a forward declared class to another module.

Ingenue answered 8/6, 2016 at 18:35 Comment(1)
"a forwarded class" and "a forward declared class" might be mistaken to refer to two very different things. What you've written follows directly from concepts implicit in Luc's answer, so while it would've made a good comment adding overt clarification, I'm not sure it justifies an answer.Croaky
M
0

As, Luc Touraille has already explained it very well where to use and not use forward declaration of the class.

I will just add to that why we need to use it.

We should be using Forward declaration wherever possible to avoid the unwanted dependency injection.

As #include header files are added on multiple files therefore, if we add a header into another header file it will add unwanted dependency injection in various parts of source code which can be avoided by adding #include header into .cpp files wherever possible rather than adding to another header file and use class forward declaration wherever possible in header .h files.

Maloriemalory answered 13/7, 2019 at 15:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.