Объекты зомби после std:: move

Я запутался в состоянии объекта после его перемещения с использованием семантики перемещения С++ 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!

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

  • Foo f1.
  • Foo f1 передается work, который принимает значение r f2.
  • Конструктор перемещения Foo равен вызванный, перемещая все ресурсы в f1 до f2.
  • Теперь вызывается деструктор f2, освобождая все ресурсы.
  • Теперь вызывается деструктор f1, который на самом деле ничего не делает поскольку все ресурсы были переданы до f2. Тем не менее, деструктор но тем не менее.

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

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

Ответ 1

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

Оригинальный ответ, сохраненный для потомков

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

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

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

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

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

Ответ 2

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

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

Возможно, в этой статье будет представлена ​​дополнительная информация о семантике ссылок rvalue.