成员初始化列表真的更高效吗?

9

我同意一般情况下最好在成员初始化列表中初始化C++数据成员,而不是在构造函数体中初始化,但我对this explanation持怀疑态度。

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

这是否正确?我原本以为编译器会省略默认构造的临时对象,因为它立即在构造函数体中被替换为赋值操作。我不知道我为什么会有这样的期望,但在阅读了上述说法之后,我想我可能已经默默地假定了多年。

成员初始化列表是否更有效率?如果是这样,是因为这个原因吗?


3
这句话的意思是:预计编译器需要对构造函数体内发生的所有事情进行完整的代码分析,但它可能不会这样做。因此是的,除了魔法般的编译器之外,初始化列表更有效率,而且在我看来更易于阅读。 - user2100815
1
编译器只能通过“as-if”规则来省略默认构造函数,因此需要能够确定没有区别。考虑到可以通过适当初始化轻松避免这种情况,为什么不这样做呢? - juanchopanza
初始化后跟赋值运算符不是复制省略情况。 - M.M
3个回答

11

根据Alexandrescu & Sutter(第9项)的说法,不要过早地降低效率

避免过早优化并不意味着无端损害效率。我们所谓的过早降低效率是指写出这样的潜在低效率代码:

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

• 使用后缀 ++ 时前缀版本同样好。(参见第28项。)

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

每当你在构造函数内部编写赋值语句时,你的代码评审者会警惕起来:是否有特殊情况发生?他真的想要某些特殊的两阶段初始化吗(因为成员的隐式默认构造已经生成!)。不要毫无理由地让代码读者感到惊讶。

请注意,Alexandrescu和Sutter在第48项中讨论了潜在的低效率问题,但没有声称在实际优化的代码中存在实际的低效率。这也不是重点,重点是表达意图和避免低效率的风险。

10
使用成员初始化列表,
#include <string>

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

int main() {
  Fred fred;
}

使用-O3 -fno-exceptions编译选项,Clang 3.9.1和gcc 6.3会生成以下代码 (Compiler Explorer):

main:                                   # @main
        xor     eax, eax
        ret
如果我们在正文中进行作业:
#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"

看起来成员初始化列表确实更有效率,至少对于某些情况而言,在现代编译器下仍然如此。


看到现代编译器无法对此进行优化,感觉真是令人沮丧。 - TemplateRex

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)

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