【问题标题】:Differences between direct-list-initialization and copy-list-initialization直接列表初始化和复制列表初始化之间的区别
【发布时间】:2018-10-29 12:47:46
【问题描述】:

想知道下面两种std::vector在C++11及以后的初始化有什么区别。

std::vector<int> v1 {1, 2, 3, 4, 5};
std::vector<int> v2 = {1, 2, 3, 4, 5};

这是一个运行良好的完整代码示例。

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v1 {1, 2, 3, 4, 5};
    std::vector<int> v2 = {1, 2, 3, 4, 5};

    std::cout << v1.size() << '\n';
    std::cout << v2.size() << '\n';
}

我看到两个初始化都导致相同的结果。

http://en.cppreference.com/w/cpp/container/vector 的示例使用了第二种,这让我开始思考这种初始化是否有任何优势。

一般来说,我想知道一种初始化是否比另一种具有特定的技术优势,或者一种初始化是否被认为是最佳实践而另一种则不是,如果是,为什么。

我特别关心的是copy-list-initialization是否会因为临时对象和复制而产生额外的开销?

【问题讨论】:

  • 第一个叫做direct-list-initialization,第二个叫做copy-list-initializaiton。对于vector hwoever,这两件事的行为完全相同
  • @M.M 是否存在 copy-list-initialization 的行为可能与 direct-list-initialization 不同的情况? copy-list-initialization 中是否有任何复制会导致它变慢?
  • @LoneLearner 这取决于所涉及的标准版本(C++14 中的一些相关更改在这里发挥作用)
  • @Frank 如果更改可以简明扼要地解释,我们能否有一个答案来解释 direct-list-initializationcopy-list-initialization 在 C++11、C++14 和 C++17 中?如果答案过于宽泛和冗长,那么我将不得不将其分成三个问题,我不确定这些问题是否合适。
  • 可能值得注意的是,different compilers 编译为与上述示例相同的代码。

标签: c++ c++11 initialization c++14 c++17


【解决方案1】:

列表初始化被非正式地称为“统一初始化”,因为它的含义和行为旨在与您如何调用它相同。

当然,C++ 就是 C++,“预期”的事情并不总是会发生。

直接列表初始化和复制列表初始化的行为基本上存在三个主要区别。第一个是你最常遇到的:如果列表初始化会调用标记为explicit的构造函数,那么如果列表初始化形式为复制列表初始化,则会出现编译错误。

这种行为差异在[over.match.list]/1中定义:

在复制列表初始化中,如果选择了explicit 构造函数,则初始化格式错误。

这是重载决议的一个功能。

第二个主要区别(C++17 的新功能)是,给定一个具有固定基础大小的枚举类型,您可以使用基础类型的值对其执行直接列表初始化。但是您不能从这样的值执行复制列表初始化。所以enumeration e{value}; 有效,但enumeration e = {value}; 无效。

第三个主要区别(也是 C++17 的新区别)与 auto 推导中的括号初始化列表的行为有关。通常,auto 的行为与模板参数推导不太明显。但与模板参数推导不同,auto 可以从花括号初始化列表中初始化。

如果您使用列表中的单个表达式使用直接列表初始化来初始化 auto 变量,编译器将推断该变量为表达式的类型:

auto x{50.0f}; //x is a `float`.

听起来很合理。但如果对复制列表初始化执行完全相同的操作,它总是会被推导出为initializer_list&lt;T&gt;,其中T 是初始化器的类型:

auto x = {50.0f}; //x is an `initializer_list<float>`

非常非常统一。 ;)

谢天谢地,如果你在花括号初始化列表中使用多个初始化器,auto-deduced 变量的直接列表初始化总是会产生编译错误,而复制列表初始化只会给出更长的 @ 987654336@。所以自动推导的直接列表初始化永远不会给出initializer_list,而自动推导的复制列表初始化总是会。

有一些细微的差异很少会影响初始化的预期行为。在这些情况下,来自单个值的列表初始化将根据列表初始化形式使用复制或直接(非列表)初始化。这些案例是:

  1. 从与正在初始化的聚合相同类型的单个值初始化聚合。这会绕过聚合初始化。

  2. 从单个值初始化非类、非枚举类型。

  3. 初始化引用。

这些不仅不会特别频繁地发生,而且它们基本上不会真正改变代码的含义。非类类型没有显式构造函数,因此复制和直接初始化之间的区别主要是学术性的。参考也是如此。聚合情况实际上只是从给定值执行复制/移动。

【讨论】:

  • 这是一个非常好的答案。为了完整起见,您是否愿意添加评论中所说的内容,std::vector v {0, 1};direct-list-initializationstd::vector v = {0, 1};copy-list-initialization我>?
  • @YSC:OP 知道哪个是哪个。问题是它们有什么不同。
  • 这不是给 OP 的,而是给未来的读者的:如果有人不知道哪个是这个问答,我担心他们不会从你的好答案中得到最大的好处,因为他们会错过关键信息。
  • 是否也禁止在 cmets 中讨论关于良好编码风格的意见?
  • 您错过了eel.is/c++draft/dcl.init.list#3.8,以及一些“(通过复制初始化复制列表初始化,或通过直接初始化直接初始化列表)”案例。
【解决方案2】:

总的来说,我非常喜欢复制列表初始化(尤其是在std::vector 的情况下),因为当只有一两个元素时,直接列表初始化与std::vector 的实际构造函数乍一看非常相似.

std::vector<int> x(2);
// is very different from
std::vector<int> x{2};

明确说明我为向量分配了一个初始值,而不是用这些值配置它,这样就不太容易误读。

【讨论】:

  • 我不确定这与问题有什么关系
  • @Barry “或者如果一个初始化被认为是最佳实践,而另一个则不是,如果是,为什么。”代码可读性/可审查性问题绝对符合问题的这一部分。
  • 其中“一个”和“另一个”是直接列表和复制列表初始化。
  • @Barry 我刚刚意识到问题的标题已经改变,因为我最初写了这个答案,所以现在的措辞肯定看起来不合适。我已经对其进行了一些清理以更好地匹配新问题。
猜你喜欢
  • 2012-11-07
  • 1970-01-01
  • 2010-11-06
  • 1970-01-01
  • 2014-03-16
  • 1970-01-01
相关资源
最近更新 更多