Empty constructors can disable useful compiler optimizations.
// Avoid this
MyClass() {}
// Prefer this
MyClass() = default;
1. The Performance Secret: "Trivial" Types
With {} (the slow way), the constructor becomes user-provided, making the type non-trivially default constructible. That prevents standard library implementations from using the highly optimized code paths available for trivial types.
With "= default" (the fast way), the constructor remains compiler-generated, allowing the type to stay trivially default constructible when all its members are trivial. This enables the standard library and compiler to use optimized code paths that are unavailable for user-provided constructors.
In the following example, the compiler generates machine instructions for A but not for B. From the language's perspective "user-provided" and "does nothing" are different concepts:
// C++ code
struct A { A() {} };
struct B { B() = default;};
int main() {
A a;
B b;
}
//Assembly code (generated with Compiler Explorer, x86-64 clang (trunk), no optimization:
main:
sub rsp, 16
lea rdi, [rbp - 1]
call A::A() [base object constructor]
xor eax, eax
add rsp, 16
pop rbp
ret
A::A() [base object constructor]:
push rbp
mov rbp, rsp
mov qword ptr [rbp - 8], rdi
pop rbp
ret
Note that if you use the optimization flag -O1, the code reduces to its optimal form:
main: xor eax, eax ret
This is a good illustration of a broader point: at -O0, C++ codegen tends to reflect the syntax of your code fairly literally (useful for debugging, every statement maps predictably to instructions). At -O1 and above, codegen instead reflects the observable semantics and "construct two empty objects nobody uses" has no observable semantics at all.
2. Guarding Your Move Semantics
Writing an empty destructor ~MyClass() {} triggers the Rule of Five in a bad way. The compiler assumes that because you wrote a custom destructor, you are managing resources manually. It automatically deletes your implicit move constructor and move assignment operator. By trying to be clean with {}, you accidentally cripple your class's ability to be efficiently moved. Using "= default" keeps your move semantics intact.
3. Why Can't the Compiler Just Optimize {}?
It isn't a lack of compiler intelligence; it is a legal restriction. The C++ Standard explicitly states that writing {} makes a constructor user-provided. Changing this rule would break the legacy code. For example, if the standard suddenly declared that empty braces implied "trivial," code that intentionally uses {} to block unsafe memcpy operations (like cryptography classes) would instantly become vulnerable. Furthermore, if you had an empty #ifdef DEBUG block inside your constructor, your class would dangerously flip between being non-trivial in Debug mode and trivial in Release mode.
The C++ committee gave us "= default" in C++11 as an explicit toggle. {} tells the compiler, "Hands off, I'm taking control." = default tells the compiler, "I want standard behavior, optimize this as much as you can." Always give the compiler the green light.
No comments:
Post a Comment