0

In the example below I have my parent class and two child classes. Objects of either child are stored in a vector of parent. Looping over the vector I only see method invocations from the parent class.

How can I get the method definition and vtables right and how to avoid the slicing effect. I have been doing Python for too long where something like this would work.

#include <iostream>
#include <vector>


using namespace std;

class A{
    public:

        virtual string print(){return string("A");};
};

class B: public A{
    virtual string print() final {return string("B");};
};

class C: public A{
    virtual string print() final {return string("C");};
};

int main()
{

   vector<A> v;
   v.push_back(B());
   v.push_back(C());

   for(auto x : v){
       cout << x.print() << endl;
   }

}

=>

$g++ -std=c++11 -o main *.cpp
$main
A
A
halfer
  • 19,824
  • 17
  • 99
  • 186
El Dude
  • 5,328
  • 11
  • 54
  • 101

2 Answers2

5

Let's look at your code:

vector<A> v;
v.push_back(B());
v.push_back(C());

Here, push_back accepts an argument of type A&& and moves it, using the move constructor of A, to construct the new element of the vector. So, your code:

  1. Creates a vector of As
  2. Constructs an instance of B, then calls A's move constructor on that instance of B to construct an instance of A (that isn't an instance of B) in the vector
  3. Does the same thing for C instead of B

If you want to use dynamic dispatch, you need to store pointers to your elements in the vector. Allocate the objects on the heap and keep handles to them using std::unique_ptr, which will deallocate the memory in its destructor.

// for std::unique_ptr, available since C++11, and std::make_unique, available since C++14
#include <memory>

// ...

std::vector<std::unique_ptr<A>> v;
v.push_back(std::make_unique<B>());
v.push_back(std::make_unique<C>());

Now that the vector uses std::unique_ptr, you need to use & when looping over the vector:

for(auto& x : v) {
    std::cout << x->print() << std::endl;
}

auto will not automatically put the reference. If you just use auto, then each element of the vector will be copied into x, but because std::unique_ptr's copy constructor is deleted, the code will not compile.

Del
  • 1,309
  • 8
  • 21
  • 1
    _Note that there is no slicing effect_ Wrong: The temporary `B` and `C` objects are sliced when they (or, properly speaking, their `A` base class) is copied/moved into the vector. – 1201ProgramAlarm Jan 29 '20 at 02:24
  • @1201ProgramAlarm Thanks, fixed. For some reason, I had the misremembered the definition of "slicing" and thought it meant something else. – Del Jan 29 '20 at 02:28
  • Thanks on the elaborate answer. I will try when I have time. I do not fully understand the last paragraph of copy and `code will not compile` though. Can you elaborate, link somewhere please? Super thankful! – El Dude Jan 29 '20 at 04:08
  • 1
    @ElDude Have you used [`std::unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr) before? This is a "smart pointer" that represents "unique ownership" of some resource. The resource is tied to the `std::unique_ptr`'s lifetime, and when its destructor runs, it cleans up the resource. As a result, copying an `std::unique_ptr` is an invalid operation, as two `std::unique_ptr`s would then be responsible for the same resource. Therefore, `std::unique_ptr`'s copy constructor is `delete`d. You can only use the move constructor, which *transfers* ownership. – Del Jan 29 '20 at 04:13
  • Yeah, used them. But long time ago. As I said, a lot of Python happening meanwhile that overrode C++. :facepalm :) – El Dude Jan 29 '20 at 04:16
  • AS for the for loop, you say to not use the `auto` type but the real type for my thing to work? – El Dude Jan 29 '20 at 04:18
  • 1
    @ElDude You can use `auto`, but you need to put the `&` after it, like `auto&`. That's because `auto` doesn't specify that the type is a reference. The full type for `auto` would be `std::unique_ptr`, and the type you want is `std::unique_ptr&`. – Del Jan 29 '20 at 04:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/206827/discussion-between-el-dude-and-types-logics-cats). – El Dude Jan 29 '20 at 04:22
1

Objects of either child are stored in a vector of parent.

No. A vector of parent can only store parent objects. Parent objects are not child objects.

Looping over the vector I only see method invocations from the parent class.

This is because the vector contains parent objects.

How can I get the method definition and vtables right

There is nothing wrong with the member function definitions or vtables.

and how to avoid the slicing effect.

You can avoid slicing effect by not slicing the base sub object off from the derived object. Dynamic polymorphism is only possible through indirection. With a pointer (or reference) you can point to a base object, that may be a base subobject of different derived objects. Example:

B b;
C c;
A* a;

a = &b;
a->print(); // dynamic dispatch invokes B::print
a = &c;
a->print(); // dynamic dispatch invokes C::print

A sliced = b;
sliced.print(); // static dispatch invokes A::print
                // sliced is an individual object that is not
                // base sub object of another
halfer
  • 19,824
  • 17
  • 99
  • 186
eerorika
  • 232,697
  • 12
  • 197
  • 326