Чому ми можемо використовувати `std :: move` на об’єкті` const`?


113

В C ++ 11 ми можемо написати цей код:

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

коли я дзвоню std::move, це означає, що я хочу перемістити об'єкт, тобто я зміню об'єкт. Рухати constоб’єкт нерозумно, тож чому б std::moveне обмежувати цю поведінку? Це буде пастка в майбутньому, правда?

Тут пастка означає, як Брендон згадував у коментарі:

"Я думаю, що він означає, що це" вловлює "його підлогою підлістю, тому що якщо він не усвідомлює, він закінчує копією, яка не є тим, що він задумав".

У книзі Скотта Меєрса "Ефективний сучасний C ++" він наводить приклад:

class Annotation {
public:
    explicit Annotation(const std::string text)
     : value(std::move(text)) //here we want to call string(string&&),
                              //but because text is const, 
                              //the return type of std::move(text) is const std::string&&
                              //so we actually called string(const string&)
                              //it is a bug which is very hard to find out
private:
    std::string value;
};

Якщо std::moveзаборонено працювати з constоб'єктом, ми могли б легко дізнатися про помилку, правда?


2
Але спробуйте перемістити його. Спробуйте змінити його стан. std::moveсам по собі нічого не робить об'єкту. Можна стверджувати std::move, погано названий.
juanchopanza

3
Це насправді нічого не рухає . Все, що вона робить, - це посилання на реальне значення. спробуйте CAT cat2 = std::move(cat);, припускаючи, що CATпідтримує регулярне призначення ходу.
WhozCraig

11
std::moveце лише акторський склад, насправді нічого не рухає
Red Alert

2
@WhozCraig: Обережно, оскільки опублікований вами код збирається та виконується без попередження, роблячи його таким чином оманливим.
Mooing Duck

1
@MooingDuck Ніколи не говорив, що не збирається. він працює лише тому, що увімкнено копіювальний код за замовчуванням. Покручіть це і колеса відпадуть.
WhozCraig

Відповіді:


55
struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

тут ми бачимо використання std::moveна T const. Він повертає a T const&&. У нас є конструктор ходу, strangeякий займає саме цей тип.

І це називається.

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

Але, з іншого боку, існуючі std::moveроботи краще в родовому коді, де ви не знаєте , якщо тип ви працюєте з є Tабо T const.


3
+1 за те, що це перша відповідь, яка насправді намагається пояснити, чому ви хочете зателефонувати std::moveна constоб’єкт.
Кріс Дрю

+1 для показу функції виконання функції const T&&. Це виражає "протокол API" типу "я прийму rvalue-ref, але обіцяю, що не зміню його". Я здогадуюсь, окрім використання мутабельних, це рідкість. Можливо, інший випадок використання - це можливість користуватися forward_as_tupleмайже будь-чим і пізніше використовувати його.
F Перейра

107

Тут є хитрість, яку ви не помічаєте, а саме std::move(cat) це насправді нічого не рухає . Це просто говорить компілятору спробувати рухатися. Однак, оскільки у вашого класу немає конструктора, який би приймав a const CAT&&, він замість цього використовуватиме const CAT&конструктор неявної копії та безпечно копіювати. Ні небезпеки, ні пастки. Якщо конструктор копій відключений з будь-якої причини, ви отримаєте помилку компілятора.

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

відбитки COPY, ні MOVE.

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

Зауважте, що помилка у згаданому вами коді - це проблема продуктивності , а не стабільність , тому така помилка ніколи не спричинить збоїв. Це просто використовувати більш повільну копію. Крім того, така помилка також виникає для об'єктів, що не мають const, у яких немає конструкторів переміщення, тому просто додавання constперевантаження не зможе наздогнати їх усіх. Ми можемо перевірити наявність здатності переміщувати конструкцію або переміщувати призначення з типу параметра, але це заважатиме загальному коду шаблону, який повинен потрапити назад в конструктор копій. І чорт, може, хтось хоче, щоб з нього можна було побудувати const CAT&&, хто я, щоб сказати, що не може?


Неприборканий. Варто відзначити неявне вилучення копіювального коду за визначеним користувачем визначенням звичайного конструктора переміщення або оператора присвоєння, також продемонструє це через розбиту компіляцію. Гарна відповідь.
WhozCraig

Також варто зазначити, що конструктор копій, який потребує constнеоціненого значення, також не допоможе. [class.copy] § 8: "В іншому випадку неявно задекларований конструктор копій матиме форму X::X(X&)"
Deduplicator

9
Я не думаю, що він мав на увазі "пастку" в комп'ютерному / складальному відношенні. Я думаю, що він означає, що це «вловлює» його підлогою підлістю, тому що якщо він цього не усвідомлює, він отримує копію, яка не є тим, що він задумав. Я здогадуюсь ..
Брендон

нехай ви отримаєте ще 1 підсумок, а я отримаю ще 4 за золотий знак. ;)
Якк - Адам Невраумон

Висока п'ять у цьому зразку коду. Діє бал по-справжньому добре.
Код Брент пише

20

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

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

Класно, тепер я можу відносно ефективно створити vector<string>з, deque<string>і кожен індивід stringбуде переміщений у процесі.

Але що робити, якщо я хочу перейти з map?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

Якщо б std::moveнаполягали на неаргументі const, вищенаведена інстанція move_eachне складеться, оскільки вона намагається перемістити a const int( key_typethe map). Але цей код не має значення, якщо він не може перемістити key_type. Він хоче перемістити mapped_type( std::string) з міркувань продуктивності.

Саме цей приклад і незліченна кількість інших прикладів, таких як загальне кодування, std::move- це запит на переміщення , а не вимога переміщення.


2

Я маю таке ж занепокоєння, як і ОП.

std :: move не переміщує об'єкт, не гарантує, що об’єкт є рухомим. Тоді чому це називається переміщенням?

Я думаю, що бути не рухомим може бути одним із наступних двох сценаріїв:

1. Рухомий тип - const.

Причиною, що у нас є ключове слово const в мові, є те, що ми хочемо, щоб компілятор не міг змінити об'єкт, визначений як const. Наведений приклад у книзі Скотта Майєрса:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     {  } // doesn't do what it seems to!    
     
    private:
     std::string value;
    };

Що це буквально означає? Перемістіть рядок const до члена значення - принаймні, це я розумію, перш ніж прочитати пояснення.

Якщо мова має намір не робити переміщення або не гарантувати переміщення, застосовне, коли викликається std :: move (), воно буквально вводить в оману при використанні слова word.

Якщо мова спонукає людей, які використовують std :: move, щоб мати кращу ефективність, вона повинна якомога раніше запобігати таким пасткам, особливо для цього типу очевидного буквального протиріччя.

Я погоджуюся, що люди повинні знати, що переміщення константи неможливо, але це зобов'язання не повинно означати, що компілятор може мовчати, коли відбувається очевидне протиріччя.

2. Об'єкт не має конструктора переміщення

Особисто я вважаю, що це окрема історія від стурбованості ОП, як сказав Кріс Дрю

@hvd Це здається мені трохи неаргументом. Тільки тому, що пропозиція ОП не виправляє всіх помилок у світі, не обов'язково означає, що це погана ідея (можливо, це є, але не з тієї причини, яку ви наводите). - Кріс Дрю


1

Я здивований, що ніхто не згадував про відсталий аспект сумісності цього. Я вважаю, std::moveбув розроблений спеціально для цього в C ++ 11. Уявіть, що ви працюєте зі застарілою кодовою базою, яка значною мірою покладається на бібліотеки C ++ 98, тож без резервного присвоєння копії переміщення може порушити справи.


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