1

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).

asked Aug 27, 2021 at 8:37
5
  • 1
    make_unique invokes the constructor of T on your behalf; arguments to make_unique are passed to the constructor of T. So cmp4 requires a copy or move constructor. Commented Aug 27, 2021 at 8:44
  • 2
    You did not create a copy/move constructor for DbCell<T>, because the ctors you defined take a T const & and not a DbCell<T> const &. Commented Aug 27, 2021 at 8:44
  • 3
    make_unique<T>(T()); is equivalent to unique_ptr<T>(new T(T())); Commented Aug 27, 2021 at 8:52
  • @AlanBirtles You are correct. I'm looking for the error in my MRE (that is, why I wrote that specifically). Commented Aug 27, 2021 at 8:54
  • Thanks @Thomas, the omission of the template argument on the DbCell constructor was the heart of the issue. (This said, I much appreciated the realisation that I have sometimes accidentally called make_unique incorrectly and that the compiler hasn't always warned me.) Commented Aug 27, 2021 at 10:31

2 Answers 2

9

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>.

answered Aug 27, 2021 at 8:42
Sign up to request clarification or add additional context in comments.

Comments

4

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 *
answered Aug 27, 2021 at 9:03

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.