C++ Forward Declarations
Sometimes, especially when writing libraries intended for others to use, you may not want to expose too many implementation details or force consumers to pull in unnecessary header files. Forward declarations let you avoid including other headers while still using the types declared in them.
Explanation
As the Forward declaration Wikipedia article states:
In computer programming, a forward declaration is a declaration of an identifier (denoting a programming entity such as a type, variable, or function) before its definition is given.
In short, suppose you want to use a class classA from some header file. Including that header—even transitively—can unnecessarily increase compilation times and, more importantly, expose implementation details contrary to the purpose of an interface.
You can instead write:
| |
As explained in “Usage of Forward Declaration of C++ Classes”:
C++ classes can be forward-declared. However, a class that has only been forward-declared but not defined is incomplete. Such a class can only be used to define pointers, references, and pointer/reference function parameters.
You cannot define objects of an incomplete class (because the compiler knows only that it is a class, not how large it is), nor can you access its members in any way (because the compiler does not yet know what members exist). Once the class has been fully defined, you can use it in all the usual ways.
Forward declarations work for ordinary classes, templates, and other constructs. They can also be combined with smart pointers, but there are some caveats to keep in mind.
Caveats
When you use smart pointers with forward declarations, you may encounter an error indicating that the type is undefined—as though the forward declaration has stopped working.
Check whether your class lacks a user-provided constructor or destructor, or whether you have defaulted them in the header file.
As Shital Shah explains in the Stack Overflow thread “Is std::unique_ptr<T> required to know the full definition of T?”:
It looks like current answers are not exactly nailing down why default constructor (or destructor) is problem but empty ones declared in cpp isn’t.
Here’s whats happening:
If outer class (i.e. MyClass) doesn’t have constructor or destructor then compiler generates the default ones. The problem with this is that compiler essentially inserts the default empty constructor/destructor in the .hpp file. This means that the code for default contructor/destructor gets compiled along with host executable’s binary, not along with your library’s binaries. However this definitions can’t really construct the partial classes. So when linker goes in your library’s binary and tries to get constructor/destructor, it doesn’t find any and you get error. If the constructor/destructor code was in your .cpp then your library binary has that available for linking.
This is nothing to do with using unique_ptr or shared_ptr and other answers seems to be possible confusing bug in old VC++ for unique_ptr implementation (VC++ 2015 works fine on my machine).
So moral of the story is that your header needs to remain free of any constructor/destructor definition. It can only contain their declaration. For example,
~MyClass()=default; in hpp won’t work. If you allow compiler to insert default constructor or destructor, you will get a linker error.One other side note: If you are still getting this error even after you have constructor and destructor in cpp file then most likely the reason is that your library is not getting compiled properly. For example, one time I simply changed project type from Console to Library in VC++ and I got this error because VC++ did not added _LIB preprocessor symbol and that produced exact same error message.
In other words, the core issue is that default constructors and destructors generated by the compiler get emitted into the header (.hpp) file, causing them to be compiled into the consuming executable rather than the library’s own binary. The linker then fails to find these definitions when linking the library. If instead you declare the constructor and destructor in the header and define them in the .cpp file, the definitions reside in the library binary and linking succeeds.
This is unrelated to the use of unique_ptr or shared_ptr in particular. Other answers in the thread may reference a confusing bug in older VC++ implementations of unique_ptr (VC++ 2015 works fine on the author’s machine).
The takeaway: your header must contain only declarations of constructors and destructors, not definitions. For example, ~MyClass() = default; in the header will not work—allowing the compiler to generate a default constructor or destructor inline will cause a linker error.
Additional note: if you still encounter this error despite having constructor and destructor definitions in the .cpp file, the most likely cause is that the library itself is not being compiled correctly. For example, the author once changed a VC++ project type from Console to Library and received the same error because VC++ did not add the _LIB preprocessor symbol, producing an identical error message.
The solution is to declare constructors and destructors in the header and define them in the source file. As Paul’s answer demonstrates:
my_class.h
1 2 3 4 5 6 7 8 9#include <memory> class Thing; class MyClass { ~MyClass(); // <--- Added std::unique_ptr< Thing > my_thing; };my_class.cpp
1MyClass::~MyClass() = default; // Or a custom implementation
References
- Forward declaration
- C++ class forward declaration usage
- [Is std::unique_ptr<T> required to know the full definition of T?>]