2

I have recently attempted to learn how to use std::shared_ptr. When modifying my existing code I've found myself confused when allocating with member variables (outside of an initialisation list).

My old code:

Class A {

Base* member_var;

A() {
    this->member_var = new Derived();
}

};

My new code:

Class A {

std::shared_ptr<Base> member_var;

A() {
    //Note the shared_ptr is given type Derived, matching the object.
    this->member_var = std::shared_ptr<Derived> (new Derived());
}

};

Is this correct? Or is this perhaps more correct?:

Class A {

std::shared_ptr<Base> member_var;

A() {
    //Note the shared_ptr is of type Base, matching the member type.
    this->member_var = std::shared_ptr<Base> (new Derived());
}

};

What is the difference between these two statements.

Worryingly, I can't seem to find any code examples of what I'm trying to do here. Is my approach to using std::shared_ptr wrong?


EDIT: Thanks to everyone for their help. I think I caused some confusion. For the sake of future readability, I'll expand on why I've chosen to take this approach. I chose to illustrate my question with a simple code example. In my actual problem, I don't use a Class A, I actually use a struct. The sole purpose of this struct, is to help me neatly hold on to a number of instances of various different objects. I frequently end up passing (by reference) each object individually, not the struct itself, as argument to functions. Furthermore, when I do give the entire struct as argument, I tend to pass-by-value this struct. Hence my interest in making these shared_ptr, not unique_ptr. I've been debating changing everything and encapsulating all these in a Class, like Class A in my example, and passing said instance of class pass-by-reference the object instance. In this instance, I agree with everyone who has commented, and unique_ptr seems more appropriate.

Of course there's no fundamental difference between a struct and a class. I just have a tendency to pass instances of structs by value (if all they contain are pointers), and instances of classes by reference. Perhaps that's a personal quirk of mine?

Regarding the use of polymorphism in the first place, there are two possible derived classes here, one of which is chosen at runtime. I wish to handle the base class, which is for all intents and purposes, an abstract class.

I'm still surprised this is not a more common situation. Perhaps the above paragraphs have highlighted further bad practice. In which case I would be grateful to hear about it.

rod
  • 3,403
  • 4
  • 19
  • 25
  • 1
    Simpler: `member_var.reset(new Derived);` – Kerrek SB Jul 05 '13 at 16:18
  • 1
    `shared_ptr` is a very special animal that's only useful for very specific situations. If anything, I would doubt your motivation for having a shared pointer in the first place. It looks like a `unique_ptr` might be more appropriate. – Kerrek SB Jul 05 '13 at 16:20
  • It's hard to know what the real question is here. The capabilities shown in this code can be implemented much more simply by just having a member of type `Derived`. So there's something in the question that hasn't been said. – Pete Becker Jul 05 '13 at 16:40

1 Answers1

5

I'd write it this way:

A() : member_var(std::make_shared<Derived>()) { }

In Modern C++ you should avoid using new whenever possible. If you cannot initialize your member_var in the initialization list, then do:

A()
{
    // ...
    member_var = std::make_shared<Derived>();
}

As mentioned by KerrekSB in the comments, I would also suggest considering whether you do indeed need shared ownership, or wheter a unique_ptr wouldn't be sufficient.

If that is the case, you should use a unique_ptr instead: in general, it is a good thing to express minimal requirements - this buys you performance and a better self-documenting code at the same time.

Unfortunately, in C++11 you sill have to resort to new to create a unique_ptr that points to a newly created object:

A() : member_var(new Derived()) { }

Or, if you cannot use initialization lists:

member_var.reset(new Derived());

On the other hand in C++14, which offers std::make_unique<>, I would rather write:

A() : member_var(std::make_unique<Derived>()) { }

Or, if you cannot use initialization lists:

member_var = std::make_unique<Derived>();
Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • I can see the argument for a unique_ptr. But say a situation where I'm using a struct rather than a class? In my specific situation, I also can't allocate this in the initialisation list, as it depends on some logic (hence the polymorphism). Perhaps reset() is the route to go? – rod Jul 05 '13 at 16:29
  • @rod: Not sure I understand – Andy Prowl Jul 05 '13 at 16:30
  • Sorry, accidentally hit enter. – rod Jul 05 '13 at 16:31
  • @rod: Sorry, I missed that part. I expanded the answer to cover it. If you want to use `unique_ptr`, then yes, `reset()` is the way to go (or just create your own `make_unique()`) – Andy Prowl Jul 05 '13 at 16:33
  • Except that if you do the initialization in the initialization list, what's the reason for using `std::make_shared` anyway? – James Kanze Jul 05 '13 at 16:36
  • @JamesKanze: Mostly consistency, to avoid using `new`. Also, if you have other smart pointers to initialize, using `new` is not exception-safe – Andy Prowl Jul 05 '13 at 16:38
  • @AndyProwl It is in initializer lists. – James Kanze Jul 05 '13 at 16:54
  • @JamesKanze: Oh wait, you mean this is safe in initializer lists? `A() : p1(new X()), p2(new X()) { }` where `p1` and `p2` are `shared_ptr`? I really don't think so – Andy Prowl Jul 05 '13 at 16:56
  • @JamesKanze: Thinking about it, you may be right about safety (although I could not find a clear statement in the Standard saying that the evaluation of the arguments of the constructors is sequenced). So for the moment only the consistency motivation remains :) – Andy Prowl Jul 05 '13 at 17:20
  • @AndyProwl In §12.6.2/7: "The initialization performed by each mem-initializer constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization." and §1.9.14: "Every value computation and side effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated." – James Kanze Jul 05 '13 at 17:34
  • @AndyProwl And I'd be pretty suspicious of a context where there was more than one `std::shared_ptr`. They're pretty special, and should be fairly rare in well written code. – James Kanze Jul 05 '13 at 17:35
  • @JamesKanze: there is a value for consistency, and while initialization of a smart pointer with `new` in the initialization list won't cause any issue, if you have a member of a type whose constructor takes two smart pointers (or a smart pointer and something else) you would have to fall back to `make_xxx`, and given that there is no additional cost using consistently `make_xxx` seems the simple way to go. – David Rodríguez - dribeas Jul 05 '13 at 17:41
  • 1
    @rod: A `struct` and a `class` are the same thing in C++, with the only difference of what the default access specifier is. More so, in your case it won't be a C-compatible POD class, since the `std::unique_ptr`/`std::shared_ptr` requires the generation of a destructor for the type. – David Rodríguez - dribeas Jul 05 '13 at 17:42