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" 的行(通常)导致 diff0
和 diff1
的类型非常复杂:有几种不同的拼写方式。就是common_type_v<duration<Rep, Period>, To>
。对于代码的 reader 来说,究竟是什么类型并不重要。唯一需要知道的重要事情是,此类型将表示两个操作数的确切差异。
综上所述,这不是"best form"。它们都是您工具箱中的好工具。诀窍是知道何时使用哪个。如果你擅长它,你将比绝大多数 C++ 程序员更熟练。
对于这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" 的行(通常)导致 diff0
和 diff1
的类型非常复杂:有几种不同的拼写方式。就是common_type_v<duration<Rep, Period>, To>
。对于代码的 reader 来说,究竟是什么类型并不重要。唯一需要知道的重要事情是,此类型将表示两个操作数的确切差异。
综上所述,这不是"best form"。它们都是您工具箱中的好工具。诀窍是知道何时使用哪个。如果你擅长它,你将比绝大多数 C++ 程序员更熟练。