Зомби объекты после std :: move - PullRequest
10 голосов
/ 03 ноября 2010

Я запутался в состоянии объекта после он был перемещен с использованием семантики перемещения C ++ 0x.Насколько я понимаю, после того, как объект был перемещен, он все еще является действительным объектом, но его внутреннее состояние было изменено, поэтому при вызове его деструктора никакие ресурсы не освобождаются.

Но если мое понимание верно,деструктор перемещенного объекта должен все еще вызываться .

Но этого не происходит, когда я выполняю простой тест:

struct Foo
{
    Foo()  
    {
        s = new char[100]; 
        cout << "Constructor called!" << endl;  
    }

    Foo(Foo&& f) 
    {
        s = f.s;
        f.s = 0;
    }

    ~Foo() 
    { 
        cout << "Destructor called!" << endl;   
        delete[] s; // okay if s is NULL
    }

    void dosomething() { cout << "Doing something..." << endl; }

    char* s;
};

void work(Foo&& f2)
{
    f2.dosomething();
}

int main()
{
    Foo f1;
    work(std::move(f1));
}

Этот вывод:

Constructor called!
Doing something...
Destructor called!

Обратите внимание, деструктор вызывается только один раз.Это показывает, что мое понимание здесь выключено.Почему деструктора не вызвали дважды?Вот мое толкование того, что должно произошло: построено

  1. Foo f1.
  2. Foo f1 передается в work, что принимает значение rf2.
  3. Вызывается конструктор перемещения Foo, перемещающий все ресурсы в f1 в f2.
  4. Теперь вызывается деструктор f2, освобождая всеresources.
  5. Теперь вызывается деструктор f1, который фактически ничего не делает, поскольку все ресурсы были переведены в f2.Тем не менее деструктор все же вызывается.

Но поскольку вызывается только один деструктор, ни шага 4, ни шага 5 не происходит.Я сделал обратный след от деструктора, чтобы увидеть, откуда он вызывается, и он вызывается с шага 5. Так почему же деструктор f2 также не называется?

EDIT: Хорошо, я изменил это так, что он фактически управляет ресурсом.(Внутренний буфер памяти.) Тем не менее, я получаю такое же поведение, когда деструктор вызывается только один раз.

Ответы [ 2 ]

9 голосов
/ 03 ноября 2010

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

Исходный ответ сохранендля потомков

Чтобы действительно выполнить ход, у вас должно быть что-то вроде Foo f3(std::move(f2)); внутри work.Затем вы можете вызвать функцию-член на f3, который является новым объектом, созданным путем перемещения из f

Насколько я вижу, вы вообще не получаете семантику перемещения.Вы просто видите старую копию elision.

, чтобы перемещение произошло, вы должны использовать std::move (или, в частности, передаваемый конструктору аргумент должен быть безымянной / временной) ссылкой на значение.например, тот, который возвращается из std::move).В противном случае он обрабатывается как простая старомодная ссылка lvalue, и затем должно произойти копирование , если , но, как обычно, компилятору разрешается оптимизировать его, оставляя вам один конструируемый объект и один объектбыть уничтоженным.

В любом случае, даже с семантикой перемещения, нет причины, по которой компилятор не должен делать то же самое: просто оптимизировать перемещение, как если бы он оптимизировал копирование.Перемещение дешево, но все же дешевле просто построить объект там, где он вам нужен, а не построить его, а затем переместить его в другое место и вызвать деструктор для первого.

Также стоит отметитьчто вы используете относительно старый компилятор, и в более ранних версиях спецификации было очень неясно, что должно происходить с этими "объектами-зомби".Так что возможно, что GCC 4.3 просто не вызывает деструктор.Я полагаю, что только последняя ревизия или, может быть, та, что была до нее, явно требует, чтобы деструктор был вызван

2 голосов
/ 03 ноября 2010

Обратите внимание, что компилятор может оптимизировать ненужную конструкцию / разрушение, эффективно создавая только один объект. Это особенно верно для ссылок rvalue (которые были изобретены именно для этой цели).

Я думаю, что вы ошибаетесь в своей интерпретации происходящего. Конструктор перемещения не вызывается: если значение не является временным, ссылка rvalue ведет себя как нормальная ссылка.

Может быть, эта статья предоставит больше информации о семантике ссылок rvalue.

...