jma
jma

Reputation: 3789

std::make_unique and unique_ptr

If I read correctly, cppreference says that the following two constructs should be equivalent for non-array types in C++14 and later:

make_unique<T>(T());
unique_ptr<T>(new T());

I've clearly not understood a subtlety. Here's an example (the full file with headers and scaffolding is here. In the example, I expect cmp3 and cmp4 to have the same effect, but cmp4 doesn't compile.

template <typename T>
class DbCell {
   public:
    DbCell() {}
    DbCell(const T& v) : value_(v.value_) {}
    DbCell(T&& v) : value_(move(v.value_)) {}
    ~DbCell() {}
   private:
    T value_;
};

struct ChapterStats {
    ChapterStats() {}
};
class ChapterMap {
   public:
    ChapterMap() {}
    ChapterMap(const ChapterMap&) = delete;
    ChapterMap(ChapterMap&& cm) : the_map_(move(cm.the_map_)) {}

   private:
    map<string, unique_ptr<ChapterStats>> the_map_;
};
void foo() {
    DbCell<ChapterMap> cm;

    unique_ptr<DbCell<int>> cmp1 =
        make_unique<DbCell<int>>(DbCell<int>());

    unique_ptr<ChapterMap> cmp2 = make_unique<ChapterMap>(ChapterMap());

    unique_ptr<DbCell<ChapterMap>> cmp3 =
        unique_ptr<DbCell<ChapterMap>>(new DbCell<ChapterMap>());

    // This next fails, even though I think it should be equivalent to cmp3.
    cout << "> cmp4:" << endl;
    unique_ptr<DbCell<ChapterMap>> cmp4 =
        make_unique<DbCell<ChapterMap>>(new DbCell<ChapterMap>());
}

Without cmp4 the output is this (adding appropriate print statements):

Constructed ChapterMap.
Constructed DbCell.
> cmp1:
Constructed DbCell.
> cmp2:
Constructed ChapterMap.
Moved ChapterMap.
> cmp3:
Constructed ChapterMap.
Constructed DbCell.

With cmp4, the compiler error is this:

In file included from rgr.cc:6:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/memory:81:
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/unique_ptr.h:765:34: error: no matching constructor for initialization of 'DbCell<ChapterMap>'
    { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
                                 ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
rgr.cc:61:9: note: in instantiation of function template specialization 'std::make_unique<DbCell<ChapterMap>, DbCell<ChapterMap> *>' requested here
        make_unique<DbCell<ChapterMap>>(new DbCell<ChapterMap>());
        ^
rgr.cc:18:7: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'DbCell<ChapterMap> *' to 'const DbCell<ChapterMap>' for 1st argument; dereference the argument with *
class DbCell {
      ^
rgr.cc:21:5: note: candidate constructor not viable: no known conversion from 'DbCell<ChapterMap> *' to 'const ChapterMap' for 1st argument
    DbCell(const T& v) : value_(v.value_) {
    ^
rgr.cc:24:5: note: candidate constructor not viable: no known conversion from 'DbCell<ChapterMap> *' to 'ChapterMap' for 1st argument
    DbCell(T&& v) : value_(move(v.value_)) { cout << "Moved DbCell." << endl; }
    ^
rgr.cc:20:5: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
    DbCell() { cout << "Constructed DbCell." << endl; }
    ^
1 error generated.

One of the pieces that confuses me is that the compiler is complaining about the implicitly deleted DbCell<ChapterMap> copy constructor, but I've defined one (that I don't want, but just for the example).

Upvotes: 1

Views: 2678

Answers (2)

zync
zync

Reputation: 461

For cmp4 you should use

unique_ptr<DbCell<ChapterMap>> cmp4 =
        make_unique<DbCell<ChapterMap>>(/*empty*/);

std::make_unique calls a constructor of its type, which in your case would be a constructor taking a pointer to DbCell, which you don't have. This is highlighted by the compiler error message:

rgr.cc:18:7: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'DbCell<ChapterMap> *' to 'const DbCell<ChapterMap>' for 1st argument; dereference the argument with *

Upvotes: 4

ALX23z
ALX23z

Reputation: 4713

These two are not identical:

   make_unique<T>(T());
   unique_ptr<T>(new T());

These two are identical:

   make_unique<T>();
   unique_ptr<T>(new T());

In the first one you had an extra move/copy ctor involved in the call of make_unique<T>.

Upvotes: 9

Related Questions