Contents
In my last post, I wrote about forward declarations for normal classes. Today, I give you some information about forward-declaring templates and enums.
Forward-declaring Enums
As I wrote in the last post, the compiler does not always need to know the definition of a class. It needs one if we use one of its members or the base class, or if it has to know how large objects of that class are. One should think that the same applies to enums, but that’s not the case. Forward-declaring plain old enums is not possible.
The good news is, that we can provide forward declarations for scoped enums aka. enum classes. We also can forward declare enums with explicit underlying types. Both features have been introduced in C++11:
enum OldEnum; //ERROR
enum WithUnderlyingType : short; //OK
enum class Scoped; //OK
enum class ScopedWithType : int; //OK
The actual definitions of the enums obviously have to match the declarations. Scoped enums that are not explicitly declared or defined with an underlying type, the underlying type is int
. That means it does not matter whether the definition of Scoped
explicitly adds int
, and whether the definition of ScopedWithType
does not mention it.
Forward-declaring class templates
Forward-declaring class templates is as easy as a normal class declaration:
template <typename T, typename U> class X;
It is also possible to provide forward declarations for specializations of those class templates:
template <typename U> class X<int, U>;
template <> class X<int, int>;
Using incomplete types in templates
When we instantiate a class template that is parametrized with one of our types, the question arises whether it is sufficient to only have a forward declaration of our type. Let’s, for example, take this class definition:
class MyClass {
//...
std::shared_ptr<MyOtherClass> pOther;
};
Is a forward declaration of MyOtherClass
OK, or do we have to #include the full definition? The answer depends on the class template, in this case, shared_ptr
. As we recall, a forward declaration of shared_ptr
is not enough here, because the compiler needs to know the size. That depends on the implementation of shared_ptr
and whether it contains or inherits from MyOtherClass
.
It may not be much of a surprise, that shared_ptr
only stores a pointer to its argument type, so a forward declaration of MyOtherClass
should be OK. Except for the fact that shared_ptr
defines functions that use the argument type. That means, that wherever we trigger the instantiation of one of those functions, MyOtherClass
needs to be defined as well.
At first glance, that may seem OK since we usually only use the member functions of class members in the source file. However, one of those member functions is the destructor. If MyClass
does not explicitly define a destructor, the compiler will do that for us. The destructor will also call the destructor of pOther
, which contains a call to the destructor of MyOtherClass
.
Whether and where we need the definition of MyOtherClass
therefore depends on where we or the compiler define the destructor and special member functions.
Rule of thumb: use fully-defined types in templates
One of the points in using smart pointers is the Rule of Zero. We don’t want to care about destructors and the like. Another point about using abstractions like class templates is, we should not need to know the exact implementation details. At least not enough to figure out whether the implementation needs us to define the template argument or whether just forward-declaring it is enough.
And, even if we know the implementation details of such a template, we should not depend on that knowledge. What happens it the template implementation changes and suddenly needs the definition of its argument? Every class that only provides a forward declaration will break.
Bottom-line is that, in general, it is better to #include the definition of our template arguments. Only in the rare case where we need to micromanage our compile-time dependencies, we can try to use a forward declaration instead.
Forward-declaring library classes
With all I’ve written about forward declarations, it might be tempting to provide forward declarations for classes and other entities provided by libraries. For example, if I only declare a function that takes a string, why should I have to #include <string>
and all the stuff that comes with it?
namespace std {
class string;
}
Do not do this! It’s simply wrong. std::string
is not a class, but a typedef to std::basic_string<char>
. And no, you can’t simply add a forward declaration to template <class C> class basic_string<CharT>;
because that’s not all there is to it, either.
There surely are things in other libraries that are easier to provide forward declarations for, right? Don’t be tempted to forward declare those classes and templates, either. Libraries change, classes become type aliases and vice versa. Those changes will then break your code in nasty ways.
If, however, you happen to be on the other side and write libraries yourself, consider to provide headers that contain forwarding declarations for your classes and templates. An example is the standard header <iosfwd>
that provides forward declarations for things related to iostreams and the like.
Permalink
Thanks for this post, it is an interesting topic that I am also currently looking into to reduce the compile time of our code base.
Please note that having a shared_ptr (where MyOtherClass is forward-declared) wont force you to move the destructor of MyClass into the Cpp-file. The destructor of shared_ptr does work with incomplete types, as it is a (in common implementations) virtual function call to a deleter that was created when constructing a shared_ptr from a MyOtherClass-Pointer. So only when using a construction-function (e.g. reset(T) or the constructor of shared_ptr taking a T) the definition of MyOtherClass needs to be present.
Permalink
it’s a great feature of C++11 that we can forward declare enums.
But I think it was available in MSVC, as an extension even before C++11
Permalink
What strategies do you suggest for avoiding writing all your template code in a ‘public’ header?
In cases where you know you’ll be using the templated code with just a few specific types, can you not hive that off into a separate library with pre-instantiated instances of the classes just with the specific types? Your code can then link to the concrete classes instead?
If the code is genuinely used as generic in your code, this isn’t necessarily possible, but seems we over-pollute our headers when we put the templated class implementation direct into our public headers….
Permalink
What you suggest is possible, although it reduces the usefulness of templates. It depends largely on the use case I’d say.
Permalink
Why do you make distinct the difference between your code and ‘library’ code. Your code could also change its implementation?
I notice that the Google guidelines suggest never to forward declare individual classes, for the ‘library’ reason unless you do so via a ‘libfwd’ header.
Permalink
Thanks for the comment! Our own code is under our control, and we know better how likely we are to make changes that would break forward declarations.
Having said that, it can of course be a good idea to have forwarding headers for our own common classes, too.
Permalink
As an individual, yes perhaps, but as part of a larger team I am wary of the cons of this approach.
Perhaps the header speedup for builds could be achieved in other ways?
e.g. https://www.viva64.com/en/b/0549/ comparing single threaded, parallel, unity and precompiled header approaches