C++中使用和不使用花括号进行初始化的区别

38
我们在C++中可以用两种方式来初始化变量。 第一种:
int abc = 7;

二:

int abc {7};

这两种方法有什么区别?编译器是否会以不同的方式处理它们,或者在执行代码的方式上有所不同?


4
http://en.cppreference.com/w/cpp/language/initialization - chris
3
对于 int 类型来说,唯一的区别是 {} 语法禁止缩窄转换,例如从字面量转换为过大而无法容纳在 int 中。对于类类型,根据您想了解差异的详细程度不同,情况可能会变得更加复杂。 - dyp
3个回答

55

简短版

通过{..}进行初始化是列表初始化,它禁止缩小转换。例如,如果LLONG_MAXlong long int的最大值,而你的int不能表示它:

int x = LLONG_MAX;  // probably accepted with a warning
int x {LLONG_MAX};  // error

同样地:
long long y = /*something*/;

int x = y;  // accepted, maybe with a warning
int x {y};  // error

长版

表单的初始化

T x = a;

复制初始化; 两种形式的初始化之一

T x(a);
T x{a};

直接初始化,参考[dcl.init]/15-16。

[dcl.init]/14 表示:

初始化的形式(使用括号或者等号)通常是无关紧要的,但当初始化器或被初始化实体具有类类型时则有所不同;请参见下面。

因此,在非类类型中,初始化的形式并不重要。但是,这两种直接初始化之间存在差异:

T x(a);  // 1
T x{a};  // 2

同样地,在这两个复制初始化之间:

T x = a;    // 1
T x = {a};  // 2

即,带有{..}的使用列表初始化。 {..}被称为花括号初始化列表
因此,当您比较T x = a;T x {a};时,存在两个区别:复制与直接初始化以及“非列表”与列表初始化。如其他人和上面的引用已经提到的那样,对于非类类型T,复制和直接初始化之间没有区别。但是,在列表初始化和没有列表初始化之间存在区别。也就是说,我们同样可以比较
int x (a);
int x {a};

在这种情况下,列表初始化禁止缩小转换。缩小转换在[dcl.init.list]/7中定义为:
- 从浮点类型到整数类型的隐式转换,或 - 从long double到double或float,或从double到float,除非源是常量表达式并且转换后的实际值在可以表示的值范围内(即使无法准确表示),或 - 从整数类型或未作用域枚举类型到浮点类型的转换,除非源是常量表达式并且转换后的实际值将适合目标类型,并且在转换回原始类型时将产生原始值,或 - 从整数类型或未作用域枚举类型到不能表示原始类型所有值的整数类型的转换,除非源是常量表达式,其经过整数提升后的值适合目标类型。

2
这种同时使用括号和大括号的初始化方式怎么样:std::random_device{}() - moooeeeep
6
这并不是一种单独的初始化方式。它使用表达式std::random_device{}构造了一个std::random_device类型的临时对象,然后调用该对象重载的operator(),就像std::random_device rd; rd()一样。random_device有一个operator(),它会调用RNG并返回一个(伪)随机数,请参见http://en.cppreference.com/w/cpp/numeric/random/random_device/operator()。 - dyp
1
很好,谢谢!现在你解释了一下,看起来很明显。 - moooeeeep
我尝试使用 int b1{2147483648}; 进行初始化。但是我没有得到错误,只得到了警告“warning: narrowing conversion of '2147483648ll' from 'long long int' to 'int' inside { } [-Wnarrowing]|”。为什么会这样呢? - Rajesh
@Rajesh 用的是哪个编译器和版本?显然,这只是在gcc 5之前的一个警告。另请参阅:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55783 - dyp
显示剩余3条评论

4

尽管对于int类型的现有回复已经很完整了,但我痛苦地发现,在某些情况下,(){}初始化之间存在其他差异。

关键词是{}是一个初始化列表。

其中一种情况是使用charcount副本来初始化std::string

std::string stars(5, '*')

将会把stars初始化为*****,但是

std::string stars{5, '*'}

将被解读为std::string stars(char(5), '*')并将星号初始化为*(前面有一个隐藏字符)。


1
第一种是复制初始化,而第二种是列表初始化。
但通常很少使用复制初始化。因为,如果您通过传递用户定义类型的对象来执行它,它只会导致位拷贝,因此如果用户定义类使用指针,则可能不会产生预期结果。

难道它们没有复制构造函数吗?我现在已经非常困惑了。 - RichieHH
如果用户定义的类型有指针,那么最好在构造函数和析构函数中编写复制构造函数(三法则)。但是,如果没有复制构造函数,则会导致“浅拷贝”,并可能导致悬空指针。 - yuvi
没错。使用指针的用户定义类型应该具有复制和初始化构造函数。也许你可以编辑一下你的回复。谢谢回复。 - RichieHH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接