It’s a part of the copy elision, which is a compiler optimization strategy defined in the C++ standard.
class T { public: T() : m_answer(42) {} private: int m_answer; }; T createNamedT() { T t; return t; } T createUnnamedT() { return T(); } int main() { T t2 = createNamedT(); T t1 = createUnnamedT(); return 0; }
Scenario 1 (gcc 5.1 (x86-64), flags: ‘-fno-elide-constructors’) ▼
T::T() [base object constructor]: push rbp mov rbp, rsp mov QWORD PTR [rbp-8], rdi mov rax, QWORD PTR [rbp-8] mov DWORD PTR [rax], 42 nop pop rbp ret T::T(T const&) [base object constructor]: push rbp mov rbp, rsp mov QWORD PTR [rbp-8], rdi mov QWORD PTR [rbp-16], rsi mov rax, QWORD PTR [rbp-8] mov rdx, QWORD PTR [rbp-16] mov edx, DWORD PTR [rdx] mov DWORD PTR [rax], edx nop pop rbp ret createNamedT(): push rbp mov rbp, rsp sub rsp, 32 lea rax, [rbp-32] mov rdi, rax call T::T() [complete object constructor] lea rdx, [rbp-32] lea rax, [rbp-16] mov rsi, rdx mov rdi, rax call T::T(T const&) [complete object constructor] mov eax, DWORD PTR [rbp-16] leave ret createUnnamedT(): push rbp mov rbp, rsp sub rsp, 32 lea rax, [rbp-16] mov rdi, rax call T::T() [complete object constructor] lea rdx, [rbp-16] lea rax, [rbp-32] mov rsi, rdx mov rdi, rax call T::T(T const&) [complete object constructor] mov eax, DWORD PTR [rbp-32] leave ret main: push rbp mov rbp, rsp sub rsp, 64 call createNamedT() mov DWORD PTR [rbp-32], eax lea rdx, [rbp-32] lea rax, [rbp-48] mov rsi, rdx mov rdi, rax call T::T(T const&) [complete object constructor] call createUnnamedT() mov DWORD PTR [rbp-16], eax lea rdx, [rbp-16] lea rax, [rbp-64] mov rsi, rdx mov rdi, rax call T::T(T const&) [complete object constructor] mov eax, 0 leave ret
Summary
- To call createNamedT()/createUnnamedT() 1 default c’tor and 2 copy c’tors are used.
Scenario 2 (gcc 12.2 (x86-64), flags: ‘-fno-elide-constructors’) ▼
T::T() [base object constructor]: push rbp mov rbp, rsp mov QWORD PTR [rbp-8], rdi mov rax, QWORD PTR [rbp-8] mov DWORD PTR [rax], 42 nop pop rbp ret T::T(T&&) [base object constructor]: push rbp mov rbp, rsp mov QWORD PTR [rbp-8], rdi mov QWORD PTR [rbp-16], rsi mov rax, QWORD PTR [rbp-8] mov rdx, QWORD PTR [rbp-16] mov edx, DWORD PTR [rdx] mov DWORD PTR [rax], edx nop pop rbp ret createNamedT(): push rbp mov rbp, rsp sub rsp, 16 lea rax, [rbp-8] mov rdi, rax call T::T() [complete object constructor] lea rdx, [rbp-8] lea rax, [rbp-4] mov rsi, rdx mov rdi, rax call T::T(T&&) [complete object constructor] mov eax, DWORD PTR [rbp-4] leave ret createUnnamedT(): push rbp mov rbp, rsp sub rsp, 16 lea rax, [rbp-4] mov rdi, rax call T::T() [complete object constructor] mov eax, DWORD PTR [rbp-4] leave ret main: push rbp mov rbp, rsp sub rsp, 16 call createNamedT() mov DWORD PTR [rbp-4], eax call createUnnamedT() mov DWORD PTR [rbp-8], eax mov eax, 0 leave ret
Summary
- To call createNamedT() 1 default c’tor and 1 move c’tor are used.
- To call createNamedT() just 1 default c’tor is used.
Scenario 3 (gcc 5.1 (x86-64), flags: none) ▼
T::T() [base object constructor]: push rbp mov rbp, rsp mov QWORD PTR [rbp-8], rdi mov rax, QWORD PTR [rbp-8] mov DWORD PTR [rax], 42 nop pop rbp ret createNamedT(): push rbp mov rbp, rsp sub rsp, 16 lea rax, [rbp-16] mov rdi, rax call T::T() [complete object constructor] mov eax, DWORD PTR [rbp-16] leave ret createUnnamedT(): push rbp mov rbp, rsp sub rsp, 16 lea rax, [rbp-16] mov rdi, rax call T::T() [complete object constructor] mov eax, DWORD PTR [rbp-16] leave ret main: push rbp mov rbp, rsp sub rsp, 32 call createNamedT() mov DWORD PTR [rbp-16], eax call createUnnamedT() mov DWORD PTR [rbp-32], eax mov eax, 0 leave ret
Summary
- To call createNamedT()/createUnnamedT() just 1 default c’tor is used.
Note ▼
If you change CreateNamedT() like that (to be smart):
T createNamedT() { T t; return std:move(t); }
The compiler (with flag ‘-Wall’) complains like that:
warning: moving a local object in a return statement prevents copy elision [-Wpessimizing-move] | return std::move(t); | ~~~~~~~~~^~~.
Used tool: https://godbolt.org/