I typically use one of the two ways.
- First way is to
typedef
at the place-of-first-declaration.
- Second way is to
typedef
at each place-of-use, and make it only visible to that place-of-use (by putting it inside the class or method that uses it).
(1) Put the typedef close to the type that is being wrapped.
/* MyAttrType.h */
#include <Optional.h>
// README : See Optional<MyAttrType> at the end of this header
struct MyAttrType
{
// ....
};
// (put the typedef here)
typedef Optional<MyAttrType> OptionalMyAttrType;
/* ---------------------------- */
/* Every other C++ source files */
#include "MyAttrType.h"
// .... Any code can use OptionalMyAttrType there.
(2) Make the typedef visible only to each class that uses it.
/* MyAttrType.h */
namespace my_pod_types
{
struct MyAttrType { /* ... */ };
}
// (nothing else outside.)
/* ---------------------------- */
/* Every other C++ source files */
#include "MyAttrType.h"
#include "Optional.h"
class MyClassX
{
private:
typedef Optional<my_pod_types::MyAttrType> OptionalMyAttrType;
public:
// ...
private:
OptionalMyAttrType m_optAttr;
};
Most of the time, the template relevant to me is either std::unique_ptr
and std::shared_ptr
. Furthermore, I will just decide that a class will either use one way or the other, and then typedef that smart pointer wrapper as "MyClassOnePtr". Draconian, but a library's main author is supposed to know what is the best for the library most of the time.
If it is not obvious which one of the two smart pointers should be preferred, then I will not put the typedef in the first header (so, I won't use option #1 unless the choice is obvious.)
Most of the time, you will realize that option #2 does not always shield the use of Optional<T>
from the end-user. That is, the application logic may require users of MyClassX
to deal with Optional<T>
when interacting with it. When that happens, you can't hide it anymore. It is not an implementation detail; it is part of the visible surface.
Finally, you should be aware of the C++ limitation to forward-declare things. Namely, there are times where C++ needs to know up-front:
- Size of the type being wrapped;
- Existence of a default constructor and destructor;
- And so on.
See this question on Stackoverflow (about unique::ptr
) for examples of such limitations.
Another unrelated thing I would like to share, after reading ixrec's answer.
I started following a "nothing in the default namespace" policy after finding that you can't get Doxygen (a widely used C++ documentation to HTML generator) to generate well-organized documentation unless you categorize your classes by using namespaces. It seems it doesn't matter how you name those namespaces; as long as Doxygen sees them as distinct, and human users don't complain about it, any namespacing approach will be fine.
Best Answer
When writing a templated C++ class, you usually have three options:
(1) Put declaration and definition in the header.
or
Pro:
Con:
Foo
as member, you need to includefoo.h
. This means that changing the implementation ofFoo::f
propagates through both header and source files.Lets take a closer look at the rebuild impact: For non-templated C++ classes, you put declarations in .h and method definitions in .cpp. This way, when the implementation of a method is changed, only one .cpp needs to be recompiled. This is different for template classes if the .h contains all you code. Take a look at the following example:
Here, the only usage of
Foo::f
is insidebar.cpp
. However, if you change the implementation ofFoo::f
, bothbar.cpp
andqux.cpp
need to be recompiled. The implementation ofFoo::f
lives in both files, even though no part ofQux
directly uses anything ofFoo::f
. For large projects, this can soon become a problem.(2) Put declaration in .h and definition in .tpp and include it in .h.
Pro:
Con:
This solution separates declaration and method definition in two separate files, just like .h/.cpp. However, this approach has the same rebuild problem as (1), because the header directly includes the method definitions.
(3) Put declaration in .h and definition in .tpp, but dont include .tpp in .h.
Pro:
Con:
Foo
member to a classBar
, you need to includefoo.h
in the header. If you callFoo::f
in a .cpp, you also have to includefoo.tpp
there.This approach reduces the rebuild impact, since only .cpp files that really use
Foo::f
need to be recompiled. However, this comes at a price: All those files need to includefoo.tpp
. Take the example from above and use the new approach:As you can see, the only difference is the additional include of
foo.tpp
inbar.cpp
. This is inconvenient and adding a second include for a class depending on whether you call methods on it seems very ugly. However, you reduce the rebuild impact: Onlybar.cpp
needs to be recompiled if you change the implementation ofFoo::f
. The filequx.cpp
needs no recompilation.Summary:
If you implement a library, you usually do not need to care about rebuild impact. Users of your library grab a release and use it and the library implementation does not change in the user's day to day work. In such cases, the library can use approach (1) or (2) and it is just a matter of taste which one you choose.
However, if you are working on an application, or if you are working on an internal library of your company, the code changes frequently. So you have to care about rebuild impact. Choosing approach (3) can be a good option if you get your developers to accept the additional include.