为什么在 C++ 中使用 = 来初始化原始类型?

发布于 2024-07-09 20:33:48 字数 308 浏览 14 评论 0原文

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

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

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

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

Where I work, people mostly think that objects are best initialised using C++-style construction (with parentheses), whereas primitive types should be initialised with the = operator:

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

Nobody seems to be able to explain why they prefer things this way, though. I can see that std::string strFoo = "Foo"; would be inefficient because it would involve an extra copy, but what's wrong with just banishing the = operator altogether and using parentheses everywhere?

Is it a common convention? What's the thinking behind it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

凉墨 2024-07-16 20:33:48

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

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

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)。 ]

Initializing variables with the = operator or with a constructor call are semantically the same, it's just a question of style. I prefer the = operator, since it reads more naturally.

Using the = operator usually does not generate an extra copy - it just calls the normal constructor. Note, however, that with non-primitive types, this is only for initializations that occur at the same time as the declarations. Compare:

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*)

When you have non-trivial default constructors, the latter construction can be slightly more inefficient.

The C++ standard, section 8.5, paragraph 14 states:

Otherwise (i.e., for the remaining copy-initialization cases), a temporary is created. User-defined conversion sequences that can convert from the source type to the destination type or a derived class thereof are enumerated (13.3.1.4), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into a temporary, whose type is the type returned by the call of the user-defined conversion function, with the cv-qualifiers
of the destination type. If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The object being initialized is then direct-initialized
from the temporary according to the rules above.87) In certain cases, an implementation is permitted to eliminate the temporary by initializing the object directly; see 12.2.

Part of section 12.2 states:

Even when the creation of the temporary object is avoided, all the semantic restrictions must be respected as if the temporary object was created. [Example:
even if the copy constructor is not called, all the semantic restrictions, such as accessibility (11), shall be satisfied. ]

星星的軌跡 2024-07-16 20:33:48

我只是觉得需要再写一篇愚蠢的闲聊帖子。

string str1 = "foo";

称为复制初始化,因为编译器在不删除任何临时变量的情况下所做的事情是:

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));

可能会将某人视为使用类型 v< 的临时变量初始化的变量 u 的定义。 /code> 为其构造函数获取一个名为 a 的参数。 但事实上,编译器对此所做的事情是这样的:

T u(v a);

它创建一个函数声明,该函数声明采用 v 类型的参数,并带有一个名为 a 的参数。 因此,人们确实可以

T u = v(a);

消除歧义,即使他们

T u((v(a)));

也可以这样做,因为函数参数周围永远没有括号,编译器也会将其读取为变量定义而不是函数声明:)

I just felt the need for another silly litb post.

string str1 = "foo";

is called copy-initialization, because what the compiler does, if it doesn't elide any temporaries, is:

string str1(string("foo")); 

beside checking that the conversion constructor used is implicit. In fact, all implicit conversions are defined by the standard in terms of copy initialization. It is said that an implicit conversion from type U to type T is valid, if

T t = u; // u of type U

is valid.

In constrast,

string str1("foo");

is doing exactly what is written, and is called direct initialization. It also works with explicit constructors.

By the way, you can disable eliding of temporaries by using -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.

The Standard says there is practically no difference between

T a = u;

and

T a(u);

if T and the type of u are primitive types. So you may use both forms. I think that it's just the style of it that makes people use the first form rather than the second.


Some people may use the first in some situation, because they want to disambiguate the declaration:

T u(v(a));

migh look to someone as a definition of a variable u that is initialized using a temporary of a type v that gets a parameter for its constructor called a. But in fact, what the compiler does with that is this:

T u(v a);

It creates a function declaration that takes a argument of type v, and with a parameter called a. So people do

T u = v(a);

to disambiguate that, even though they could have done

T u((v(a)));

too, because there are never parentheses around function parameters, the compiler would read it as a variable definition instead of a function declaration too :)

我是男神闪亮亮 2024-07-16 20:33:48

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

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

Unless you've proven that it matters with respect to performance, I wouldn't worry about an extra copy using the assignment operator in your example (std::string foo = "Foo";). I'd be pretty surprised if that copy even exists once you look at the optimized code, I believe that will actually call the appropriate parameterized constructor.

In answer to your question, yes, I'd say that it's a pretty common convention. Classically, people have used assignment to initialize built-in types, and there isn't a compelling reason to change the tradition. Readability and habit are perfectly valid reasons for this convention given how little impact it has on the ultimate code.

平生欢 2024-07-16 20:33:48

您可能会发现诸如此类的代码

std::string strFoo = "Foo";

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

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

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

You will probably find that code such as

std::string strFoo = "Foo";

will avoid doing an extra copy and compiles to the same code (a call of a single-argument constructor) as the one with parentheses.

On the other hand, there are cases where one must use parentheses, such as a constructor member initialisation list.

I think the use of = or parentheses to construct local variables is largely a matter of personal choice.

囍笑 2024-07-16 20:33:48

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

Well, who knows what they think, but I also prefer the = for primitive types, mainly because they are not objects, and because that's the "usual" way to initialize them.

冷夜 2024-07-16 20:33:48

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

foo::foo()   
  ,anInt(0)   
  ,aFloat(0.0)   
{   
}   

But then just to confuse you even more you initialize primitives in the initialization list using object syntax.

foo::foo()   
  ,anInt(0)   
  ,aFloat(0.0)   
{   
}   
感性不性感 2024-07-16 20:33:48

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

It's an issue of style. Even the statement that "std::string = "Foo"; would be inefficient because it would involve an extra copy" is not correct. This "extra copy" is removed by the compiler.

路弥 2024-07-16 20:33:48

我相信这更多是一种习惯,很少有对象可以使用 = 初始化,字符串就是其中之一。 这也是执行您所说的“到处使用括号(该语言允许您使用它)”的一种方式

I believe that is more of a habit, very few objects could be initialized using = , the string is one of them. It's also a way of doing what you said "using parenthesis everywhere (that the language allows you to use it)"

守望孤独 2024-07-16 20:33:48

可以提出的一个论点是:

std::string foo("bar");

即使参数计数发生变化,它也会保持不变,即:

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

不适用于“=”符号。

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

Array arr = 5;

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

Array arr(5);

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

One argument that one could make for:

std::string foo("bar");

Is that it keeps things the same even if the argument count changes, i.e.:

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

Doesn't work with a '=' sign.

Another thing is that for many objects a '=' feels unnatural, for example say you have a Array class where the argument gives the length:

Array arr = 5;

Doesn't feel good, since we don't construct an Array with the value 5, but with length 5:

Array arr(5);

feels more natural, since you are constructing an object with the given parameter, not just copying a value.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文