Are member-initialization lists really more efficient?
Asked Answered
E

3

10

I agree with the consensus that it's generally best to initialise C++ data members in a member initialization list rather than the body of a constructor, but I am sceptical of this explanation

The other (inefficient) way to build constructors is via assignment, such as: Fred::Fred() { x_ = whatever; }. In this case the expression whatever causes a separate, temporary object to be created, and this temporary object is passed into the x_ object’s assignment operator. Then that temporary object is destructed at the ;. That’s inefficient.

Is this actually correct? I would have expected the compiler to elide the default-constructed temporary object which is immediately replaced by assignment in the body. I don't know why I expected this but having read the above claim I guess I have been quietly assuming it for years.

Are member initialization lists actually more efficient? If so, is it for this reason?

Elane answered 15/2, 2017 at 21:31 Comment(3)
That would seem to expect the compiler to perform a complete code analysis of what happens in the body of the constructor, which it probably won't do. So yes, initialisation lists are, magic compilers aside, more efficient, and also, IMHO, easier to read.Goodell
the compiler could only "elide" the default construction through the "as-if" rule, so it needs to be able to figure out there is no difference. Given that it is trivial to avoid this by suitably initializing, why not do it?Madancy
Initialization followed by assignment operator is not a copy-elision scenarioWise
M
11

Using member init list,

#include <string>

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

int main() {
  Fred fred;
}

Clang 3.9.1 and gcc 6.3 generate the following with -O3 -fno-exceptions (Compiler Explorer):

main:                                   # @main
        xor     eax, eax
        ret

If we do an assignment in the body instead:

#include <string>

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

int main() {
  Fred fred;
}

both generate a lot more code, e.g. Clang 3.9.1 outputs this:

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"

So it seems member init lists really are more efficient, at least for some cases, even with modern compilers.

Minardi answered 15/2, 2017 at 21:52 Comment(1)
that's actually depressing to see that modern compilers won't optimize this away.Outroar
O
12

In the words of Alexandrescu & Sutter (item 9) Don't pessimize prematurely

Avoiding premature optimization does not imply gratuitously hurting efficiency. By premature pessimization we mean writing such gratuitous potential inefficiencies as:

• Defining pass-by-value parameters when pass-by-reference is appropriate. (See Item25.)

• Using postfix + + when the prefix version is just as good. (See Item 28.)

• Using assignment inside constructors instead of the initializer list. (See Item 48.)

Whenever you are writing assignments inside constructors, your code reviewers will be on alert: is something special going on? Did he really want some special two-stage initialization (because there is an implicit default construction of the member being generated anyway!). Don't surprise the readers of your code gratuitously.

Note that Alexandrescu & Sutter go on in Item 48 to discuss the potential inefficiency, but don't claim anywhere that there an actual inefficiency in real optimized code. That is also beside the point, it's about expressing intent and avoiding the risk of inefficiency.

Outroar answered 15/2, 2017 at 21:37 Comment(0)
M
11

Using member init list,

#include <string>

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

int main() {
  Fred fred;
}

Clang 3.9.1 and gcc 6.3 generate the following with -O3 -fno-exceptions (Compiler Explorer):

main:                                   # @main
        xor     eax, eax
        ret

If we do an assignment in the body instead:

#include <string>

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

int main() {
  Fred fred;
}

both generate a lot more code, e.g. Clang 3.9.1 outputs this:

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"

So it seems member init lists really are more efficient, at least for some cases, even with modern compilers.

Minardi answered 15/2, 2017 at 21:52 Comment(1)
that's actually depressing to see that modern compilers won't optimize this away.Outroar
I
4

Are member initialization lists actually more efficient? If so, is it for this reason?

Generally yes. With member initialization you pass value directly to a constructor, otherwise a default-constructed object would be created and then assignment operator would be called. Note this is not about "temporary" mentioned in quote you provided, this is about field itself.

You can see it live here

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

output:

case 1 --------------------
Verbose::Verbose(int)
case 2 --------------------
Verbose::Verbose()
Verbose::operator=(int)
Iodic answered 15/2, 2017 at 21:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.