C-like,构造函数和统一初始化之间有什么区别?

day*_*oli 52 c++ initialization variable-initialization c++11 c++03

TTBOMK,有三种方法可以在C++中初始化变量.

int x = 0;    // C-like initialization
int x (0);    // Constructor initialization
int x {0};    // Uniform initialization
Run Code Online (Sandbox Code Playgroud)

C++ 11引入了统一初始化,以便为初始化不同类型的变量提供更加统一的语法,这需要在C++ 03中使用不同的语法.

C-like,构造函数和统一初始化之间有什么区别?我应该总是使用统一初始化吗?

nas*_*-sh 60

首先,我建议看看Herb Sutter 的以下讲话,其中他提出了一些有关该主题的建议.大括号初始化讨论从大约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.
Run Code Online (Sandbox Code Playgroud)

这是因为std::vector有一个显式定义std::initializer_list为唯一参数的构造函数.请记住

auto var = {1, 2};
Run Code Online (Sandbox Code Playgroud)

std::initializer_list使用var标识符创建一个.

关于初始化程序列表的事情是它们提供的一致性是对事先可用的可接受的改变.例如,如果要在C++中初始化数组,则可以使用:

int arr[] = {1, 2, 3, 4};
Run Code Online (Sandbox Code Playgroud)

但是,如果你想vector<int>用相同的元素初始化a ,你要么必须:

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

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

vector<int> v = {1, 2, 3, 4}; // Same syntax. Nice! Note that the = is optional
Run Code Online (Sandbox Code Playgroud)

大括号初始化有用的另一个例子是它为C++ 最令人烦恼的解析提供了一种解决方法.从谈话中,假设我们有两个类,origin并且extents可以传递其实例来构造另一个类型的对象rectangle.以下声明:

rectangle w(origin(), extents());
Run Code Online (Sandbox Code Playgroud)

不允许您rectangle使用originextents临时创建对象,因为该语句被解析为函数声明.Tsk tsk.通常情况下,您必须这样做:

origin  o;
extents e;
rectangle w(o, e);
Run Code Online (Sandbox Code Playgroud)

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

rectangle w {origin(), extents()};
Run Code Online (Sandbox Code Playgroud)

将按预期工作,即传递给构造函数,该构造函数重载了一个origin对象作为它的第一个参数,一个extents对象作为第二个参数.

该规则适用于对象,除非您有理由不使用支撑初始化.

  • +1是最令人烦恼的解析评论 (8认同)
  • 不完全的.`rect()中的`origin()`(origin(),extents())`被解析为一个不带参数的函数的指针,并返回一个`origin`对象.与`extents()`相同.所以他们是类型. (4认同)
  • 我还是不明白.当我在某处读到时,C++会将其解释为函数声明,如果它看起来像一个.`矩形w(origin(),extents());`和`矩形w(o,e);`看起来像函数声明,它们之间有什么区别? (2认同)
  • @Cupidvogel第二个示例不是函数声明,因为那里没有参数的类型。“ o”和“ e”分别是“ origin”和“ extents”的实例。 (2认同)
  • *"[...]关于原始数据类型,所有3都产生相同的结果."* - 这不是100%正确,因为大括号初始化不允许缩小转换而其他方式(`c c(999);`在`char c {999}时有效;`不适用于8位`char`).你也没有提到`T x = ...`执行复制初始化(我同意,大多数编译器会忽略). (2认同)

Mik*_*our 15

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

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

第一种风格相当于

T x(T(0));
Run Code Online (Sandbox Code Playgroud)

从初始化表达式创建临时对象,然后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
Run Code Online (Sandbox Code Playgroud)

你也不应该称它为"统一初始化",因为它在任何有意义的意义上都不是"统一的".官方术语是"支撑初始化".