Adam Ernst
Adam Ernst

Reputation: 54060

C++ Abstract Class as std::map key

I have a class hierarchy like this:

struct Vehicle {
  virtual string model() = 0; // abstract
  ...
}
struct Car : public Vehicle {...}
struct Truck : public Vehicle {...}

I need to keep a std::map with some information that I acquire about some Vehicle instances:

std::map<Vehicle, double> prices;

However I get the following error:

/usr/include/c++/4.2.1/bits/stl_pair.h: In instantiation of ‘std::pair<const Vehicle, double>’:
/usr/include/c++/4.2.1/bits/stl_map.h:349:   instantiated from ‘_Tp& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const _Key&) [with _Key = Vehicle, _Tp = double, _Compare = std::less<Vehicle>, _Alloc = std::allocator<std::pair<const Vehicle, double> >]’
test.cpp:10:   instantiated from here
/usr/include/c++/4.2.1/bits/stl_pair.h:73: error: cannot declare field ‘std::pair<const Vehicle, double>::first’ to be of abstract type ‘const Vehicle’
Model.hpp:28: note:   because the following virtual functions are pure within ‘const Vehicle’:
Model.hpp:32: note:     virtual string Vehicle::model()

So you can't use an abstract class as a std::map key. As far as I can tell this is because maps copy their keys (via copy-constructor or assignment operator) and this would imply instantiating an abstract class (Vehicle). Also even if you could, we'd fall prey to object slicing anyway.

What should I do?

It seems I can't use pointers because there might be separate copies of logically identical Cars or Trucks. (i.e. Two Car objects instantiated separately, but which represent the same car and operator== returns true. I need these to map to the same object in the std::map.)

Upvotes: 8

Views: 6216

Answers (3)

Begemoth
Begemoth

Reputation: 1389

  1. You need to use pointers to Vehicle.

  2. operator== is not used by the std::map but a compare functor which is the third parameter of the std::map. By default it is std::less. You need to implement your own compare functor to work with Vehicle:

     struct less_vehicle: std::binary_function<const Vehicle *, const Vehicle *, bool>
     {
       bool  operator() (const Vehicle *a, const Vehicle *b) const { ... }
     };
    

And then use it:

std::map<Vehicle *, double, less_vehicle>

Upvotes: 10

Mac
Mac

Reputation: 14791

You cant use an ABC as a key, because having an instance of an ABC doesn't make sense.

With the line std::map<Vehicle, double> prices; you're saying, "make a map with Vehicle instances as keys". Since you can't have instances of Vehicle (as it is pure virtual), you can't have them as keys to your map either.

It's not something I've dabbled with before, but I believe using a custom allocator with the map you can use Vehicle pointers with checks for logically identical pointees.

Upvotes: 1

Jerry Coffin
Jerry Coffin

Reputation: 490108

You'll need to use pointers, and pass a template argument to specify the type of object that will carry out the comparison (and when you create the map, pass an object of that type to do the comparisons).

For your comparison, you'll want that to dereference the pointers and compare the objects they point at. Also note, however, that for this to succeed you'll need to define some sort of comparison between a Car and a Truck. It doesn't necessarily have to be particularly meaningful, but it does have to be consistent and transitive (the official terminology is that it must define a "strict weak ordering").

Upvotes: 1

Related Questions