-3

It's been a long3 time since I programmed in C++. My polymorphism isn't working: the map<string, Base> converts my ArmyBase and NavyBase objects to Base objects when I add them to the map, so GetDescription() returns an empty string rather than the values I set via ArmyBase::SetDescription() and NavyBase::SetDescription(). Here's the extremely rough pseudo-code:

class Base
{ protected:
    string STR__Description;  // Pardon my non-standard style
  public:
    virtual
    string GetDescription()
    { return STR__Description;
    }
    void   SetDescription( string str__Description )
    { STR__Description = str__Description;
    }
}

class ArmyBase: public Base
{ public:
    string GetDescription()
    { return STR__Description + " (Army)";
    }
}

class NavyBase: public Base
{ public:
    string GetDescription()
    { return STR__Description + " (Navy)";
    }
}

It sounds like map<string, Base*> causes memory leaks and I'd rather not upgrade mid-project to use shared_ptr. Would storing the derived-class instances in a container that "destructs" them properly allow me to use the pointer map for polymorphism without risk of memory leakage?

Base                         base;
ArmyBase                     base_Army;
set<ArmyBase>                set__ArmyBases;
map<string, Base*>::iterator iter_Bases;
map<string, Base*>           map__Bases;
NavyBase                     base_Navy;
set<NavyBase>                set__NavyBases;
...
while( ... )
{   base_Army = ArmyBase();
    base_Navy = NavyBase();
    ...
    set__ArmyBases.insert(                                        base_Army   );
    map__Bases.insert(     pair<string, Base*>( "Boca Raton",    &base_Army ) );
    ...
    set__NavyBases.insert(                                        base_Navy   );
    map__Bases.insert(     pair<string> Base*>( "NAS Pensacola", &base_Navy ) );
    ...
    base = iter_Bases->second;
    std::cout << ..." " << base->GetDescription() << std::endl;
}

Desired output from map__Bases:

Boca Raton ... (Army)
NAS Pensacola ... (Navy)
...
pbyhistorian
  • 341
  • 2
  • 10
  • 2
    It's perfectly fine to use a `map`, but you'll have to `delete` the pointers yourself. The map doesn't cause memory leak per say, it's just that its design is such that it doesn't `delete` the pointers it contains itself. – Nbr44 Jul 24 '13 at 08:20
  • please provide a [SSCCE](http://sscce.org/) – TemplateRex Jul 24 '13 at 08:21
  • Maybe you want to polish up a bit about terms like slicing, lifetime and freestore. – PlasmaHH Jul 24 '13 at 08:21
  • Do you want deep copy, shallow copy, or disallow copy for your `map`? – jxh Jul 24 '13 at 08:21
  • BTW, you should not be using [Hungarian notation](http://stackoverflow.com/q/111933/819272) (like your `set__NavyBases` or `STR__description`), and I'd also not use double underscores anywhere (it's very uncommon and if you ever forget a prefix you can get name clashes with macro names). – TemplateRex Jul 24 '13 at 08:27
  • For me it is not clear what is meant with this source code, it looks very bugy. I don't see a way to compile it without making a lot of assumtions about what is meant. – Jan Herrmann Jul 24 '13 at 08:28
  • I think the problem is that base_Army and base_Navy fall out of scope and thus get deleted automatically. The pointers stored in the maps don't point to the values which were copied (by value) to the map. – Kristian Duske Jul 24 '13 at 08:34
  • @Nbr44, I was hoping to avoid mass manual deletion, but I'm already doing individual deletions. This code is a metaphor; in the real code, the "Bases" come and go. – pbyhistorian Jul 24 '13 at 16:06
  • @PlasmHH, this issue taught me about slicing; I'll brush up on the other two. – pbyhistorian Jul 24 '13 at 16:13
  • @jxh, I don't plan to perform a copy on the `map`. – pbyhistorian Jul 24 '13 at 16:13

5 Answers5

1

You should create and use pointers of objects using "new" to use polymorphism in c++. What you are experiencing is called object slicing.

mkirci
  • 169
  • 5
  • He is creating a full object just casting to a pointer to base class so there is no slicing here – mathematician1975 Jul 24 '13 at 08:22
  • @TemplateRex Yes, but he creates the objecs using assignment where the slicing occurs. – mkirci Jul 24 '13 at 08:27
  • @TemplateRex Also, the objects are created on the stack, so no delete is needed. – mkirci Jul 24 '13 at 08:28
  • @mathematician1975 Unless the code provided above is not missing a new operator, I do not see any part creating a pointer of an object. He just passes the address of some object on the stack to a container. – mkirci Jul 24 '13 at 08:30
  • He creates an object of derived type and adds the address of it to a map. That derived class pointer passed to the map insert function will be cast to a base class pointer and polymorphism will STILL occur. Call the virtual function on that pointer in the map and the derived override of it will be called. Your answer implies that only dynamic allocation of objects is the ONLY way to get polymorphic behaviour and this is not true. – mathematician1975 Jul 24 '13 at 11:02
  • @mkirci, I didn't miss the `new` operator. Due to Java, I type `new` automatically, but the compiler (MinGW) made me take it out. – pbyhistorian Jul 24 '13 at 16:26
  • @mathematician1975, it was while trying to avoid slicing that I hit upon this approach. So even though overall the approach is risky and inefficient, thanks for confirming that I at least overcame slicing. – pbyhistorian Jul 25 '13 at 02:27
1

The problem is that the map entries point to the adresses of the objects which you created on the stack. You copy those objects into your sets, but you don't store the addresses of the copies in your maps. When the original objects fall out of scope, they get deleted automatically and thus your map entries become invalid.

I think the best way to solve your problem is to allocate your objects on the heap and store pointers in your containers. This, of course, requires you to be careful about memory management. I know three options to handle this:

  1. Manual memory management: Delete objects when you erase them from your container. This is dangerous and error-prone, of course, but with some care, you can make it work. I usually do this by wrapping the container in another class that will manage the objects, i.e., the wrapper has methods such as add(Base* base) and remove(Base* base), and it will also delete all objects in the container in its destructor. Of course, you must still take care not to delete such managed objects outside of the wrapper.

  2. Smart pointers: Use either shared_ptr or unique_ptr (depending on ownership semantics) and store those in the container. The smart pointers will take care of deleting the objects when they are removed from the container.

  3. Use a custom allocator. You can parametrize the std containers with allocators which should allow the container to delete the objects when they are removed from the map. However, I have never done this and can't comment on whether it's a good idea. I hear that writing custom allocators is quite difficult to get right.

I would suggest either 1 and 2. I think it depends on taste and on other requirements which one you actually use, but if you use the smart pointer option, then make sure that you choose the smart pointer that models your particular ownership semantics (most likely unique_pointer).

Kristian Duske
  • 1,769
  • 9
  • 14
  • Thank you, Kristian. I don't know yet when I'm creating on the stack vs. the heap (`new`?) but I know the stack won't work. Though my natural tendency is toward #1, it's probably beyond my current skill level. Smart pointers seem to be the generally accepted strategy. – pbyhistorian Jul 24 '13 at 22:03
  • Thank you all for spending time on this poor question. Kristian, then TempleRex convinced me to use smart pointers. But Nbr44, mathematician1975, Kristian, and jxh gave guidance on how the original approach should be improved, if used. – pbyhistorian Jul 25 '13 at 03:22
0

To little information to really work out exactly what your problem is. But I'll have a stab going from what's available.

How do you detect memory leakage? One obvious problem if ArmyBase/NavyBase contains non-POD members is that its destructor won't be called if you delete from base, hence you need a virtual destructor for Base.

The second solution you've suggested using several different containers is almost certainly the wrong approach in this case. A different container for each derived type defeats the whole purpose of polymorphism in the first place. Besides, your loop doesn't make any sense at all. set__ArmyBases/set__NavyBases contains identical default constructed objects, while map__Bases just contains pointers to the 2 global variables base_Navy & base_Army that gets re initialized every frame.

Ylisar
  • 4,293
  • 21
  • 27
  • I haven't tried this yet. It looked like I had run into slicing so I stopped to ask for directions. The virtual destructor is on my ToDo list. The other two containers may serve a purpose: each supports a window dedicated to that type. I shouldn't have converted them to `set`s in the example; they are `map`s and their keys are used in the Bases `map`. This way, I can use the key from `map__Bases` to fetch the actual `ArmyBase` or `NavyBase` from the appropriate `map` when the user double-clicks it. – pbyhistorian Jul 24 '13 at 16:43
  • The example loop was only intended to show the mechanism of using pointers, but following TempleRex' SSCCE suggestion would have been better. I was just afraid we'd end up debugging my code instead of discussing the general idea. – pbyhistorian Jul 24 '13 at 16:55
0

Apart from a ridiculous amount of typos in your code (which is why you should always provide a SSCCE), your code can be made to work after two small corrections:

  • write a small comparison function object Cmp that compares the descriptions by following the pointers to Base
  • change your set<ArmyBase> and set<NavyBase> as set<Base*, Cmp> and set<Base*, Cmp>.

Code:

#include <string>
#include <set>
#include <map>
#include <iostream>

using namespace std;

class Base
{ protected:
    string STR__Description;  // Pardon my non-standard style
  public:
    virtual
    string GetDescription() const
    { return STR__Description;
    }
    void   SetDescription( string str__Description )
    { STR__Description = str__Description;
    }
};

class ArmyBase: public Base
{ public:
    string GetDescription() const
    { return STR__Description + " (Army)";
    }
};

class NavyBase: public Base
{ public:
    string GetDescription() const
    { return STR__Description + " (Navy)";
    }
};

class Cmp
{
public:
    bool operator()(Base const* lhs, Base const* rhs) const
    { return lhs->GetDescription() < rhs->GetDescription(); };
};

int main()
{

    Base                         base;
    ArmyBase                     base_Army;
    set<Base*, Cmp>           set__ArmyBases;
    map<string, Base*>      map__Bases;
    map<string, Base*>::iterator         iter_Bases;
    NavyBase                     base_Navy;
    set<Base*, Cmp>          set__NavyBases;


    base_Army = ArmyBase(); 
    base_Navy = NavyBase();

    set__ArmyBases.insert(                   &base_Army   );
    map__Bases.insert(     pair<string, Base*>( "Boca Raton",    &base_Army ) );


    set__NavyBases.insert(                   &base_Navy   );
    map__Bases.insert(     pair<string, Base*>( "NAS Pensacola", &base_Navy ) );

    for (auto b: map__Bases)
        std::cout << b.first << "..." << b.second->GetDescription() << std::endl;
}

Live example with output.

There is no object slicing or memory errors going on. However, you are using raw pointers to scoped objects (i.e. their descructors are called when they go out of scope).

The recommended way for having containers of polymorphic objects is to use std::map<std::string, std:unique_ptr<Base>> and to allocate these through std::make_unique<NavyBase>("NAS Penscalola").

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Well, I tried to set expectations by saying it was pseudocode but you're right: an SSCCE would have been better. I suspect I would have gotten it working in the process, but still wouldn't have known if this was a good approach. The answer seems to be "NO!". Thanks for taking the time to write an example. You and Kristian are both saying smart pointers are the way to go, so I'm looking into what it will take to get them working with MinGW 4.6.2. – pbyhistorian Jul 24 '13 at 20:29
  • Darn. I think I misdirected some of your effort by using `set`. I was simply (mis)using `set` as a repository to hold the derived-class instances (which are just database Beans). `map` would have been better, since the key string could be used to link the polymorphic pointers back to the derived-class instances. – pbyhistorian Jul 25 '13 at 03:06
0

You should be able to get something close to what you want using Boost Intrusive associative containers. Unlike the Standard Library containers, they do not store a copy but a reference to your object. This should allow your polymorphism to work when retrieving the object from the intrusive container.

From the documentation:

The main difference between intrusive containers and non-intrusive containers is that in C++ non-intrusive containers store copies of values passed by the user. ...

On the other hand, an intrusive container does not store copies of passed objects, but it stores the objects themselves. ...

   acme_intrusive_list<MyClass> list;

   MyClass myclass;
   list.push_back(myclass);

   //"myclass" object is stored in the list
   assert(&myclass == &list.front());

In practical terms, this means you should use one or more simple Standard Library containers (like list<>) to actually store your derived instances, but you add those instances to the Boost.Intrusive container. Since the Boost.Intrusive container holds references, if you destroy the actual instances, the Boost.Intrusive container will be corrupted.

Community
  • 1
  • 1
jxh
  • 69,070
  • 8
  • 110
  • 193