@ryzhikovas

Почему после вызова t1 = std::move(t0) вызывается деструктор t0?

Есть класс:
class T {
private:
	int t;
public:
	T(){
		t = 1;
	}
	~T() {
		t = 0;
	}
	T(T&& right) :t(right.t) {
		right.t = 0;
	}
	T& operator=(T&& right) {
		t = right.t;
	}

	T(const T&) = delete;
	T& operator=(const T& right) = delete;
};


Он используется следующим образом:
...
T fn() {
T t;
return move(t);
}
...
T t1(fn());

Насколько я понял идею std::move, вызов в return move(t) должен осуществить перемещение данных класса t в t1 без вызова деструктора. На практике отладчик показал, что деструктор вызывается. Хотелось бы понять, почему так происходит.
Кроме того, по возможности, скажите, как изменить существующий реальный класс в этой ситуации. В реальной программе T содержит объект fstream, а в деструкторе осуществляется закрытие потока. При передаче владения объектом T (вызове move) закрывать файловый поток нельзя. Изменение поведения деструктора возможно, но это наименее приятное решение, т.к. в теряется основная идея - автоматическое освобождение ресурса при разрушении объекта. Использовать shared_ptr так же не хотелось бы.
  • Вопрос задан
  • 2664 просмотра
Решения вопроса 1
gbg
@gbg Куратор тега C++
Любые ответы на любые вопросы
По-хорошему, компилятор сам применит move-семантику при возврате значения.

Обсуждение на SO, смотрите абзац Best Practice.

Коротко: return move(x) эквивалентно return(x) при наличии конструктора перемещения.
А при включенной оптимизации у вас даже и перемещения не должно быть - у оптимизатора должно хватить мозгов сделать RVO (оптимизацию возвращаемого значения).

Мораль - у компиялторов есть опция (у gcc -S), которая заставляет генерировать не бинарный код, а ассемблерный листинг. Правильно изучать этот листинг, а не творчество отладчика. Потому как отладочная версия программы и оптимизированная - это разный код с разным поведением.
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
jcmvbkbc
@jcmvbkbc
"I'm here to consult you" © Dogbert
Насколько я понял идею std::move, вызов в return move(t) должен осуществить перемещение данных класса t в t1 без вызова деструктора.

Насколько я понимаю идею std::move, вы получаете вызов конструктора T(T&&) вместо T(const T&), в котором вы сами должны переместить (или не перемещать) нужные поля в создаваемый объект. После перемещения оригиналы полей нужно подходящим способом "обнулить". Деструктор локального объекта, выходящего из области видимости будет вызван в любом случае.

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

Насколько я вижу, std::fstream поддерживает конструирование из ссылки на rvalue.
Ответ написан
Комментировать
AxisPod
@AxisPod
А почему не должен? Конструируется новый объект, а старый при этом будет удалён, но move конструктор как бы намекает, что не нужно использовать глубокое копирование в конструкторе. Нужно просто скопировать указатели и всё, при этом да, в исходном объекте указатели нужно обнулить. А в вашем случае от move конструктора толку вообще нет и не будет. Ибо нет никакого смысла в move конструкторе для примитивных типов, которые при этом создаются на стеке.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы