Reputation: 1759
Take this simple example:
struct has_destruct_t {
int a;
~has_destruct_t() {}
};
struct no_destruct_t {
int a;
};
int bar_no_destruct(no_destruct_t);
int foo_no_destruct(void) {
no_destruct_t tmp{};
bar_no_destruct(tmp);
return 0;
}
int bar_has_destruct(has_destruct_t);
int foo_has_destruct(void) {
has_destruct_t tmp{};
bar_has_destruct(tmp);
return 0;
}
foo_has_destruct
gets slightly worse codegen, because the destructor seems to force tmp
onto the stack:
foo_no_destruct(): # @foo_no_destruct()
pushq %rax
xorl %edi, %edi
callq bar_no_destruct(no_destruct_t)@PLT
xorl %eax, %eax
popq %rcx
retq
foo_has_destruct(): # @foo_has_destruct()
pushq %rax
movl $0, 4(%rsp)
leaq 4(%rsp), %rdi
callq bar_has_destruct(has_destruct_t)@PLT
xorl %eax, %eax
popq %rcx
retq
https://godbolt.org/z/388K1EfYa
But why does this need to be the case given that the destructor is 1) trivially inlinable and 2) empty?
Is there any way to include a destructor at zero cost?
Upvotes: 5
Views: 156
Reputation: 76829
The Itanium C++ ABI calling convention defines that a type with non-trivial destructor must be passed on the stack and ~has_destruct_t() {}
is always a non-trivial destructor. A trivial destructor must either be implicitly-declared or defaulted on its first declaration (~has_destruct_t() = default
).
The rule is necessary to make copy elision work if a prvalue is passed as function argument. Copy elision requires that the address of the function parameter is the same as the address of the temporary object materialized from the the prvalue function argument and that's an observable property.
So the caller needs to provide memory for the temporary object (and at the same time the function parameter object) to assure equal addresses.
This copy elision is mandatory since C++17 for types with non-trivial (and non-deleted) destructor, but was also allowed in all previous C++ standard editions, which the Itanium C++ ABI made use of.
If you don't intent to do anything in the destructor body, then don't declare it at all, except if you need to also make it virtual
, in which case virtual ~has_destruct_t() = default;
will do.
Upvotes: 9