Writing containers for abstract types

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?
It doesn't really matter if it's abstract or not. If you have a container containing objects of some base class you will not be able to store objects of a derived class in it.

1
2
3
4
class Base {};
class Derived : public Base {};

std::vector<Base> vec; // You can't store Derived objects in here! 


If you try to add a Derived object to the vector, it will not give an error but the object that is stored in the vector will be of Base type and with all members of Derived stripped off. This phenomenon is called object slicing. http://en.wikipedia.org/wiki/Object_slicing

1
2
Derived obj;
vec.push_back(obj); // object slicing 


You are more or less forced to store pointers in the container when dealing with inheritance. I recommend you use a smart pointer, like std::unique_ptr (C++11), to have it automatically deallocated when it's removed from the vector.
Last edited on
Thanks for the information. std::vector<std::unique_ptr> sounds like a better approach for this case. That way copy-construction can be avoided all together.
Topic archived. No new replies allowed.