15

Boost's make_shared() function promises to be exception-safe while attempting to create a shared_ptr.

Why is there no make_scoped() equivalent? Is there a common best practice?

Here's a code example from the boost::scoped_ptr documentation that seems unsafe to me:

    boost::scoped_ptr<Shoe> x(new Shoe);

This line of code will do these three things in order:

  • Allocate heap memory for Shoe
  • Call the constructor for Shoe
  • Call the constructor for boost::scoped_ptr<Shoe>

If the constructor for Shoe throws an exception, memory will be leaked. (see R. Martinho Fernandes answer) The scoped_ptr won't handle the deallocation because it hasn't been constructed yet.

Is this an oversight? Or is there a solution that I've failed to notice?

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
  • This example is safe, but one that is not: `f(boost::scoped_ptr(new Shoe), g());`. The coding practice to solve the issue: Always name smart pointers as variables or members, don't construct them as temporary subexpressions. – aschepler Jan 19 '13 at 19:44

3 Answers3

15

scoped_ptr predates move semantics and is noncopyable by design. Thus, make_scoped would be impossible to implement because in order to return an object from a function, its type must either be movable or copyable.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • To be clear: since C++11 it is possible to return non-copyable, non-movable object from a function: https://wandbox.org/permlink/GLIgM5nA9ROJahvk But like you pointed, scoped_ptr predated `c++11` ant that is the clue here. – Mariusz Jaskółka Dec 31 '18 at 11:13
14

If the constructor fails, no memory is leaked. That's part of the semantics of new, no smart pointers involved:

struct Foo { Foo() { throw 23; } };
new Foo(); // no memory leaked

The added exception safety provided by make_shared comes from when you're initializing two shared_ptrs in an expression and the two initializations are not sequenced, as is the case in function call arguments:

struct Bar {
    Bar(bool fail) {
        if(fail) throw 17;
    }
}
f(shared_ptr<Bar>(new Bar(true)), shared_ptr<Bar>(new Bar(false)));

Since there is no sequencing between the evaluations of new Bar(true), shared_ptr<Bar>(new Bar(true)), new Bar(false) and shared_ptr<Bar>(new Bar(false)), the following could happen:

  1. new Bar(false) is evaluated and succeeds: memory is allocated;
  2. new Bar(true) is evaluated and fails: it doesn't leak memory resulting from this evaluation;

No shared_ptr was constructed at this time, and so the memory allocated in #1 is now leaked.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • Does this sound like I understand you? `new` promises to catch exceptions thrown from the constructor, free the memory, then re-throw? – Drew Dormann Jul 05 '12 at 03:07
  • 3
    @Drew: Yes, something to that effect. If you're interested, this is described in the standard on §5.3.4 paragrah 18. – R. Martinho Fernandes Jul 05 '12 at 03:09
  • 3
    For completeness another purpose of `make_shared` (in most implementations) is that it allocates memory for the reference count at the same as time as the object created (allocates one block big enough for the object and the count) so that you don't incur the performance penalty if two heap allocations. The `scoped_ptr` (or `unique_ptr` in c++11 don't need a reference count and therefore don't gain anything from it in that department. – John5342 Jan 19 '13 at 20:15
1

If Shoe throws then Shoe isn't constructed so there is nothing scoped_ptr can really do. No? The scoped_ptr x is on the stack and will get cleaned up on scope exit.

emsr
  • 15,539
  • 6
  • 49
  • 62