There are a few cases where you have almost no choice but to store a pointer. One obvious one is when you're creating something like a binary tree:
template <class T>
struct tree_node {
struct tree_node *left, *right;
T data;
}
In this case, the definition is basically recursive, and you don't know up-front how many descendants a tree node might have. You're pretty much stuck with (at least some variation of) storing pointers, and allocating descendant nodes as needed.
There are also cases like dynamic strings where you have only a single object (or array of objects) in the parent object, but its size can vary over a wide enough range that you just about need to (at least provide for the possibility to) use dynamic allocation. With strings, small sizes are common enough that there's a fairly widely-used "short string optimization", where the string object directly includes enough space for strings up to some limit, as well as a pointer to allow dynamic allocation if the string exceeds that size:
template <class T>
class some_string {
static const limit = 20;
size_t allocated;
size_t in_use;
union {
T short_data[limit];
T *long_data;
};
// ...
};
A less obvious reason to use a pointer instead of directly storing a sub-object is for the sake of exception safety. Just for one obvious example, if you store only pointers in a parent object, that can (usually does) make it trivial to provide a swap
for those objects that gives the nothrow
guarantee:
template <class T>
class parent {
T *data;
void friend swap(parent &a, parent &b) throw() {
T *temp = a.data;
a.data = b.data;
b.data = temp;
}
};
With only a couple of (usually valid) assumptions:
- the pointers are valid to start with, and
- assigning valid pointers will never throw an exception
...it's trivial for this swap to give the nothrow
guarantee unconditionally (i.e., we can just say: "swap will not throw"). If parent
stored objects directly instead of pointers, we could only guarantee that conditionally (e.g., swap
will throw if and only if the copy constructor or assignment operator for T throws.")
For C++11, using a pointer like this often (usually?) makes it easy to provide an extremely efficient move constructor (that also gives the nothrow
guarantee). Of course, using a pointer to (most of) the data isn't the only possible route to fast move construction -- but it is an easy one.
Finally, there are the cases I suspect you had in mind when you asked the question -- ones where the logic involved doesn't necessarily indicate whether you should use automatic or dynamic allocation. In this case, it's (obviously) a judgement call. From a purely theoretical viewpoint, it probably makes no difference at all which you use in these cases. From a practical viewpoint, however, it can make quite a bit of difference. Even though neither the C nor C++ standard guarantees (or even hints at) anything of the sort, the reality is that on most typical systems, objects using automatic allocation will end up on the stack. On most typical systems (e.g., Windows, Linux) the stack is limited to only a fairly small fraction of the available memory (typically on the order of single-digit to low double-digit megabytes).
This means that if all the objects of these types that might exist at any given time might exceed a few megabytes (or so) you need to ensure that (at least most of) the data is allocated dynamically, not automatically. There are two ways to do that: you can either leave it to the user to allocate the parent objects dynamically when/if they might exceed the available stack space, or else you can have the user work with relatively small "shell" objects that allocate space dynamically on the user's behalf.
If that's at all likely to be an issue, it's almost always preferable for the class to handle the dynamic allocation instead of forcing the user to do so. This has two obvious good points:
- The user gets to use stack-based resource management (SBRM, aka RAII), and
- The effects of limited stack space are limited instead of "percolating" through the whole design.
Bottom line: especially for a template where the type being stored isn't known up-front, I'd tend to favor a pointer and dynamic allocation. I'd reserve direct storage of sub-objects primarily to situations where I know the stored type will (almost?) always be quite small, or where profiling has indicated that dynamic allocation is causing a real speed problem. In the latter case, however, I'd give at least some though to alternatives like overloading operator new
for that class.