int&& i = 42;
int j = 42;
int&& k = j;
Таким образом, функцию можно перегрузить в зависимости от того, являются параметры l -значениями или r -значениями, — один вариант будет принимать ссылку на l -значение, другой — на r -значение. Эта возможность — краеугольный камень семантики перемещения .
A.1.1. Семантика перемещения
r -значения — это обычно временные объекты, поэтому их можно спокойно модифицировать; если известно, что параметр функции — r-значение, то его можно использовать как временную память, то есть «позаимствовать» его содержимое без ущерба для корректности программы. Это означает, что вместо копирования параметра, являющегося r -значением, мы можем просто переместить его содержимое. В случае больших динамических структур это позволяет сэкономить на выделении памяти и оставляет простор для оптимизации.
Рассмотрим функцию, которая принимает в качестве параметра std::vector
и хочет иметь его внутреннюю копию для модификации, так чтобы не затрагивать оригинал. Раньше мы для этого должны были принимать параметр как const
-ссылку на l -значение и делать внутреннюю копию:
void process_copy(std::vector const& vec_) {
std::vector vec(vec_);
vec.push_back(42);
}
При этом функция может принимать как l -значения, так и r -значения, но копирование производится всегда. Однако, если добавить перегруженный вариант, который принимает ссылку на r -значение, то в этом случае можно будет избежать копирования, поскольку нам точно известно, что оригинал разрешается модифицировать:
void process_copy(std::vector&& vec) {
vec.push_back(42);
}
Если функция является конструктором класса, то можно умыкнуть содержимое r -значения и воспользоваться им для создания нового экземпляра. Рассмотрим класс, показанный в листинге ниже. В конструкторе по умолчанию он выделяет большой блок памяти, а в деструкторе освобождает его.
Листинг А.1.Класс с перемещающим конструктором
class X {
private:
int* data;
public:
X() : data(new int[1000000]) {}
~X() {
delete [] data;
}
X(const X& other) : ←
(1)
data(new int[1000000]) {
std::copy(other.data, other.data + 1000000, data);
}
X(X&& other) : ←
(2)
data(other.data) {
other.data = nullptr;
}
};
Копирующий конструктор (1)определяется как обычно: выделяем новый блок памяти и копируем в него данные. Но теперь у нас есть еще один конструктор, который принимает ссылку на r -значение (2). Это перемещающий конструктор . В данном случае мы копируем только указатель на данные, а в объекте other
остается нулевой указатель. Таким образом, мы обошлись без выделения огромного блока памяти и сэкономили время на копировании данных из r -значения.
В классе X
перемещающий конструктор — всего лишь оптимизация, но в ряде случаев такой конструктор имеет смысл определять, даже когда копирующий конструктор не предоставляется. Например, идея std::unique_ptr<>
в том и заключается, что любой ненулевой экземпляр является единственным указателем на свой объект, поэтому копирующий конструктор лишен смысла. Однако же перемещающий конструктор позволяет передавать владение указателем от одного объекта другому, поэтому std::unique_ptr<>
можно использовать в качестве возвращаемого функцией значения — указатель перемещается, а не копируется.
Чтобы явно переместить значение из именованного объекта, который больше заведомо не будет использоваться, мы можем привести его к типу r -значения либо с помощью static_cast
, либо путем вызова функции std::move()
:
X x1;
X x2 = std::move(x1);
X x3 = static_cast(x2);
Это особенно удобно, когда требуется переместить значение параметра в локальную переменную или переменную-член без копирования, потому что хотя параметр, являющийся ссылкой на r -значение, и может связываться с r -значениями, но внутри функции он трактуется как l -значение:
void do_stuff(X&& x_) {
X a(x_); ←
Копируется
X b(std::move(x_)); ←
Перемещается
} │
r -значение связывается
do_stuff(X());←┘
со ссылкой на r -значение
X x; │
Ошибка, l -значение нельзя связывать
do_stuff(x);←┘
со ссылкой на r -значение
Читать дальше