Переміщення семантики в C ++ - Переміщення-повернення локальних змінних


11

Я розумію, що в 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

Якщо ви подивитеся на вихід, то це інакше.

Отже, я думаю, це питання зараз стає, чи враховувалося це при додаванні неявного переходу до стандарту, і було вирішено, що було б правильно додати цю переломну зміну, оскільки такий тип коду досить рідкісний? Мені також цікаво, чи будь-які компілятори будуть попереджати у таких випадках ...


Рух завжди повинен залишати об’єкт у руйнуваному стані.
Зан Лінкс

Так, але це не питання. Код Pre-c ++ 11 міг припустити, що значення локальної змінної не зміниться просто через повернення її, тому цей неявний хід може порушити це припущення.
Bwmat

Ось що я намагався з’ясувати на своєму прикладі; за допомогою деструкторів ви можете перевірити стан (підмножини) локальних змінних функції "після" виконання оператора return, але до того, як функція фактично повернеться.
Bwmat

Це відмінне запитання із прикладом, який ви додали. Я сподіваюся, що це отримає більше відповідей від професіоналів, які можуть це з’ясувати. Єдиний реальний відгук, який я можу дати: це тому, що об'єкти зазвичай не мають власних поглядів на дані. Насправді існує багато способів написання невинно виглядаючого коду, який проводиться за допомогою сегфаултів, коли ви надаєте об'єктам, що не мають власних поглядів (необроблені покажчики чи посилання). Я можу сказати про це правильною відповіддю, якщо ви хочете, але я здогадуюсь, що це не те, про що ви хочете насправді почути. І до речі, вже відомо, що 11 може порушити існуючий код, наприклад, визначивши нові ключові слова.
Нір Фрідман

Так, я знаю, що C ++ 11 ніколи не стверджував, що не порушує жодного старого коду, але це досить тонко, і його було б дуже легко пропустити (жодних помилок компілятора, попереджень, segfaults)
Bwmat

Відповіді:


8

Скотт Майєрс опублікував на comp.lang.c ++ (серпень 2010 р.) Про проблему, коли неявне покоління конструкторів рухів може зламати інваріанти класу C ++ 03:

struct X
{
  // invariant: v.size() == 5
  X() : v(5) {}

  ~X() { std::cout << v[0] << std::endl; }

private:    
  std::vector<int> v;
};

int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

Тут проблема полягає в тому, що в C ++ 03 Xбув інваріант, що його vчлен завжди мав 5 елементів. X::~X()розраховували на цей інваріант, але нещодавно введений конструктор переміщення перемістився з v, тим самим встановивши його довжину до нуля.

Це пов'язано з вашим прикладом, оскільки зламаний інваріант виявляється лише в Xдеструкторі s (як ви говорите, деструктор локального об'єкта може посилатися на об'єкт, який неявно переміщується, і тому несподівано бачить порожній об’єкт).

C ++ 11 намагаються досягти балансу між порушенням деякого існуючого коду та наданням корисних оптимізацій на основі конструкторів переміщення.

Спочатку Комітет вирішив, що конструктори переміщення та оператори присвоєння переміщення повинні створюватися компілятором, коли користувач не надає їх.

Тоді він вирішив, що це справді викликає тривогу, і це обмежило автоматичну генерацію конструкторів переміщення та операторів присвоєння переміщення таким чином, що набагато рідше, хоча і не неможливо, існуючий код зламається (наприклад, чітко визначений деструктор).

Заманливо думати, що запобігання генерації неявних конструкторів переміщення, коли присутній визначений користувачем деструктор, достатньо, але це неправда ( N3153 - Implicit Move Must Go для подальших деталей).

У N3174 - Переміщувати чи не рухати Stroupstrup говорить:

Я вважаю це проблемою дизайну мови, а не простою проблемою сумісності на зворотному рівні. Легко уникнути зламу старого коду (наприклад, просто видалити переміщення операцій з C ++ 0x), але я бачу зробити C ++ 0x кращою мовою, зробивши операції переміщення всебічно важливими цілями, для яких, можливо, варто зламати деякі C + +98 код.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.