Observando a saída do assembly da seguinte função C++ simples:
#include <memory>
int square(std::unique_ptr<int> num) {
return *num * *num;
}
Gcc e clang emitem o seguinte assembly ( -O2
):
square(std::unique_ptr<int, std::default_delete<int> >):
mov rax, QWORD PTR [rdi]
mov eax, DWORD PTR [rax]
imul eax, eax
ret
Isso foi muito inesperado para mim: para onde o destruidor está sendo chamado?
O MSVC, por outro lado, faz o que eu esperava e chama o destruidor.
Aqui está uma reprodução de godbolt: https://godbolt.org/z/e66xh9Yos
Também tentei usar tipos maiores int
para ver se é alguma otimização de tamanho pequeno, mas não pareceu ser o caso.
Alguém pode explicar o que está acontecendo aqui?
Não é especificado se os objetos de parâmetro de função são destruídos quando uma função retorna ou no final da expressão completa que contém a chamada para a função.
Em outras palavras, é uma decisão da ABI se o chamador ou o receptor é ou não responsável por chamar o destruidor.
A ABI Itanium C++ (usada em todos os lugares, exceto MSVC, até onde sei) opta por destruir objetos de parâmetro de função no final da expressão completa que contém a chamada. Por outro lado, a ABI do MSVC opta por destruí-los quando a função retorna.
O padrão tornou explicitamente não especificado o suporte retroativo de ambas as decisões da ABI.
Observe também que chamar o construtor do objeto de parâmetro de função é sempre responsabilidade do chamador. Assim, o chamador é capaz de chamar o destruidor tão bem quanto o receptor. Ele sabe onde o objeto foi construído e qual é o seu tipo a partir da declaração da função.