【问题标题】:Why use = to initialise a primitive type in C++?为什么使用 = 来初始化 C++ 中的原始类型?
【发布时间】:2008-12-09 17:52:07
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: c++ coding-style c++03


    【解决方案1】:

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

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

    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++ standard,第 8.5 节第 14 段指出:

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

    第 12.2 节的部分内容指出:

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

    【讨论】:

    • 使用 = 将创建一个临时字符串,并将使用该临时字符串和复制构造函数初始化对象。(这就是表单被称为复制初始化的原因,而字符串 o("foo" ) 称为直接初始化)。另一个区别是 = "foo" 需要一个隐式 ctor。
    • 但是,允许编译器消除创建并传递给复制构造函数的临时变量。所以你可能不会注意到正在创建的临时文件。
    • Herb Sutter 关于“直接初始化”与“复制初始化”的文章:gotw.ca/gotw/036.htm
    • @litb: 编译器可以完全优化掉临时的构造,只调用普通的构造函数。
    • 它还必须检查复制构造函数的可见性。如果无法访问,则 = 表单确实无效:)
    【解决方案2】:

    我只是觉得需要另一个愚蠢的 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)));
    

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

    【讨论】:

    • 历史:在原始类型的情况下,T a = u 是长期以来唯一允许的形式。第二种形式 T a(u) 被引入标准中,用于初始化列表,对于原始类型是等价的。
    【解决方案3】:

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

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

    【讨论】:

    • 啊!是的,就是这样:在 C 语言中,= 运算符是唯一的方法,所以对于老前辈来说,它仍然“感觉不错”。谢谢。
    【解决方案4】:

    你可能会发现这样的代码

    std::string strFoo = "Foo";
    

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

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

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 在 C++ 中,原始类型的对象是对象,它们只是不是类对象。
      • 什么?从何时起?我说的是 OOP 意义上的对象。 C++ 原始类型绝对不是对象。
      • 是的。除了引用、函数和 void 类型之外的所有内容都是对象。
      • 我指的是 C++ 标准中“对象”的定义。我想我同意他们充其量只是退化的对象,OOP 方面:他们唯一的“成员”是操作员。但我认为这与它们的初始化方式没有任何关系:我认为这纯粹是因为它是从 C 继承的“通常方式”。
      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

          一个人可以提出的论点:

          std::string foo("bar");

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

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

          不适用于“=”符号。

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

          数组 arr = 5;

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

          数组 arr(5);

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

          【讨论】:

          • 'explicit' 关键字仅用于此目的 - 确保“Array arr = 5”不会编译。如果 Array(int) 构造函数被显式声明,则必须说“Array arr(5)”。
          【解决方案9】:

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

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

          【讨论】:

            猜你喜欢
            • 2011-04-17
            • 1970-01-01
            • 2014-06-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-03-05
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多