问题
#include <memory>
#include <vector>
using namespace std;
vector<unique_ptr<int>> e;
void f(unique_ptr<int> u) {
e.emplace_back(move(u));
}
For both Clang and GCC, the above code snippet generates something like:
f(std::unique_ptr<int, std::default_delete<int> >):
mov rsi, QWORD PTR e[rip+8] # rsi: vector.end_ptr
cmp rsi, QWORD PTR e[rip+16] # [e + rip + 16]: vector.storage_end_ptr
je .L52 # Slow path, need to reallocate
mov rax, QWORD PTR [rdi] # rax: unique_ptr<int> u
add rsi, 8 # end_ptr += 8
mov QWORD PTR [rdi], 0 # <==== Do we need to set the argument u to null here?
mov QWORD PTR [rsi-8], rax # *(end_ptr - 8) = u
mov QWORD PTR e[rip+8], rsi # update end_ptr
ret
.L52: # omitted
I was wondering why does the compiler generate mov QWORD PTR[rdi], 0
in this function? Is there any convention that requires compiler to do so?
Moreover, for simpler case like this:
void f(unique_ptr<int> u);
void h(int x) {
auto p = make_unique<int>(x);
f(move(p));
}
Why does the compiler generate:
call operator delete(void*, unsigned long)
at the end of h()
, given that p is always nullptr
after the invocation of f
?
回答1:
In both of these cases, the answer is: because the object you moved from will still be destroyed.
If you look at the code generated for a call to
void f(unique_ptr<int> u);
you will notice that the caller creates the object for parameter u
and calls its destructor afterwards as mandated by the calling convention. In case the call to f()
is inlined, the compiler will most likely be able to optimize this away. But the code generated for f()
has no control over the destructor of u
and, thus, has to set the internal pointer of u
to zero assuming that the destructor of u
will run after the function returns.
In your second example, we have sort of the inverse situation:
void h(int x) {
auto p = make_unique<int>(x);
f(move(p));
}
Contrary to what the name may suggest, std::move()
does not actually move an object. All it does is cast to an rvalue reference which allows the recipient of that reference to move from the object referred to—if he so choses. The actual move only happens, e.g., when another object is constructed from the given argument via a move constructor. Since the compiler does not know anything about what happens inside f()
at the point of definition of h()
, it can't assume that f()
will always move from the given object. For example, f()
could simply return or move only in some cases and not in others. Therefore, the compiler has to assume that the function might return without moving from the object and has to emit the delete
for the destructor. The function could also perform a move assignment instead of a move construction, in which case the outer destructor would still be needed to release ownership of the object previously held by whatever was assigned ownership of the new object…
来源:https://stackoverflow.com/questions/53330428/why-this-dead-store-of-unique-ptr-cannot-be-eliminated