0

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.

Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156

2 Answers2

4

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.

Community
  • 1
  • 1
Hampus Nilsson
  • 6,692
  • 1
  • 25
  • 29
2
 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.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • +1 Thanks also for the destructor warning. If I do not provide a destructor (as I have done here only for debugging purposes) I guess the compiler would take care of that for me? – Evgeniy Berezovsky Jul 16 '12 at 07:29
  • @EugeneBeresovksy in this case, no. You **need** a virtual destructor, even if it's empty. – Luchian Grigore Jul 16 '12 at 07:31
  • Good to know. One last thing: Smart pointers aren't in the C++ standard until C++11, correct? – Evgeniy Berezovsky Jul 16 '12 at 07:32
  • 1
    @EugeneBeresovksy I think there's `auto_ptr`, but I'd use something from boost. – Luchian Grigore Jul 16 '12 at 07:33
  • 2
    Just to make that one clear: Don't use auto_ptr as elements in a container. – user331471 Jul 16 '12 at 07:39
  • 1
    @EugeneBeresovksy Actually, there are some smart pointers in [TR1](http://en.wikipedia.org/wiki/C%2B%2B_Technical_Report_1), which is supported by some compilers that don't support C++11 yet. For example, VS2008 (I think with SP1) has shared_ptr and weak_ptr. – MikMik Jul 16 '12 at 08:02
  • @user331471 Thanks for the heads up. This would seem to not apply to `shared_ptr`, though, as mentioned by MikMik – Evgeniy Berezovsky Jul 16 '12 at 09:13