Это правильное использование семантики «перемещения» в C ++? - PullRequest
33 голосов
/ 10 ноября 2010

Сегодня я взглянул на код, над которым работал последние несколько дней, и начал изучать семантику перемещения, в частности, std :: move. У меня есть несколько вопросов к вам, профессионалы, чтобы убедиться, что я иду по правильному пути и не делаю глупых предположений!

Во-первых:

1) Изначально в моем коде была функция, которая возвращала большой вектор:

template<class T> class MyObject
{
public:
    std::vector<T> doSomething() const;
    {
        std::vector<T> theVector;

        // produce/work with a vector right here

        return(theVector);
    }; // eo doSomething
};  // eo class MyObject

Учитывая, что "theVector" является временным в этом и "выбросить", я изменил функцию на:

    std::vector<T>&& doSomething() const;
    {
        std::vector<T> theVector;

        // produce/work with a vector right here

        return(static_cast<std::vector<T>&&>(theVector));
    }; // eo doSomething

Это правильно? Есть ли подводные камни в этом?

2) Я заметил в функции, которая возвращает std::string, что она автоматически вызывает конструктор перемещения. Отладка в Return of String (спасибо, Арагорн), я заметил, что он называется явным конструктором перемещения. Почему есть класс строки, а не вектор?

Мне не пришлось вносить какие-либо изменения в эту функцию, чтобы воспользоваться семантикой перемещения:

// below, no need for std::string&& return value?
std::string AnyConverter::toString(const boost::any& _val) const
{
    string ret;
    // convert here
    return(ret); // No need for static_cast<std::string&&> ?
}; // eo toString

3) Наконец, я хотел сделать несколько тестов производительности, удивительно быстрые результаты, которые я получил из-за семантики std :: move, или мой компилятор (VS2010) тоже немного оптимизировал?

(реализация _getMilliseconds() для краткости опущена)

std::vector<int> v;
for(int a(0); a < 1000000; ++a)
    v.push_back(a);

std::vector<int> x;
for(int a(0); a < 1000000; ++a)
    x.push_back(a);

    int s1 = _getMilliseconds();
std::vector<int> v2 = v;
    int s2 =  _getMilliseconds();
std::vector<int> v3 = std::move(x);
    int s3 =  _getMilliseconds();

    int result1 = s2 - s1;
    int result2 = s3 - s2;

Результаты были, очевидно, потрясающими. Результат1, стандартное задание, занял 630мс. Второй результат был 0мс. Это хороший тест производительности этих вещей?

Я знаю, что кое-что из этого очевидно для многих из вас, но я хочу убедиться, что понимаю семантику прямо перед тем, как приступить к блейзеру в своем коде.

Заранее спасибо!

Ответы [ 4 ]

36 голосов
/ 10 ноября 2010

Ссылка все еще является ссылкой.Точно так же вы не можете вернуть ссылку на локальный в C ++ 03 (или вы получаете UB), вы не можете в C ++ 0x.Вы получите ссылку на мертвый объект;это просто случайная ссылка.Так что это неправильно:

std::vector<T>&& doSomething() const
{
    std::vector<T> local;

    return local; // oops
    return std::move(local); // also oops
}

Вы должны просто сделать то, что вы видели в номере два:

// okay, return by-value 
std::vector<T> doSomething() const
{
    std::vector<T> local;

    return local; // exactly the same as:
    return std::move(local); // move-construct value
}

Переменные, локальные для функции, являются временными, когда вы возвращаетесь, поэтому нет необходимостиизменить любой ваш код.Тип возврата - это то, что отвечает за реализацию семантики перемещения, а не вы.

Вы хотите использовать std::move в явном перемещении чего-либо, когда это не будет сделано нормально, как втвой тест(Что, кажется, хорошо; это было в Release? Вы должны вывести содержимое вектора, иначе компилятор его оптимизирует.)

Если вы хотите узнать о ссылках на rvalue, прочтите это.

14 голосов
/ 10 ноября 2010
return(theVector);

Это уже неявно перемещается из-за специального правила языка, потому что theVector является локальным объектом.См. Параграфы 34 и 35 раздела 12.8:

При выполнении определенных критериев реализация может опустить конструкцию копирования / перемещения объекта класса , даже если копия /Конструктор перемещения и / или деструктор для объекта имеют побочные эффекты.В таких случаях реализация рассматривает источник и цель пропущенной операции копирования / перемещения просто как два разных способа обращения к одному и тому же объекту, и уничтожение этого объекта происходит в более поздние времена, когда два объекта были быуничтожен без оптимизации.Такое исключение операций копирования / перемещения, называемое разрешением копирования, допускается в следующих обстоятельствах (которые могут быть объединены для удаления нескольких копий):

- в операторе return в функции с возвращаемым классомтип, когда выражение является именем энергонезависимого автоматического объекта с тем же типом cv-unqualified, что и тип возврата функции , операцию копирования / перемещения можно опустить, создав автоматический объект непосредственно в возвращаемое значение функции

[...]

Когда соблюдаются критерии исключения операции копирования и объект, подлежащий копированию, обозначается lvalue, разрешением перегрузки для выбора конструктора дляСначала копия выполняется так, как если бы объект был обозначен как r * .

Обратите внимание, что вы должны вернуть std::vector<T> ( по значению ), не a std::vector<T>&& ( по ссылке ).

Но почему скобка?return не является функцией:

return theVector;
7 голосов
/ 10 ноября 2010

Чтобы добавить к ответу GMan: даже если вы измените тип возвращаемого значения на std::vector<T> (без каких-либо ссылок, иначе вы получите UB), ваше изменение выражения возврата в «1)» никогда не улучшит производительность , но может сделать это немного хуже. Так как std::vector имеет конструктор перемещения и вы возвращаете локальный объект, конструктор копирования vector будет не вызываться, независимо от того, написали ли вы return theVector;, return static_cast<std::vector<T>&&>(theVector); или return std::move(theVector). В последних двух случаях компилятор будет вынужден вызывать конструктор перемещения. Но в первом случае у него есть свобода полностью оптимизировать движение, если он может выполнить NRVO для этой функции. Если по какой-либо причине NRVO невозможен, только тогда компилятор прибегнет к вызову конструктора перемещения. Поэтому не изменяйте return x; на return std::move(x);, если x является локальным нестатическим объектом в функции, из которой возвращается функция, иначе вы не позволите компилятору использовать другую возможность оптимизации.

5 голосов
/ 10 ноября 2010

Стандартный способ перемещения чего-либо - с std::move(x), а не static_cast. AFAIK, Оптимизация именованного возвращаемого значения, вероятно, вступит в силу с возвращением вектора по значению, поэтому она бы хорошо работала и до семантики перемещения.

Ваш тест производительности является хорошей иллюстрацией того, насколько семантика перемещения хороша для производительности: назначение копирования должно копировать миллион элементов, а назначение перемещения по сути просто меняет внутренние указатели вектора, что является тривиальным назначением слова или два, всего несколько циклов.

...