Reputation: 54060
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 Car
s or Truck
s. (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
Reputation: 1389
You need to use pointers to Vehicle
.
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
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
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