Зачем бросать локальную переменную invokes move constructor?
Недавно я "играл" с rvalues, чтобы понять их поведение. Большинство результатов меня не удивили, но потом я увидел, что, если я выложу локальную переменную, вызывается конструктор перемещения.
До тех пор я думал, что целью правил семантики перемещения является гарантировать, что объект будет перемещаться (и становится недействительным) только в том случае, если компилятор может обнаружить, что он больше не будет использоваться (как в временных объектах), или пользователь обещает не использовать его (как в std:: move).
Однако в следующем коде не выполняется ни одно из этих условий, и моя переменная все еще перемещается (по крайней мере, на g++ 4.7.3).
Почему это?
#include <iostream>
#include <string>
using namespace std;
int main() {
string s="blabla";
try {
throw s;
}
catch(...) {
cout<<"Exception!\n";
}
cout<<s; //prints nothing
}
Ответы
Ответ 1
В данном случае это, вероятно, ошибка компилятора, поскольку после этого ссылается на переменную, которая была выбрана (и перемещена из нее).
В общем случае вызов move throw
концептуально совпадает с перемещением на return
. Хорошо вызывать движение автоматически, когда известно, что переменная не может быть указана после данной точки (throw
или return
).
Ответ 2
Стандарт С++ говорит (15.1.3):
Выбрасывание исключения copy-initializes (8.5, 12.8) временного объекта, называемого объектом исключения. Временной является значением lvalue и используется для инициализации переменной, указанной в соответствующем обработчике (15.3).
Этот пункт может быть также уместен здесь (12.8.31):
Когда определенные критерии выполняются, реализации разрешено опускать конструкцию копирования/перемещения объекта класса, даже если конструктор, выбранный для операции копирования/перемещения и/или деструктор объекта, имеет побочные эффекты. В таких случаях реализация рассматривает источник и цель пропущенной операции копирования/перемещения как просто два разных способа обращения к одному и тому же объекту и уничтожение этого объекта
происходит в более поздние времена, когда два объекта были бы уничтожены без оптимизации. Это разрешение операций копирования/перемещения, называемое копированием, разрешено в следующих случаях (которые могут быть объединены для устранения нескольких копий):
(...)
- в выражении throw, когда операндом является имя энергонезависимого автоматического объекта (отличного от параметра функции или параметра catch-clause) , объем которого не распространяется за пределы (если есть один), операция копирования/перемещения из операнда в объект исключения (15.1) может быть опущена путем создания автоматического объекта непосредственно в объект исключения
Проверено в Visual Studio 2012, эффект:
Exception!
blabla
Похоже на ошибку в GCC.