2

In the following code, why is std::make_unique necessary to initialize Foo::up_?
Specifically, why doesn't initializing Foo::up_ with new Bar(...) -- as in the commented-out code -- work?

#include <iostream>
#include <memory>

enum E {
  Y = 0,
  Z = 1,
  NUM_E
};

class Bar {
public: // Functions
  Bar( const int& i, const int& j ) : i_(i), j_(j) { }

public: // Objects
  int i_;
  int j_;
};

class Foo {
public: // Functions
  Foo();

  void reset( const int& Yi, const int& Yj,
              const int& Zi, const int& Zj );

public: // Objects
  std::unique_ptr<Bar> up_[NUM_E];
};

Foo::Foo()
  : up_{ std::make_unique<Bar>( 42, 43 ),
         std::make_unique<Bar>( 44, 45 ) }
//  : up_{ new Bar( 42, 43 ),
//         new Bar( 44, 45 ) } // err: could not convert from Bar* to unique_ptr
{ }

void Foo::reset( const int& Yi, const int& Yj,
                 const int& Zi, const int& Zj ) {
  up_[Y].reset( new Bar( Yi, Yj ) );
  up_[Z].reset( new Bar( Zi, Zj ) );
}

int main( int argc, char* argv[] ) {
  (void)argc;
  (void)argv;
  Foo foo;

  std::cout << foo.up_[Y]->i_ << std::endl;
  std::cout << foo.up_[Y]->j_ << std::endl;
  std::cout << foo.up_[Z]->i_ << std::endl;
  std::cout << foo.up_[Z]->j_ << std::endl;

  foo.reset( 1, 2, 3, 4 );

  std::cout << foo.up_[Y]->i_ << std::endl;
  std::cout << foo.up_[Y]->j_ << std::endl;
  std::cout << foo.up_[Z]->i_ << std::endl;
  std::cout << foo.up_[Z]->j_ << std::endl;

  return 0;
}
$ g++ --version && g++ --std=c++14 ./main.cpp && ./a.out
g++ (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

42
43
44
45
1
2
3
4

How does one work around this for C++11, where std::make_unique doesn't appear to be available?

$ g++ --version && g++ --std=c++11 ./main.cpp && ./a.out
g++ (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

./main.cpp: In constructor ‘Foo::Foo()’:
./main.cpp:31:10: error: ‘make_unique’ is not a member of ‘std’
   : up_{ std::make_unique<Bar>( 42, 43 ),
          ^~~
./main.cpp:31:30: error: expected primary-expression before ‘>’ token
   : up_{ std::make_unique<Bar>( 42, 43 ),
                              ^
./main.cpp:32:10: error: ‘make_unique’ is not a member of ‘std’
          std::make_unique<Bar>( 44, 45 ) }
          ^~~
./main.cpp:32:30: error: expected primary-expression before ‘>’ token
          std::make_unique<Bar>( 44, 45 ) }
                              ^
0

3 Answers 3

7

1. The constructor unique_ptr( pointer p ) is marked explicit, which prevents instances of unique_ptr from being constructed implicitly in array list initialization.

2. std::make_unique is available since C++14.

In C++11 a possible solution to both points is to construct an instance explicitly e.g. std::unique_ptr<Bar>(new Bar( 42, 43 )).

Sign up to request clarification or add additional context in comments.

1 Comment

@StoneThrow there is none in this context. You can have one, pre-C++17, under some narrow circumstances, more details here: stackoverflow.com/questions/55509661/…
4

The reason why you can't construct your array of unique_ptr's from array initializer of simple pointers is because unique_ptr pointer constructor is explicit.

To fix this issue, you could either call explicit constructors of unique_ptrs, but that's quite boring and prone to issues which prompted adding make_unique in the first place. Instead, you can simple have your own compat::make_unique template function, which is literally 1 line:

template<class T, class... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Comments

1

If you do not have make_unique, you can just use the constructor of unique_ptr like this

Foo::Foo()
  : up_{ std::unique_ptr<Bar>(new Bar( 42, 43 )),
         std::unique_ptr<Bar>(new Bar( 44, 45 )) }
{ }

https://godbolt.org/z/G9f5v1

5 Comments

This question fails to answer most interesting aspect: "why is std::make_unique necessary to initialize Foo::up_?"
@MooingDuck not in this context, though.
My understanding was that In C++11, the compiler is allowed to execute the new Bar(32, 43), and then it can attempt to execute new Bar(44, 45), which can throw a std::bad_alloc, which can unwind the stack. But the first Bar(32, 43) will not be deleted because it was not yet assigned to a unique_ptr. This ordering issue was updated in C++17. Can you remind me what I'm overlooking? 🤔
@MooingDuck yes, I understand this is your understanding, and your understanding is incorrect. Unspecified order of evaluation only applies to function calls. In the example above, there is no function call, there is a braced-list-initialization, and for braced list initialization, "every value computation and side effect of a given initializer clause is sequenced before every value computation and side effect associated with any initializer clause that follows it in the brace-enclosed comma-separated list of initalizers".
@SergeyA: Aha! Thanks for correcting me! I was unaware of this clause!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.