【问题标题】:make_unique with brace initialization带大括号初始化的 make_unique
【发布时间】:2019-08-04 02:42:17
【问题描述】:

https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique 写道,std::make_unique 可以实现为

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

这不适用于没有构造函数的普通结构。这些可以被大括号初始化,但没有非默认构造函数。示例:

#include <memory>
struct point { int x, z; };
int main() { std::make_unique<point>(1, 2); }

Compiling this 会让编译器抱怨缺少 2 参数构造函数,这是正确的。

我想知道,是否有任何技术原因不使用大括号初始化来定义函数?如

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

works well enough 用于上述场景。是否还有其他合法的用例会被破坏?

看到总体趋势似乎更喜欢大括号进行初始化,我认为在该模板中制作大括号将是规范的选择,但标准不这样做的事实可能表明我错过了一些东西。

【问题讨论】:

  • 当你打电话给make_unique&lt;vector&lt;int&gt;&gt;(10,20)时考虑一下你想得到什么?具有 10 个项目的向量,所有 20 个值(当在 make_unique 中使用 () 时),或具有 2 个项目的向量 10,20(当使用 {} 时,向量具有采用 initializer_list 的构造函数)。
  • 如果有什么安慰的话,p0960 正在筹备中。未来的 C++ 版本可能会支持聚合,而无需更改工厂库函数工作方式的实现。
  • 尽管可以想象,今天在std::is_aggregate trait 的帮助下支持类似的东西是可能的。我猜人们觉得最好有一个语言解决方案,而不是一个库。
  • “看到总体趋势似乎更喜欢使用大括号进行初始化”我担心由于矢量等问题不再有这种趋势。
  • 来自this proposal,匹配make_shared。我还没有找到为什么make_shared 选择一种方式而不是另一种方式。 shared_ptr 虽然在 TR1 中,早在统一初始化语法存在之前 - 不确定当时是否存在 make_shared

标签: c++ initialization c++14 unique-ptr c++-standard-library


【解决方案1】:

在 C++20 中,这将编译:

std::make_unique<point>(1, 2);

由于新规则allowing initializing aggregates from a parenthesized list of values


在 C++17 中,您可以这样做:

std::unique_ptr<point>(new point{1, 2});

这不适用于make_shared。所以你也可以只创建一个工厂(作为练习左转):

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

在 C++14 中,您必须实现 apply 并为 braced_init 编写工厂函数,因为还没有 CTAD - 但这些都是可行的。


了解总体趋势似乎更喜欢使用大括号进行初始化

需要引用。这是一个充满争议的话题——但我绝对不同意这种说法。

【讨论】:

  • 括号初始化也有自己的守护进程。如果这个初始化器类进行非大括号直接初始化,它会更复杂。
  • 对于 C++20,这仍然无法编译:auto p = std::make_unique&lt;std::array&lt;int, 2&gt;&gt;(1, 2); C++ 初始化是一个谜。
【解决方案2】:

某些类在 2 种初始化样式中具有不同的行为。例如

std::vector<int> v1(1, 2); // 1 element with value 2
std::vector<int> v2{1, 2}; // 2 elements with value 1 & 2

可能没有足够的理由选择一个更喜欢另一个;我认为标准只是选择一个并明确说明决定。

作为解决方法,您可能希望实现自己的make_unique 版本。正如你所展示的,这不是一项艰苦的工作。

【讨论】:

  • 我相信 C++20 改变了这个答案。
  • 不,C++20 不会破坏答案。两个版本的 std::vector 构造函数仍然不同,make_shared 仍然会调用圆括号构造函数(委员会永远不会支持破坏大量代码的更改)。但是,C++20 将允许使用标准大括号进行聚合初始化。
【解决方案3】:

除了其他答案之外,Alisdair Meredith 在他关于 C++17 的presentation 中给出了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)...});
}

它使用 C+17 if constexpr,但没有它也可以轻松重写。

有了这个版本,你可以做到这两点

auto v = make_unique<std::vector<int>>(10, 20); // *v is a vector of 10 elements

auto p = make_unique<point>(10, 20); // *p is a point (10, 20)

【讨论】:

    猜你喜欢
    • 2014-07-31
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 2012-11-21
    • 1970-01-01
    • 2021-12-03
    • 2016-12-11
    • 1970-01-01
    相关资源
    最近更新 更多