5

I have a boost::ptr_map which stores abstract base class (e.g. VectorWrapperBase) as values and this allows me to map strings to vectors of different types.

boost::ptr_map<std::string, VectorWrapperBase> memory_map;
//...
memory_map.insert(str_key, new VectorWrapper<T>());

This appears to work. However, when I have memory_map as a member of another class and attempt to store that class in an std::map, compilation fails.

class AgentMemory {
  //...
  private:
    boost::ptr_map<std::string, VectorWrapperBase> memory_map;
};

std::map<std::string, AgentMemory> agent_map;
//...
agent_map.insert(std::pair<std::string, AgentMemory>(agent_name, AgentMemory()));

The last line fails with:

/SOMEPATH/boost_1_48_0/boost/ptr_container/clone_allocator.hpp:34
   error: cannot allocate an object of abstract type ‘xyz::VectorWrapperBase’

Being new to C++, this is baffling.

I suspect that error is down to the map insertion copying the AgentMemory object which involves cloning the ptr_map. And since my VectorWrapper objects are not cloneable, the error is raised.

My questions are:

  • Why am I getting the error? (Are my suspicions even close to what's actually happening?)
  • How do I address this?

To address the compilation error, I've considered the following, but without much experience with C++ can't decide which is more appropriate:

  1. Remove the pure specifier (= 0) so VectorWrapperBase is no longer abstract
    • This feels like a hack since VectorWrapperBase should never be instantiated
  2. Make the VectorWrappers cloneable
    • This seems to work, but in my use case only empty containers are assigned to the top-level map so VectorWrappers within the inner ptr_map need never be cloned. The cloneability would therefore be there just to appease the compiler and does not reflect the actual usage.
  3. Forget ptr_map and use a std::map and shared_ptr instead.
    • I'm less keen on this solution as I would like the lifetime of the vector wrappers to be linked to that of the map. I'm also a little concerned (perhaps unnecessarily so?) about the potential overheads of extensive use of shared_ptr in a heavily multi-threaded application.
Shawn Chin
  • 84,080
  • 19
  • 162
  • 191

1 Answers1

0

The statement

agent_map.insert(std::pair<std::string, AgentMemory>(agent_name, AgentMemory()));

will call the default constructor of AgentMemory, which in turn will call the default constructor of boost::ptr_map<std::string, VectorWrapperBase>, which will try to call the non-existent constructor for the abstract base class VectorWrapperBase.

So you have to make sure that every constructor of types wrapping or inheriting VectorWrapperBase should always construct a concrete derived class. In your case, option 3 (a map of shared pointers to derived classes) could be sensible, but that depends on the larger context of your code.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Thanks for the explanation. What confuses me is that the `ptr_map` starts off empty so why is there a need to call the constructor for `VectorWrapperBase`? – Shawn Chin May 09 '12 at 12:18
  • @ShawnChin As I explained in my answer, the `insert()` call with the `AgentMemory()` argument will generate the constructor call. – TemplateRex May 09 '12 at 13:58
  • Perhaps I misunderstood, but `VectorWrapperBase` is not a member of `AgentMemory` and so I don't see why an instance of it should be created. That said, I now appreciate the fact that `VectorWrapperBase` needs to be cloneable since the `ptr_map` needs to be copied by the copy constructor of the encapsulating class. – Shawn Chin May 09 '12 at 14:08