1

I have two classes like this (simplified and renamed):

class Base
{
 public:
  virtual void operator()( ) { cout << "Base\n";}
};

class Derived : public Base
{
 public:
  void operator()( ) { cout << "Derived\n"; }
};

void doSomething( Base obj )
{
  obj( );
}

int main( )
{
  list<Base> bList;
  Derived d1, d2, d3;
  bList.push_back( d1 );
  bList.push_back( d2 );
  bList.push_back( d3 );

  for( list<Base>::iterator it = bList.begin(); it != bList.end(); it++ )
    {
      doSomething( *it );
    }
  return 0;
}

When I run this, I get:

Base
Base
Base

i.e. the Base::operator() is called. Obviously this is not what I want to happen. I tried changing doSomething to

void doSomething( Base& obj )

but this only works if called directly with a Derived object and not with one from the list. And references can't be stored in a list.
Is there any way how I can make doSomething "see" that it has to call a derived class' function (without having to resort to pointers)?

Edit: Problem is pointed out, for other people who might run into this here is a link: What is object slicing?

Community
  • 1
  • 1
J. Curwen
  • 143
  • 1
  • 1
  • 6

3 Answers3

7

The following:

list<Base> bList;
Derived d1, d2, d3;
bList.push_back( d1 );
bList.push_back( d2 );
bList.push_back( d3 );

slices your objects. They are no longer of type Derived, but Base. You need to use pointers or smart pointers in your container for the expected behavior.

Read up on object slicing.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
2

The problem isn't that the compiler doesn't "see" that it needs to call a function in a derived class, the problem is that you are only storing the base class in your container list<Base>. This results in what is commonly called object slicing. When the compiler copies your Derived objects into the container that can only hold Base objects, it can only copy the base portion of the object and "slices" off the part that makes it a Derived object.

In order to make use of polymorphism with containers that follow value semantics (ie, assume that you store the actual object in them and not a form of reference to an object), you need store a pointer to said object. I would advise against storing raw pointers in a standard library container as it introduces additional lifecycle management issues - you'd have to control the lifetimes of the pointed-to objects from outside the container, which tends to be a really bad idea and usually either leads to memory leaks or dangling pointers, if not both.

My recommendation would be that if you can use boost, use one of the ptr_container template classes that were explicitly designed for this purpose. If you can't and you're on a modern enough compiler, you can use a std::list<std::shared_ptr<Base> > > and rely on the shared_ptrto manage the objects' lifecycles.

Timo Geusch
  • 24,095
  • 5
  • 52
  • 70
  • in C++11 he can use `std::unique_ptr` if no sharing is required. It is more efficient that managing reference counting with only one subject. – Emilio Garavaglia Apr 29 '12 at 17:02
0

The answers above lead me to the ultimate solution. The short answer is, "No. You must use a list of pointers." Here's what I came up with...

#include <iostream>
#include <memory>
#include <list>

class Base
{
    public:
        virtual void foo()
        {
            std::cout << "Base::foo" << std::endl;
        }
};

class Derived : public Base
{
    public:
        void foo()
        {
            std::cout << "Derived::foo" << std::endl;
            Base::foo();
        }
};

int main()
{
    std::list<std::shared_ptr<Base>> bases;
    auto p = std::make_shared<Derived>();
    bases.push_back(p);

    for (std::list<std::shared_ptr<Base>>::iterator i = bases.begin(); i != bases.end(); i++)
    {
        (*i)->foo();
    }

    return 0;
}
BenMaGoo
  • 45
  • 1
  • 8