Насколько я понимаю, в C ++ 11, когда вы возвращаете локальную переменную из функции по значению, компилятору разрешается обрабатывать эту переменную как ссылку на r-значение и «перемещать» ее из функции для ее возврата (если RVO / NRVO не происходит вместо этого, конечно).
Мой вопрос, не может ли это сломать существующий код?
Рассмотрим следующий код:
#include <iostream>
#include <string>
struct bar
{
bar(const std::string& str) : _str(str) {}
bar(const bar&) = delete;
bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
void print() {std::cout << _str << std::endl;}
std::string _str;
};
struct foo
{
foo(bar& b) : _b(b) {}
~foo() {_b.print();}
bar& _b;
};
bar foobar()
{
bar b("Hello, World!");
foo f(b);
return std::move(b);
}
int main()
{
foobar();
return EXIT_SUCCESS;
}
Я думал, что деструктор локального объекта мог бы ссылаться на объект, который неявно перемещается, и, следовательно, неожиданно видеть «пустой» объект. Я пытался проверить это (см. Http://ideone.com/ZURoeT ), но я получил «правильный» результат без явного std::move
в foobar()
. Я предполагаю, что это из-за NRVO, но я не пытался изменить код, чтобы отключить это.
Правильно ли я в том, что это преобразование (вызывающее выход из функции) происходит неявно и может нарушить существующий код?
ОБНОВЛЕНИЕ Вот пример, который иллюстрирует то, о чем я говорю. Следующие две ссылки относятся к одному и тому же коду. http://ideone.com/4GFIRu - C ++ 03 http://ideone.com/FcL2Xj - C ++ 11
Если вы посмотрите на вывод, он другой.
Итак, я полагаю, что теперь возникает вопрос, учитывался ли этот вопрос при добавлении неявного перемещения в стандарт, и было решено, что можно было бы добавить это критическое изменение, так как этот вид кода достаточно редок? Мне также интересно, предупредит ли какой-либо компилятор в таких случаях ...
Ответы:
Скотт Мейерс написал в comp.lang.c ++ (август 2010 г.) о проблеме, при которой неявная генерация конструкторов перемещения может нарушить инварианты класса C ++ 03:
Здесь проблема в том, что в C ++ 03
X
был инвариант, чтобы егоv
член всегда имел 5 элементов.X::~X()
рассчитывал на этот инвариант, но недавно введенный конструктор перемещения переместился изv
, тем самым установив его длину на ноль.Это связано с вашим примером, поскольку нарушенный инвариант обнаруживается только в
X
деструкторе (как вы говорите, деструктор локального объекта может ссылаться на объект, который неявно перемещается, и, следовательно, неожиданно видит пустой объект).C ++ 11 пытается достичь баланса между нарушением части существующего кода и предоставлением полезных оптимизаций на основе конструкторов перемещения.
Первоначально Комитет решил, что конструкторы перемещения и операторы присваивания перемещения должны генерироваться компилятором, если он не предоставлен пользователем.
Затем решил, что это действительно повод для тревоги, и он ограничил автоматическую генерацию конструкторов перемещения и операторов назначения перемещения таким образом, что вероятность того, что существующий код будет поврежден (например, явно заданный деструктор), значительно ниже, хотя и не невозможна.
Соблазнительно думать, что предотвращения генерации неявных конструкторов перемещения, когда присутствует определяемый пользователем деструктор, достаточно, но это не так ( N3153 - Неявное перемещение должно идти для получения дополнительной информации).
В N3174 - двигаться или не двигаться Stroupstrup говорит:
источник