Abstract types (those with pure virtual methods) generally cannot be stored in standard STL containers because they cannot be copy-constructed (or constructed at all). The quick answer is to store pointers to an abstract type in the container, but then the user of the container is responsible for the pointed-to object's scope. What is an acceptable way to write a container for an abstract type?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
class Abstract {
virtual void run() = 0;
};
class Concrete {
virtual void run() override;
};
class AbstractContainer {
std::vector<Abstract*> abstracts;
template<typename T>
void insert(const T& obj) {
abstracts.push_back(new T(obj));
}
}
|
Intuition lead me to this solution.
insert
is a template function, so it will copy-construct an object of the Concrete type onto the heap, and then store a pointer to it in abstracts. AbstractContainer has control of the object's scope, and all objects in the container may be treated as Abstracts. All sounds good, but I'm uncertain if this is bad practice or not. There is, of course, an extra level of indirection (and overhead) when using
new
. In addition, the objects are no longer stored in contiguous memory (as std::vector would normally guarantee), increasing the likelihood of cache misses.
Thomas Becker described a type-erasure class ("any"), whose implementation is similar to the one I came up with (it copy-constructs onto the heap), except that it uses a dedicated wrapper class for storing the pointer, rather than storing the pointers directly in the container.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
class placeholder
{
public:
virtual ~placeholder() {}
virtual placeholder* clone() const=0;
};
//And this is the wrapper class template:
template<typename ValueType>
class holder : public placeholder
{
public:
holder(ValueType const & value) : held(value) {}
virtual placeholder* clone() const
{return new holder(held);}
private:
ValueType held;
};
//The actual type erasing class any is a handle class that holds a pointer to the abstract base class:
class any
{
public:
any() : content(0) {}
template
any(ValueType const & value) : content(new holder(value)) {}
any(any const & other) :
content(other.content ? other.content->clone() : 0) {}
~any()
{delete content;}
// Implement swap as swapping placeholder pointers, assignment
// as copy and swap.
private:
placeholder* content;
};
|
The advantage to this pattern, I suppose, is that an "any" object can be used just like any other object; it need not be an element of a container. In my specific case, however, this isn't really necessary, and I'd rather avoid the overhead than have a means of copy-constructing an abstract type outside the container. Are there other advantages to his type-erasure pattern that I'm not aware of?
So, is it really that simple? Is there a standard container already written for this kind of problem? Is there a better way?