Безопасно ли передавать в качестве аргумента член объекта, который движется

#include <iostream>
#include <string>
#include <map>

struct A {
    int n { 42 };
    std::string s { "ciao" };
};

int main() {
    A a;
    std::map<std::string, A> m;
    std::cout << "a.s: " << a.s << std::endl; // print: "a.s: ciao"
    m.emplace(a.s, std::move(a)); // a.s is a member of a, moved in the same line
    std::cout << "in map: " << m.count("ciao") << std::endl; // print: "in map: 1"
    std::cout << "a.s: " << a.s << std::endl; // print: "a.s: " (as expected, it has been moved)
}

Безопасно ли передавать в качестве аргумента член "движущегося" объекта? В этом случае emplace, кажется, работает: карта имеет ожидаемый ключ.

7 голосов | спросил Alessandro Pezzato 3 WedEurope/Moscow2014-12-03T11:21:06+03:00Europe/Moscow12bEurope/MoscowWed, 03 Dec 2014 11:21:06 +0300 2014, 11:21:06

2 ответа


0

Интересно. Я думаю, что это безопасно по запутанным причинам. (Кстати, я также считаю это очень плохим стилем - явная копия здесь ничего не стоит, поскольку она будет перемещена в карту.)

Прежде всего, фактический вызов функции не является проблемой. std::move только a к ссылке на rvalue и rvalue ссылки являются просто ссылками; не сразу перемещается. emplace_back перенаправляет свои параметры в конструктор std::pair<std::string, A> и вот тут все становится интересно.

Итак, какой конструктор std::pair используется? Это довольно много, но два имеют отношение:

pair(const T1& x, const T2& y);
template<class U, class V> pair(U&& x, U&&y);

(см. 20.3.2 в стандарте), где T1 и T2 - это аргументы шаблона std::pair. Согласно пункту 13.3, мы в конечном итоге получаем U == const T1& и V == T2, что имеет интуитивный смысл (в противном случае переход в std::pair был бы практически невозможен). Это оставляет нас с конструктором вида

pair(const T1& x, T2 &&y) : first(std::forward(x)), second(std::forward(y)) { }

согласно 20.3.2 (6-8).

Итак, это безопасно? Полезно, что std::pair определено довольно подробно, включая расположение памяти. В частности, говорится, что

T1 first;
T2 second;

идут в таком порядке, поэтому first будет инициализирован до second. Это означает, что в вашем конкретном случае строка будет скопирована до ее удаления, и вы в безопасности.

Однако, если бы вы делали это наоборот:

m.emplace(std::move(A.s), A); // huh?

... тогда вы получите забавные эффекты.

ответил Wintermute 3 WedEurope/Moscow2014-12-03T11:59:36+03:00Europe/Moscow12bEurope/MoscowWed, 03 Dec 2014 11:59:36 +0300 2014, 11:59:36
0

Что происходит, когда вы вызываете m.emplace(a.s, std::move(a));, так это то, что вы передаете ссылку на l-значение в a.s и ссылка на r-значение для a для функции emplace. Насколько это безопасно, зависит от реализации этой функции. Если он сначала вырвет кишки из второго аргумента, а затем попытается использовать первый аргумент, у вас, вероятно, возникнет проблема. Если он сначала использует первый аргумент, а затем вырывает внутренности из второго аргумента, нет проблем. Поскольку это стандартная библиотечная функция, вы не можете полагаться на реализацию, поэтому мы не можем сделать вывод, что код безопасен.

Для обеспечения безопасности вы можете убедиться, что копия строки сделана до того, как содержимое строки будет перемещено из a.

m.emplace(std::string(a.s), std::move(a));

Теперь вы передаете ссылку на r-значение во временную копию a.s и ссылку на r в a к функции emplace. Так что в этом случае это безопасно, потому что std::map::emplace принимает набор универсальных ссылок. Если вы сделаете то же самое с функцией, которая получает второй аргумент по значению, это небезопасно, поскольку конструктор перемещения может быть вызван перед копией a.s сделано (поскольку порядок вычисления аргументов функции не указан). Зная это, этот код, вероятно, слишком умен, чтобы его поддерживать.

Итак, самый понятный код выглядит примерно так:

auto key = a.s;
m.emplace(std::move(key), std::move(a));
ответил D Drmmr 3 WedEurope/Moscow2014-12-03T11:59:55+03:00Europe/Moscow12bEurope/MoscowWed, 03 Dec 2014 11:59:55 +0300 2014, 11:59:55

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

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132