Evgeniy Berezovsky
Evgeniy Berezovsky

Reputation: 19218

Putting derived classes into STL maps as values

I have 2 simple classes. Base class A, and a derived class B. For debugging purposes, copy constructor and destructor are overridden to cout stuff:

class A
{
protected:
    char * c;
public:
    A(char * c) : c(c) { cout << "+A " << this << "\n"; }
    A(const A& other) : c(other.c) { cout << "*A " << this << "\n"; }
    ~A() { cout << "-A " << this << "\n"; }
};

class B : public A
{
public:
    B(char * c) : A(c) { cout << "+B " << this << "\n"; }
    B(const B& other) : A(other.c) { cout << "*B " << this << "\n"; }
    ~B() { cout << "-B " << this << "\n"; }
};

Here's how I insert an instance of B into the map:

    {
        cout << "-- 1\n";
        map<string, A> m;
        cout << "-- 2\n";
        m.insert(pair<string, A>( "b", B("bVal")));
        cout << "-- 3\n";
    }
    cout << "-- 4 --\n";

The result of that:

-- 1
-- 2
+A 0051F620
+B 0051F620
*A 0051F5EC
*A 00BD8BAC
-A 0051F5EC
-B 0051F620
-A 0051F620
-- 3
-A 00BD8BAC
-- 4 --

As regards creation of instances, I read this as follows:

  1. B gets created by my own code
  2. that B gets copy-constructed into an A by pair
  3. that last A instance then gets copied once more by the map, where it is inserted

Btw changing the pair in the insert line to a pair<string, B> did not do the trick, it only changed the 2nd step to create an B instead of the A, but the last step would downgrade it to an A again. The only instance in the map that remains until the map itself is eligible for destruction seems to be that last A instance.

What am I doing wrong and how am I supposed to get a derived class into a map? Use maps of the format

map<string, A*>

perhaps?

Update - Solution As my solution is not stated directly in the accepted answer, but rather buried in its comments in form of suggestions, here's what I ended up doing:

map<string, shared_ptr<A>> m;

This assumes your build environment supports shared_ptrs - which mine (C++/CLI, Visual Studio 2010) does.

Upvotes: 0

Views: 1395

Answers (2)

Hampus Nilsson
Hampus Nilsson

Reputation: 6822

This is called slicing and happens when a derived class does not fit into the object it has been assigned to. This is the tirivial example:

B b;
A a = b; // This would only copy the A part of B.

If you want to store different types of object in a map, using map<..., A*> is the way to go, as you correctly deduced. Since the object is not stored inside the map but rather somewhere on the heap, there is no need to copy it and it always fits.

Upvotes: 4

Luchian Grigore
Luchian Grigore

Reputation: 258568

 map<string, A> m;

will slice the inserted object, which will become an A, losing all other information (it is no longer a B).

Your intuition is correct, you'll need to use pointers or, better yet, smart pointers.

Also, the destructor in A needs to be virtual:

class A
{
    //...
    virtual ~A() { cout << "-A " << this << "\n"; }
};

otherwise deleting an object of actual type B through a pointer to an A leads to undefined behavior.

Upvotes: 2

Related Questions