0

I haven't found any issues quite like this yet: but if someone finds one then sorry. I've been trying to use std::shared_ptr to greatly simplify memory management, however I've come across what must be some sort of bug.

When I create a DerivedType pointer with std::make_shared<type>(DerivedType(...)) It can only be addressed as a Type rather than a DerivedType.

Yet when I use the syntax std::shared_ptr<Type>(new DerivedType) the vfptr table lists the correct entries and it can be cast to a DerivedType without a problem.

I believe there should be no difference. Is this a bug in my understanding? or an actual bug?

Thanks for your help. Luke

Quentin
  • 62,093
  • 7
  • 131
  • 191
Luke Dickety
  • 7
  • 1
  • 4
  • 5
    Use `shared_ptr p = make_shared(...);` otherwise you slice the object the way you're constructing it by passing a `DerivedType` instance to the move constructor of `type` – Praetorian Feb 13 '15 at 00:49
  • What does "It can only be addressed as a Type rather than a DerivedType" mean? – philipxy Feb 13 '15 at 00:51
  • 1
    @philipxy: It means that the object's dynamic type is `Type` not `DerivedType`. Which is to be expected, if you tell `make_shared` to make a `Type`. – Mike Seymour Feb 13 '15 at 02:40

2 Answers2

4

You must pass to std::make_shared the parameters that you'd pass to your type's constructor, and they are forwarded.
You can then convert the resulting pointer to a base pointer implicitly.

std::shared_ptr<Type> p = std::make_shared<DerivedType>(...);

Let's dig a bit in the "why" in your question.

std::shared_ptr<Type>(new DerivedType);

This does work, and does nothing noteworthy. However, std::make_shared is usually preferred, because the latter can allocate std::shared_ptr's bookkeeping data along with your object, and thus is faster and cheaper, as well as exception-safe*.

std::make_shared<Type>(DerivedType(...))

You saw it : it doesn't work. What happens here is that you create a DerivedType instance. std::make_shared happily forwards it to the constructor of Type.
Overload resolution happens, and Type's copy constructor (or its move constructor, if available) is called, with a reference to your DerivedType's base part. And a plain Type object gets instantiated and pointed to. The original DerivedType vanishes. This issue as a whole is called slicing.

* Note on exception safety : Shoud you use a function of the form :

void func(std::shared_ptr<A> a, std::shared_ptr<B> b);

... constructing the std::shared_ptrs like so :

func(std::shared_ptr<A>(new A), std::shared_ptr<B>(new B);

... can leak if, for example, new A is called, then new B, and the latter throws an exception. The A has not yet been wrapped into a smart pointer, and is unrecoverable.

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • This answer is correct but would be better if the word "slice" appeared somewhere (see @Praetorian's comment on the question). Also he is correct that in C++11 it could be the move constructor rather than the copy constructor, but that is a nitpick. – Nemo Feb 13 '15 at 01:31
  • 2
    I don't really like "deprecated". It isn't deprecated in the standard, and there are actually good reasons to do the allocation separately, such as when memory is a constraint and you have `weak_ptr`s to the object that can significantly outlive the object. (Using `make_shared` in that scenario means that the memory cannot be freed until all `weak_ptr`s are gone.) It's also perfectly exception-safe; if the `shared_ptr` constructor throws an exception, the pointer is guaranteed deleted. – T.C. Feb 13 '15 at 04:04
  • @T.C. Yes, I thought so. I'd welcome a better term/expression, because I don't know one ! Also, I'll add some more on exception safety. – Quentin Feb 13 '15 at 09:55
  • I'd just say something like "usually `make_shared` is preferred" – T.C. Feb 13 '15 at 10:06
  • @T.C. thank you ! Also, if you're now worried about exception-safety, the way to go is `std::shared_ptr(std::make_unique(...))` :) – Quentin Feb 13 '15 at 10:09
  • @Quentin Yep. I wasn't thinking about the two `shared_ptr`s scenario when I wrote my first comment :) – T.C. Feb 13 '15 at 10:12
  • Thanks, it never would have occurred to me that calling `std::make_shared(DerivedType(...)` would end up calling the copy (or move) constructor of the base class rather than just creating an instance and using that. Hence it was my understanding that was wrong. – Luke Dickety Feb 13 '15 at 15:56
-3
std::make_shared<type>(DerivedType(...))

HEre replace make_shared<Type> with make_shared<DerivedType> and it all should work

upd: highlighted the change

Severin Pappadeux
  • 18,636
  • 3
  • 38
  • 64
  • 1
    Because although this will work, it's still using `make_shared` incorrectly. The point of `make_shared` is it constructs the object, so constructing an object to pass to `make_shared` is redundant and less efficient, and won't compile for non-copyable types. – Jonathan Wakely Feb 13 '15 at 10:11