7

Testing polymorphism & virtual functions & shared_ptr, I am trying to understand the situation described by the following minimal example.

class B{
public:
  // Definition of class B
  virtual void someBMethod(){
   // Make a burger
  };
};

class C : public B {
public:
  // Definition of class C
  void someBMethod(){
   // Make a pizza
  };
};

class A{
public:
  A(B& SomeB) : Member(std::make_shared<B>(SomeB)){};
  std::shared_ptr<B> Member;
};

Now, in the main we can have

int main(){
  C SomeC;
  A SomeA(SomeC);
  A.Member->someBMethod(); // someBMethod from B is being executed.
};

Unless I didn't include some mistake from my actual code to the minimal example, I think SomeC is getting sliced down to a B, or at least someBMethod from B is being calledin the last line.

Question: What should be the right way to initialize Member in such a way that the method someBMethod from C gets called?

Kae
  • 279
  • 2
  • 10
  • `Member(std::make_shared(SomeB))` creates a new instance of `B`, via implicit `B(const B&)` copy constructor, that instance has no relation to the original `C` instance – Piotr Skotnicki Oct 08 '14 at 16:07
  • Thanks @PiotrS. Do you know the answer to my question? i.e. What is the right way to initialize ..? – Kae Oct 08 '14 at 16:09
  • what is your goal? `std::make_shared` explicitly says "build an instance of B", not "build some instance, of exact same type as `SomeB` and then cast it up and store in my shared_ptr" – Piotr Skotnicki Oct 08 '14 at 16:13
  • @PiotrS. My goal is for the class `A` to have a member that points to a `B` but that I can put there any derived class from `B` and call its methods (the methods of the derived class). – Kae Oct 08 '14 at 16:15

2 Answers2

8

you're performing slicing by calling std::make_shared<B>(SomeB) This will construct a shared_ptr pointing to a new object of type B and construct that object by using the copy-constructor on B: B::B(const B& b) slicing off all information about the C-ness of SomeB.

change A to:

class A{
public:
  A(const std::shared_ptr<B>& pB) : pMember(pB) {}
  std::shared_ptr<B> pMember;
};

And main:

int main(){
  A SomeA(std::make_shared<C>());
  A.pMember->someBMethod(); // someBMethod from C is being executed.
}
Community
  • 1
  • 1
BeyelerStudios
  • 4,243
  • 19
  • 38
3

I think SomeC is getting sliced down to a B

That's exactly what's happening. make_shared makes a new object of the specified type, forwarding its arguments to a suitable constructor. So this makes a new B, initialised using its copy-constructor to copy the B sub-object of SomeC.

What should be the right way to initialize Member in such a way that the method someBMethod from C gets called?

That's tricky: C is not shared, but Member is, and you can't have it both ways. It's probably best if you require the user to pass in a shared pointer, exposing the fact that it is to be shared with this class:

A(std::shared_ptr<B> SomeB) : Member(SomeB){}

If you really want to allow it to use a non-shared object, you could create a shared pointer with a dummy deleter, so it doesn't try to share ownership:

A(B& SomeB) : Member(std::shared_ptr<B>(&SomeB, [](B*){})){}

but beware that you are now responsible for ensuring that C isn't destroyed until after A, and any copy of it, no longer requires it. You've lost the safety of an "owning" shared pointer.

Whatever you do, don't simply create a shared pointer from &SomeB. The default deleter will try to delete it, which is an error because it wasn't dynamically created.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • Interesting! I have another question but I think it is too trivial and it is not going to be well received if I post it as an independent post. What I want is to have a class `A` that has a member that works like a plugin. So that `A` can use its functionality of the dynamic type of the plugin. Can you expand your answer to include some ideas about this? – Kae Oct 08 '14 at 16:24
  • Of course, your change to the constructor of `A` now avoids slicing the `C`. So, it is doing what I want. what I am trying to ask in the comment above is if this is is the common way to have a 'plugin member'? – Kae Oct 08 '14 at 16:27
  • @Karene: Then I think the answer is the same: the best option is to make `A` take `shared_ptr`, and always use smart pointers to manage plugins. – Mike Seymour Oct 08 '14 at 16:28
  • Sorry. I think I found a way, it seems to look nicer. Can you help me judge if it is satisfactory? The idea is: In the constructor of A, in the initialization, to put `A() : Member(&SomeB){};`. This way the pointer `Member` gets the address of `SomeB` and that is all. – Kae Oct 08 '14 at 16:41
  • @Karene: No, that will go horribly wrong - the shared pointer will try to delete the object, but the object mustn't be deleted since it wasn't dynamically allocated. If you really, really want to be able to use non-dynamic objects for some reason, give the shared pointer a dummy deleter as in my second suggestion. And be very careful about the lifetimes of the object and the pointers. – Mike Seymour Oct 08 '14 at 16:44
  • I see. I just saw the consequences when I ran it. the polymorphic functions gets called just fine, but when the program begins to wrap up to close it complains. – Kae Oct 08 '14 at 16:45