8

在我工作的地方,人们大多认为最好使用 C++ 风格的构造(带括号)初始化对象,而应该使用 = 运算符初始化原始类型:

std::string strFoo( "Foo" );
int nBar = 5;

不过,似乎没有人能够解释为什么他们更喜欢这种方式。我可以看到这std::string = "Foo";将是低效的,因为它会涉及额外的副本,但是=完全消除运算符并在任何地方使用括号有什么问题?

这是一个共同的约定吗?背后的想法是什么?

4

9 回答 9

17

使用 = 运算符或使用构造函数调用初始化变量在语义上是相同的,只是样式问题。我更喜欢 = 运算符,因为它读起来更自然。

使用 = 运算符通常不会生成额外的副本——它只是调用普通的构造函数。但是请注意,对于非原始类型,这仅适用于与声明同时发生的初始化。比较:

std::string strFooA("Foo");  // Calls std::string(const char*) constructor
std::string strFoo = "Foo";  // Calls std::string(const char*) constructor
                             // This is a valid (and standard) compiler optimization.

std::string strFoo;  // Calls std::string() default constructor
strFoo = "Foo";      // Calls std::string::operator = (const char*)

当您有非平凡的默认构造函数时,后一种构造可能会稍微低效。

C++ 标准,第8.5 节,第 14 段指出:

否则(即,对于剩余的复制初始化情况),创建一个临时的。枚举了可以从源类型转换为目标类型或其派生类的用户定义的转换序列(13.3.1.4),并通过重载决议(13.3)选择最佳的转换序列。调用如此选择的用户定义转换将初始化表达式转换为临时类型,其类型是调用用户定义转换函数返回的类型,带有目标类型的 cv 限定符。如果转换无法完成或不明确,则初始化格式错误。然后根据上述规则从临时对象中直接初始化正在初始化的对象。87 )在某些情况下,允许实现通过直接初始化对象来消除临时性;见 12.2。

第 12.2 节的一部分规定:

即使避免创建临时对象,也必须遵守所有语义限制,就像创建临时对象一样。[示例:即使不调用复制构造函数,也应满足所有语义限制,例如可访问性(11)。]

于 2008-12-09T17:57:42.883 回答
11

我只是觉得需要另一个愚蠢的 litb 帖子。

string str1 = "foo";

被称为copy-initialization,因为如果编译器没有省略任何临时文件,编译器所做的是:

string str1(string("foo")); 

除了检查使用的转换构造函数是隐式的。事实上,所有隐式转换都是由标准根据复制初始化定义的。据说从 U 类型到 T 类型的隐式转换是有效的,如果

T t = u; // u of type U

已验证。

相比之下,

string str1("foo");

正在执行所写的操作,称为直接初始化。它也适用于显式构造函数。

顺便说一句,您可以使用 -fno-elide-constructors 禁用临时对象的省略:

-fno-elide-constructors
    The C++ standard allows an implementation to omit creating a temporary which 
    is only used to initialize another object of the same type. Specifying this 
    option disables that optimization, and forces G++ to call the copy constructor 
    in all cases.

标准说两者之间几乎没有区别

T a = u;

T a(u);

如果 T 和 u 的类型是原始类型。所以你可以使用这两种形式。我认为正是它的风格使人们使用第一种形式而不是第二种形式。


有些人可能会在某些情况下使用第一个,因为他们想消除声明的歧义:

T u(v(a));

可能会将某人视为变量的定义,该变量u是使用临时类型初始化的,该类型v为其构造函数获取参数,称为a. 但实际上,编译器的作用是这样的:

T u(v a);

它创建一个函数声明,该声明接受一个类型为v的参数,并带有一个名为 的参数a。所以人们这样做

T u = v(a);

消除歧义,即使他们本可以做到

T u((v(a)));

同样,因为函数参数周围从来没有括号,编译器也会将其作为变量定义而不是函数声明来读取:)

于 2008-12-09T18:21:34.147 回答
4

除非您已经证明它对性能很重要,否则我不会担心在您的示例 ( std::string foo = "Foo";) 中使用赋值运算符会产生额外的副本。一旦您查看优化的代码,如果该副本甚至存在,我会感到非常惊讶,我相信它实际上会调用适当的参数化构造函数。

在回答您的问题时,是的,我会说这是一个非常普遍的约定。传统上,人们使用赋值来初始化内置类型,并且没有令人信服的理由来改变传统。鉴于它对最终代码的影响很小,可读性和习惯是这种约定的完全正当理由。

于 2008-12-09T17:59:16.937 回答
2

你可能会发现这样的代码

std::string strFoo = "Foo";

将避免进行额外的复制并编译为与带括号的代码相同的代码(调用单参数构造函数)。

另一方面,在某些情况下必须使用括号,例如构造函数成员初始化列表。

我认为使用 = 或括号来构造局部变量在很大程度上是个人选择的问题。

于 2008-12-09T17:57:58.920 回答
1

好吧,谁知道他们的想法,但我也更喜欢原始类型的 =,主要是因为它们不是对象,而且因为这是初始化它们的“通常”方式。

于 2008-12-09T17:58:06.017 回答
0

这是风格问题。即使是“std::string = "Foo"; 的声明也会效率低下,因为它会涉及额外的副本”是不正确的。编译器会删除此“额外副本”。

于 2008-12-09T17:57:05.100 回答
0

我相信这更像是一种习惯,很少有对象可以使用 = 来初始化,字符串就是其中之一。这也是一种做你所说的“在任何地方使用括号(语言允许你使用它)”的方式

于 2008-12-09T17:59:23.723 回答
0

人们可以提出的一种论点:

std::string foo("bar");

即使参数计数发生变化,它是否保持不变,即:

std::string foo("bar", 5);

不适用于“=”符号。

另一件事是,对于许多对象来说,'=' 感觉不自然,例如,假设您有一个 Array 类,其中参数给出了长度:

数组 arr = 5;

感觉不太好,因为我们没有构造一个值为 5 而是长度为 5 的 Array:

数组 arr(5);

感觉更自然,因为您正在使用给定参数构造一个对象,而不仅仅是复制一个值。

于 2008-12-09T18:36:15.910 回答
0

但是为了让您更加困惑,您使用对象语法在初始化列表中初始化原语。

foo::foo()   
  ,anInt(0)   
  ,aFloat(0.0)   
{   
}   
于 2008-12-09T18:44:33.460 回答