【问题标题】:What are the differences between C-like, constructor, and uniform initialization?类 C、构造函数和统一初始化之间有什么区别?
【发布时间】:2014-09-17 04:27:44
【问题描述】:

据我所知,在 C++ 中初始化变量有三种方法。

int x = 0;    // C-like initialization
int x (0);    // Constructor initialization
int x {0};    // Uniform initialization

C++11 引入了统一初始化,以提供更统一的语法来初始化不同类型的变量,这需要C++03 中的不同语法。

类 C、构造函数和统一初始化之间有什么区别?我应该始终使用统一初始化吗?

【问题讨论】:

  • 差异是什么意思?对于像 int 这样的基本数据类型,它们是完全相同的。我会为 int 使用“C-like”,因为这是最直观的方式。
  • 我是 C++ 新手,但我正在思考幕后发生的事情。我正在阅读它,但我假设构造函数初始化调用对象中的构造函数方法。在那个构造函数中会发生什么?我也听说统一初始化方法在某些情况下会回退到构造函数初始化。

标签: c++ c++11 initialization c++03 variable-initialization


【解决方案1】:

首先,我建议您查看 Herb Sutter 的 following talk,他在其中就该主题提出了一些建议。大括号初始化讨论从around 23:00开始。

当您谈论原始数据类型时,所有 3 种都会产生相同的结果。我个人更喜欢坚持使用旧的 int x = 0 语法,但这取决于个人喜好。

对于类类型,大括号初始化和老式构造函数初始化不是完全可以互换的。例如:

vector<int> v (100); // Creates a 100-element vector
vector<int> v {100}; // Creates a 1-element vector, holding the value 100.

这是因为std::vector 有一个构造函数,该构造函数将std::initializer_list 明确定义为其唯一参数。请记住

auto var = {1, 2};

创建一个std::initializer_list,以var 作为其标识符。

关于初始化列表的事情是,它们提供了与之前可用的内容相比的可喜变化的一致性。例如,如果你要在 C++ 中初始化一个数组,你会使用:

int arr[] = {1, 2, 3, 4};

但是,如果您想用相同的元素初始化 vector&lt;int&gt;,您必须:

  1. 先初始化上面的arr再传arrarr + 4
  2. 单独或循环创建向量和 push_back() 元素。

使用 C++11,您可以使用

vector<int> v = {1, 2, 3, 4}; // Same syntax. Nice! Note that the = is optional

大括号初始化有用的另一个例子是它为 C++ 的most vexing parse 提供了一种解决方法。从谈话中,假设我们有两个类,originextents,可以传递它们的实例来构造另一个rectangle 类型的对象。以下声明:

rectangle w(origin(), extents());

不允许您使用originextents 临时对象创建rectangle 对象,因为该语句被解析为函数声明。啧啧。所以通常,你必须这样做:

origin  o;
extents e;
rectangle w(o, e);

通过大括号初始化,您可以动态创建它们,并且

rectangle w {origin(), extents()};

将按预期工作,即传递给以origin 对象作为其第一个参数和extents 对象作为第二个参数重载的构造函数。

规则是针对对象的,除非你有理由不这样做,否则使用大括号初始化。

【讨论】:

  • 我还是不明白。正如我在某处读到的,如果它看起来像一个函数声明,C++ 会将它解释为一个函数声明。 rectangle w(origin(), extents());rectangle w(o, e); 看起来都像函数声明,它们有什么区别?
  • @Cupidvogel 第二个例子不是函数声明,因为那里没有参数的类型。 oe 分别是 originextents 的实例。
  • 是的,不过第一个也差不多吧? o 实际上是origin(),所以两者都没有类型,对吧?
  • 不完全。 rectangle w(origin(), extents()) 中的 origin() 被解析为指向不带参数并返回 origin 对象的函数的指针。与extents() 相同。所以它们是类型。
  • "[...] 关于原始数据类型,所有 3 产生相同的结果。" - 这不是 100% 正确的,因为大括号初始化不允许缩小转换而其他人则有效(char c(999); 有效,而char c{999}; 不适用于 8 位 char)。您也没有提到 T x = ... 执行复制初始化(我同意,大多数编译器都会忽略)。
【解决方案2】:

c-like、构造函数和统一初始化有什么区别?

对于像int 这样的原始类型,没有实际区别;所以让我们考虑一个类类型T

第一个样式等价于

T x(T(0));

从初始化表达式创建一个临时对象,然后通过移动或复制来初始化x。在实践中,移动或复制将被省略,这样结果与第二种样式相同;唯一的区别是,如果没有可访问的副本或移动构造函数,第一个将失败。

第二个直接使用带有一个参数的构造函数初始化对象,如果没有合适的构造函数,则会出错。

第三个取决于可用的构造函数。

  • 如果有一个构造函数采用std::initializer_list,它会使用它;
  • 否则,如果有一个构造函数采用合适类型的单个参数,它会使用该参数;
  • 否则,如果它是具有一个成员的聚合(没有构造函数),则将该成员初始化为零;
  • 否则,这是一个错误。

我应该始终使用统一初始化吗?

没有。有时您需要函数式初始化来区分 initializer_list 构造函数和采用其他参数类型的构造函数。例如:

std::vector<int> v1(10, 42);  // 10 elements with value 42
std::vector<int> v2{10, 42};  // 2 elements with values 10 and 42

您也不应该将其称为“统一初始化”,因为它在任何有意义的意义上都不是“统一”。官方术语是“大括号初始化”。

【讨论】:

  • 如果构造函数 T(int) 被标记为显式,T x = 0; 也会失败(这对于某些标准容器来说很常见!)。
  • 您是否在此声明中犯了错误? “如果它是具有一个成员的聚合(没有构造函数),则该成员初始化为零;” ?没看懂
  • @PatrickParker 我认为他的意思是,例如,如果您有struct Thing { int value };,并且您没有手动为其编写构造函数,那么编写Thing t{0} 将构造一个t 类型Thing,其唯一成员 int value 将被初始化为 0。如果我错了,@MikeSeymour 纠正我。 (我不是 C++ 专家。)
猜你喜欢
  • 2017-02-07
  • 1970-01-01
  • 1970-01-01
  • 2012-04-24
  • 2010-10-18
  • 1970-01-01
  • 2013-02-10
  • 2011-09-02
相关资源
最近更新 更多