8

我同意这样的共识,即通常最好在成员初始化列表而不是构造函数的主体中初始化 C++ 数据成员,但我对这种解释持怀疑态度

构建构造函数的另一种(低效)方法是通过赋值,例如:Fred::Fred() { x_ = whatever; }.在这种情况下,表达式无论如何都会导致创建一个单独的临时对象,并且这个临时对象被传递给 x_ 对象的赋值运算符。然后该临时对象在;. 那是低效的。

这真的正确吗?我原以为编译器会忽略默认构造的临时对象,该对象会立即被正文中的赋值替换。我不知道为什么我会预料到这一点,但在阅读了上述声明后,我想我多年来一直在悄悄地假设它。

成员初始化列表实际上更有效吗?如果是这样,是不是因为这个原因?

4

3 回答 3

10

Alexandrescu & Sutter的话(第 9 项)不要过早地悲观

避免过早的优化并不意味着无缘无故地损害效率。通过过早的悲观化,我们的意思是写出这样无端的潜在低效率:

• 在适合引用传递时定义传递值参数。(见第 25 项。)

• 当前缀版本一样好时使用后缀++。(见第 28 条。)

• 在构造函数中使用赋值而不是初始化列表。(见第 48 条。)

每当您在构造函数中编写赋值时,您的代码审查员都会保持警惕:发生了什么特别的事情吗?他真的想要一些特殊的两阶段初始化吗(因为无论如何都会生成成员的隐式默认构造!)。不要无缘无故地让代码的读者感到惊讶。

请注意,Alexandrescu 和 Sutter 在第 48 条中继续讨论潜在的低效率,但在任何地方都没有声称在真正优化的代码中存在实际的低效率。这也是题外话,它是关于表达意图并避免低效率的风险

于 2017-02-15T21:37:33.100 回答
9

使用成员初始化列表,

#include <string>

struct Fred {
  Fred() : x_("hello") { }
  std::string x_;
};

int main() {
  Fred fred;
}

-O3 -fno-exceptionsClang 3.9.1 和 gcc 6.3 使用( Compiler Explorer )生成以下内容:

main:                                   # @main
        xor     eax, eax
        ret

如果我们在 body 中做一个赋值:

#include <string>

struct Fred {
  Fred() { x_ = "hello"; }
  std::string x_;
};

int main() {
  Fred fred;
}

两者都生成更多代码,例如 Clang 3.9.1 输出:

main:                                   # @main
        push    rbx
        sub     rsp, 32
        lea     rbx, [rsp + 16]
        mov     qword ptr [rsp], rbx
        mov     qword ptr [rsp + 8], 0
        mov     byte ptr [rsp + 16], 0
        lea     rdi, [rsp]
        xor     esi, esi
        xor     edx, edx
        mov     ecx, .L.str
        mov     r8d, 5
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace(unsigned long, unsigned long, char const*, unsigned long)
        mov     rdi, qword ptr [rsp]
        cmp     rdi, rbx
        je      .LBB0_2
        call    operator delete(void*)
.LBB0_2:
        xor     eax, eax
        add     rsp, 32
        pop     rbx
        ret

.L.str:
        .asciz  "hello"

所以看起来成员初始化列表确实更有效,至少在某些情况下,即使使用现代编译器也是如此。

于 2017-02-15T21:52:22.067 回答
4

成员初始化列表实际上更有效吗?如果是这样,是不是因为这个原因?

一般是的。通过成员初始化,您可以将值直接传递给构造函数,否则将创建一个默认构造的对象,然后调用赋值运算符。请注意,这不是关于您提供的报价中提到的“临时”,而是关于字段本身。

你可以在这里看到它

class Verbose {
public:
    Verbose() { std::cout << "Verbose::Verbose()" << std::endl; }
    Verbose( int ) { std::cout << "Verbose::Verbose(int)" << std::endl; }
    Verbose &operator=( int )  { std::cout << "Verbose::operator=(int)" << std::endl; }
};

class A {
public:
    A() : v( 0 ) {}
    A(int)  { v = 0; }
private:
    Verbose v;    
};


int main() {
    std::cout << "case 1 --------------------" << std::endl;
    A a1;
    std::cout << "case 2 --------------------" << std::endl;
    A a2( 0 );
    // your code goes here
    return 0;
}

输出:

case 1 --------------------
Verbose::Verbose(int)
case 2 --------------------
Verbose::Verbose()
Verbose::operator=(int)
于 2017-02-15T21:43:09.197 回答