Мой вопрос: действительно ли нужен этот std::move? Я хочу сказать, что это p_name не используется в теле конструктора, так что, может быть, в языке есть какое-то правило использовать семантику перемещения для него по умолчанию?
Конечно нужен. p_name
— это lvalue, поэтому std::move
необходимо, чтобы превратить его в rvalue и выбрать конструктор перемещения.
Это не только то, что говорит язык — что, если тип будет таким:
struct Foo {
Foo() { cout << "ctor"; }
Foo(const Foo &) { cout << "copy ctor"; }
Foo(Foo &&) { cout << "move ctor"; }
};
Язык предписывает, что copy ctor
должно быть напечатано, если вы пропустите ход. Здесь нет вариантов. Компилятор не может сделать это по-другому.
Да, исключение копирования по-прежнему применяется. Но не в вашем случае (список инициализации), см. комментарии.
Или ваш вопрос касается почему мы используем этот шаблон?
Ответ заключается в том, что он обеспечивает безопасный шаблон, когда мы хотим сохранить копию переданного аргумента, при этом извлекая выгоду из перемещений и избегая комбинаторного взрыва аргументов.
Рассмотрим этот класс, который содержит две строки (т.е. два тяжелых объекта для копирования).
struct Foo {
Foo(string s1, string s2)
: m_s1{s1}, m_s2{s2} {}
private:
string m_s1, m_s2;
};
Итак, давайте посмотрим, что происходит в различных сценариях.
Возьмите 1
string s1, s2;
Foo f{s1, s2}; // 2 copies for passing by value + 2 copies in the ctor
Аргх, это плохо. Здесь происходит 4 копии, когда действительно нужны только 2. В C++03 мы бы немедленно превратили аргументы Foo() в константные ссылки.
Возьмите 2
Foo(const string &s1, const string &s2) : m_s1{s1}, m_s2{s2} {}
Теперь у нас есть
Foo f{s1, s2}; // 2 copies in the ctor
Это намного лучше!
Но как насчет ходов? Например, из временных:
string function();
Foo f{function(), function()}; // still 2 copies in the ctor
Или при явном перемещении lvalue в ctor:
Foo f{std::move(s1), std::move(s2)}; // still 2 copies in the ctor
Это не очень хорошо. Мы могли бы использовать команду перемещения string
для непосредственной инициализации членов Foo
.
Возьмите 3
Итак, мы могли бы ввести некоторые перегрузки для конструктора Foo:
Foo(const string &s1, const string &s2) : m_s1{s1}, m_s2{s2} {}
Foo(string &&s1, const string &s2) : m_s1{std::move(s1)}, m_s2{s2} {}
Foo(const string &s1, string &s2) : m_s1{s1}, m_s2{std::move(s2)} {}
Foo(string &&s1, string &&s2) : m_s1{std::move(s1)}, m_s2{std::move(s2)} {}
Итак, хорошо, теперь у нас есть
Foo f{function(), function()}; // 2 moves
Foo f2{s1, function()}; // 1 copy + 1 move
Хорошо. Но, черт возьми, мы получаем комбинаторный взрыв: каждый аргумент теперь должен появляться в своих вариантах const-ref + rvalue. Что, если мы получим 4 строки? Мы собираемся написать 16 ктор?
Возьми 4 (хороший)
Вместо этого давайте посмотрим на:
Foo(string s1, string s2) : m_s1{std::move(s1)}, m_s2{std::move(s2)} {}
С этой версией:
Foo foo{s1, s2}; // 2 copies + 2 moves
Foo foo2{function(), function()}; // 2 moves in the arguments + 2 moves in the ctor
Foo foo3{std::move(s1), s2}; // 1 copy, 1 move, 2 moves
Поскольку ходы чрезвычайно дешевы, этот шаблон позволяет в полной мере использовать их и избежать комбинаторного взрыва. Мы действительно можем спуститься вниз.
И я даже не коснулся поверхности безопасности исключений.
В рамках более общего обсуждения давайте теперь рассмотрим следующий фрагмент, в котором все задействованные классы делают копию s путем передачи по значению:
{
// some code ...
std::string s = "123";
AClass obj {s};
OtherClass obj2 {s};
Anotherclass obj3 {s};
// s won't be touched any more from here on
}
Если я вас правильно понял, вам бы очень хотелось, чтобы компилятор фактически сдвинулся на s
при последнем использовании:
{
// some code ...
std::string s = "123";
AClass obj {s};
OtherClass obj2 {s};
Anotherclass obj3 {std::move(s)}; // bye bye s
// s won't be touched any more from here on.
// hence nobody will notice s is effectively in a "dead" state!
}
Я сказал вам, почему компилятор не может этого сделать, но я понял вашу точку зрения. С определенной точки зрения это имело бы смысл — бессмысленно заставлять s
жить дольше, чем его последнее использование. Думаю, пища для размышлений о C++2x.
person
peppe
schedule
29.05.2014
std::move
, чтобы превратить их в rvalue и выбрать правильные перегрузки конструктора. См. это ">похожая запись. Компилятор может оптимизировать по правилу «как будто». Маловероятно, что он сделает это, изменив lvalue на rvalue! - person juanchopanza   schedule 29.05.2014