Подтвердить что ты не робот

Это правильное использование семантики С++ "move"?

Сегодня я просматриваю код, над которым я работал последние несколько дней, и начал читать семантику перемещения, в частности 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 (спасибо, Aragorn), я заметил, что он называется явным конструктором перемещения. Почему существует один для класса строк, а не для вектора?

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

// 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;

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

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

Спасибо заранее!

4b9b3361

Ответ 1

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

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, прочитать это.

Ответ 2

return(theVector);

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

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

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

[...]

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

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

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

return theVector;

Ответ 3

Чтобы добавить к GMan ответ: даже если вы измените тип возвращаемого значения на std::vector<T> (без каких-либо ссылок, иначе вы получите UB), ваша замена выражения return в "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 - это локальный нестатический объект в возвращаемой функции, иначе вы не сможете использовать другую возможность оптимизации.

Ответ 4

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

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