direct-list-init vs. copy-list-init vs. copy-init from a prvalue differences since C++17的例子

Examples of direct-list-init vs. copy-list-init vs. copy-init from a prvalue differences since C++17

对于这3种初始化形式(对于某些类型A

A x = {<...>};
A x{<...>};
auto x = A{<...>};

自 C++17 以来行为差异的示例是什么,除了差异 w.r.t。显式 ctors(显式 ctor 会破坏第一个)?

(因为 C++17 保证复制省略省略了副本; 复制构造函数隐式转换也不应该在这里适用,因为 auto...)

我大致了解 C++ 初始化形式的工作原理,对于 broadness/vagueness 的问题,我很抱歉,我只是想知道上述三种形式之间是否存在任何(显着)差异,或者它们可能是基本上可以互换使用(提到的区别除外)。

我认为这里的差异很重要,尤其是因为 implicit/explicit 差异,我相信您很快就会忽略它。

我将回答一个与您提出的问题略有不同的问题:为什么使用哪种表格?相对于:有什么区别?

A x = {<...>};

如果可以编译,最好使用这种形式。

如果 rhs 表达式具有可显式转换为 A 的类型,则无法编译。这是一件非常好的事情!这是该语言提供的安全功能。

类型作者通常(或至少应该)保留隐式 construction/conversion 以进行非常安全的转换。这种转换应该是无损的,并且不会改变 rhs 和 lhs 之间表达式的基本语义。例如:milliseconds x = 3s; rhs 为seconds,lhs 为milliseconds。并且构造在两个持续时间之间转换,并且根本不丢失任何信息。这是使用这种构造形式的好地方。

通过使用这种形式,程序员就像在说:给我 A 类型的 "safe set" 构造函数。如果我在 rhs 上的表达式类型有错误可能导致不安全的转换,我想在编译时找出它。

A是整数类型时,即使包含{}也有优势:

unsigned x = {i};

这遵循了只给我安全转换的说法。但它为您的背带增加了一条额外的腰带:只需给我不会变窄的转换。 {} 基本上是后来的 C++ 中的一个额外的权宜之计,以弥补几十年前在 C 中犯下的设计错误。


但有时您需要更大的锤子。有时显式转换是您想要和需要的。现在是时候:

A x{<...>};

例如:milliseconds x{3}; 这会将 int 转换为 millisecond。虽然转换是无损的,但 lhs 的类型与 rhs 的类型不同。 rhs 可以代表 3 任何东西。 3 个苹果。 3 国税局通知。 3年。这不是隐式进行的安全转换。 std::lib 知道这一点。如果您尝试过,它不会编译。然而,有时这正是您需要做的。为那种情况保留此表格。

如果不遵循此建议,在某些情况下可能会导致 运行 时错误。 this lightning talk.

中演示了其中两个

最后,这是一个很好的形式:

auto x = A{<...>}; 

当您希望 x 的类型为 A 时。当 A 不是一个容易拼写的类型,甚至没有出现在 rhs 上时,它特别好。

我最喜欢使用这种形式的例子是在 std::chrono::round 实现中:

template <class To, class Rep, class Period>
constexpr
To
round(const duration<Rep, Period>& d)
{
    auto t0 = floor<To>(d);
    auto t1 = t0 + To{1};
    if (t1 == To{0} && t0 < To{0})
        t1 = -t1;
    auto diff0 = d - t0;  // here
    auto diff1 = t1 - d;  // and here
    if (diff0 == diff1)
    {
        if (t0 - duration_cast<To>(t0/2)*2 == To{0})
            return t0;
        return t1;
    }
    if (diff0 < diff1)
        return t0;
    return t1;
}

标记为 "here and here" 的行(通常)导致 diff0diff1 的类型非常复杂:有几种不同的拼写方式。就是common_type_v<duration<Rep, Period>, To>。对于代码的 reader 来说,究竟是什么类型并不重要。唯一需要知道的重要事情是,此类型将表示两个操作数的确切差异。


综上所述,这不是"best form"。它们都是您工具箱中的好工具。诀窍是知道何时使用哪个。如果你擅长它,你将比绝大多数 C++ 程序员更熟练。