Почему это мертвое хранилище unique_ptr не может быть устранено? - PullRequest
0 голосов
/ 16 ноября 2018
#include <memory>
#include <vector>
using namespace std;

vector<unique_ptr<int>> e;

void f(unique_ptr<int> u) {
    e.emplace_back(move(u));
}

Для обоих Clang и GCC приведенный выше фрагмент кода генерирует что-то вроде:

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

Мне было интересно, почему компилятор генерирует mov QWORD PTR[rdi], 0 в этой функции?Есть ли соглашение, которое требует компилятора для этого?

Более того, для более простого случая, например this :

void f(unique_ptr<int> u);

void h(int x) {
    auto p = make_unique<int>(x);
    f(move(p));
}

Почему компилятор генерирует:

call    operator delete(void*, unsigned long)

в конце h(), учитывая, что p всегда nullptr после вызова f?

1 Ответ

0 голосов
/ 16 ноября 2018

В обоих случаях ответ таков: объект, с которого вы переместились, все равно будет уничтожен.

Если вы посмотрите на код, сгенерированный для вызова

void f(unique_ptr<int> u);

вы заметите, что вызывающая сторона создает объект для параметра u и впоследствии вызывает его деструктор в соответствии с соглашением о вызовах. В случае, если вызов f() встроен, компилятор, скорее всего, сможет оптимизировать это. Но код, сгенерированный для f(), не контролирует деструктор u и, таким образом, должен установить нулевой внутренний указатель u, предполагая, что деструктор u будет запущен после возврата функции.

Во втором примере мы имеем обратную ситуацию:

void h(int x) {
    auto p = make_unique<int>(x);
    f(move(p));
}

Вопреки тому, что может предложить название, std::move() на самом деле не перемещает объект. Все, что он делает, - это приведение к rvalue-ссылке, которая позволяет получателю этой ссылки перемещаться от объекта, на который делается ссылка, - если он того пожелает. Фактическое перемещение происходит, например, когда другой объект создается из данного аргумента через конструктор перемещения. Поскольку компилятор ничего не знает о том, что происходит внутри f() в точке определения h(), он не может предполагать, что f() всегда будет перемещаться из данного объекта. Например, f() может просто вернуться или переместиться только в некоторых случаях, а не в других. Следовательно, компилятор должен предположить, что функция может вернуться, не выходя из объекта, и должен выдать delete для деструктора. Функция может также выполнять назначение перемещения вместо конструкции перемещения, и в этом случае внешний деструктор все еще будет необходим для освобождения владельца объекта, ранее принадлежавшего тому, кому было назначено право собственности на новый объект…

...