8
votes

When I need to have a data member which is a type of std::unique_ptr, then I have usually used std::unique::reset to initialize this unique_ptr with a new object.

The following is a simplified example:

class A {
 public:
  void SetValue(int x) {
    data_.reset(new B(x));
  }

 private:
  std::unique_ptr<B> data_;
};

In the code review, one reviewer mentioned that it is a bad habit and he asked me not to use reset if possible. Instead, he suggested to use the following methods:

std::make_unique

or a template function like the following:

template <typename T>
struct MakeUniqueResult {
  using scalar = std::unique_ptr<T>;
};
template <typename T, typename... Args>
typename internal::MakeUniqueResult<T>::scalar
MakeUnique(Args&&... args) {  
  return std::unique_ptr<T>(
      new T(std::forward<Args>(args)...));  
}

Are there some special reasons to avoid using std::unique_ptr::reset in the above case?

1
The next edit of the code could introduce some operation between the call to new and the call to reset(). At this point, what was merely a 'bad smell' becomes a problem.Richard Hodges
You're not initializing when you call reset. You're changing the state of an already existing object. It makes little sense not to initialize an object that is useless uninitialized.juanchopanza
@chris this is the purpose of the standard idiom, make_unique<>Richard Hodges
Dear original questioner: you should change the tag as a C++14 question since C++11 does not have make_uniqueTheCppZoo
Voting to re-open. There are factual objective answers available to the question "Are there some special reasons to avoid using std::unique_ptr::reset in the above case?"Adrian McCarthy

1 Answers

9
votes

In your example, the effect is the same whether you use std::make_unique or std::unique_ptr::reset with new.

But in more interesting code, using std::make_unique solves some exception safety issues, and that's why your code reviewer is suggesting to make it a habit.

Consider what happens if you try to create two unique pointers at once:

Frobnicate(std::unique_ptr<Foo>(new Foo), std::unique_ptr<Bar>(new Bar(3)));

The compiler has to do a bunch of work to create those two parameters, and it has a lot of flexibility in the order it can do that work. If one of the constructors throws an exception, the memory allocated for the other one may or may not be cleaned up.

But if you use std::make_unique:

Frobnicate(std::make_unique<Foo>(), std::make_unique<Bar>(3));

The temporary unique_ptrs will immediately own their respective objects and thus be able to clean them up if creating the other one throws an exception.

For exception safety, and the general guideline of avoiding using new and delete directly, it's a good habit to use std::make_unique every time.