In short: There is a small overhead but the compiler is smart!
The long version:
The memory has to be zeroed out and that takes a bit of work. You can see that when you compile your class to assembly, together with a simple driver function
int main() { C c; }
using -O1
optimizations.
Then without the member initialization, the generated code looks like
main: # @main
push rax
mov rdi, rsp
call C::C() [base object constructor]
xor eax, eax
pop rcx
ret
C::C() [base object constructor]: # @C::C() [base object constructor]
ret
Where in the last two lines you see that the constructor is trivial. When you add the member initialization with the brackets, it becomes
C::C() [base object constructor]: # @C::C() [base object constructor]
mov qword ptr [rdi], 0
ret
The mov
instruction is setting a DWORD
at some specific memory location to zero. DWORD
is 32-bits.
The compiler may be able to combine initializations. For example, if you add a second int
:
class C {
public:
explicit C()
: member(), anotherMember()
{}
private:
int member;
int anotherMember; // <====
};
int main() {
C c;
}
then the DWORD
changes to a QWORD
so it actually zeroes both integers at once. You will see this even with a higher optimization level, for example when you add something the compiler cannot optimize away such as a read from stdin
and compile this with -O2
#include <iostream>
class C {
public:
explicit C()
: member()
{}
int member;
};
int main() {
int x;
C c;
std::cin >> c.member;
}
then the constructor body will be inlined into the main function but you will still find the zero-instruction
mov dword ptr [rsp], 0
Also note that depending on the code following the instantiation, the compiler may optimize further. For example, if you look at the output for
C c;
c.member = expression;
then you will see that the zero-assignment will be removed from the output.