问题
https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique writes that std::make_unique
can be implemented as
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
This does not work for plain structs with no constructors. Those can be brace-initialized but don't have a non-default constructor. Example:
#include <memory>
struct point { int x, z; };
int main() { std::make_unique<point>(1, 2); }
Compiling this will have the compiler complain about lack of a 2-argument constructor, and rightly so.
I wonder, is there any technical reason not to define the function in terms of brace initialization instead? As in
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
}
That works well enough for the scenario above. Are there any other legitimate use cases this would break?
Seeing how the general trend appears to prefer braces for initialization, I would assume making braces in that template would be the canonical choice, but the fact that the standard doesn't do it might be an indication of me missing something.
回答1:
Some classes have different behavior with the 2 initialization styles. e.g.
std::vector<int> v1(1, 2); // 1 element with value 2
std::vector<int> v2{1, 2}; // 2 elements with value 1 & 2
There might not be enough reason to choose one prefer to another; I think the standard just choose one and state the decision explicitly.
As the workaround, you might want to implement your own make_unique
version. As you have showed, it's not a hard work.
回答2:
In C++20, this will compile:
std::make_unique<point>(1, 2);
due to the new rule allowing initializing aggregates from a parenthesized list of values.
In C++17, you can just do:
std::unique_ptr<point>(new point{1, 2});
That won't work with make_shared
though. So you can also just create a factory (forwarding left as an exercise):
template <typename... Args>
struct braced_init {
braced_init(Args... args) : args(args...) { }
std::tuple<Args...> args;
template <typename T>
operator T() const {
return std::apply([](Args... args){
return T{args...};
}, args);
}
};
std::make_unique<point>(braced_init(1, 2));
In C++14, you'll have to implement apply
and write a factory function for braced_init
because there's no CTAD yet - but these are doable.
Seeing how the general trend appears to prefer braces for initialization
Citation needed. It's a charged topic - but I definitely disagree with the claim.
回答3:
In addition to other answers, in his presentation on C++17, Alisdair Meredith gives the following implementation of make_unique
:
template<typename T, typename... Args>
auto make_unique(Args&&... args) -> std::unique_ptr<T> {
if constexpr (std::is_constructible<T, Args...>::value)
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
else
return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
}
It uses C+17 if constexpr
, but can easily be rewritten without it.
With this version you can do both
auto v = make_unique<std::vector<int>>(10, 20); // *v is a vector of 10 elements
and
auto p = make_unique<point>(10, 20); // *p is a point (10, 20)
来源:https://stackoverflow.com/questions/55141594/make-unique-with-brace-initialization