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

С++ 11 Константа ссылки VS move semantics

Мне было интересно, в каких ситуациях мне все еще нужно использовать ссылки const в параметрах с С++ 11. Я не совсем понимаю семантику перемещения, но я думаю, что это законный вопрос. Этот вопрос предназначен только для ситуаций, когда ссылка на константу заменяет копию, когда требуется только "прочитать" значение (например, использование функций-членов-констант).

Обычно я бы написал функцию (member) следующим образом:

#include <vector>

template<class T>
class Vector {
    std::vector<T> _impl;
public:
    void add(const T& value) {
         _impl.push_back(value);
    }
};

Но я думаю, что с уверенностью предположить, что компилятор будет оптимизировать его с помощью семантики перемещения, если я напишу его так, а class T of the course реализует конструктор перемещения:

#include <vector>

template<class T>
class Vector {
    std::vector<T> _impl;
public:
    void add(T value) {
         _impl.push_back(value);
    }
};

Я прав? Если да, можно ли предположить, что он может использоваться в любой ситуации? Если нет, я хотел бы знать, что. Это значительно облегчило бы жизнь, так как мне не пришлось бы применять специализацию класса для фундаментальных типов, например, он выглядит намного чище.

4b9b3361

Ответ 1

Решение, которое вы предлагаете:

void add(T value) {
     _impl.push_back(value);
}

Требуется некоторая корректировка, так как вы всегда будете выполнять одну копию value, даже если вы передадите rvalue на add() (две копии, если вы передадите lvalue): поскольку value является lvalue, компилятор автоматически не переместится от него, когда вы передадите его как аргумент push_back.

Вместо этого вы должны сделать следующее:

void add(T value) {
     _impl.push_back(std::move(value));
//                   ^^^^^^^^^
}

Это лучше, но все еще недостаточно хорошо для кода шаблона, потому что вы не знаете, стоит ли T дешево или дорого двигаться. Если T является POD следующим образом:

struct X
{
    double x;
    int i;
    char arr[255];
};

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

Одним из возможных решений (принятым стандартной библиотекой С++) является предоставление двух перегрузок add(), один из которых принимает опорную ссылку lvalue, а другой - для ссылки rvalue:

void add(T const& val) { _impl.push_back(val); }
void add(T&& val) { _impl.push_back(std::move(val)); }

Еще одна возможность - предоставить версию шаблона версии add() (возможно, с ограничением SFINAE), которая будет принимать так называемую универсальную ссылку (нестандартный термин, придуманный Скоттом Мейерсом):

template<typename U>
void add(U&& val) { _impl.push_back(std::forward<U>(val)); }

Оба этих решения оптимальны в том смысле, что только одна копия выполняется, когда lvalues ​​предоставляются, и только один шаг выполняется, когда rvalues ​​предоставляются.