Если функция является конструктором класса, то можно умыкнуть содержимое
Листинг А.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;
}
};
other
остается нулевой указатель. Таким образом, мы обошлись без выделения огромного блока памяти и сэкономили время на копировании данных из
В классе X
перемещающий конструктор — всего лишь оптимизация, но в ряде случаев такой конструктор имеет смысл определять, даже когда копирующий конструктор не предоставляется. Например, идея std::unique_ptr<>
в том и заключается, что любой ненулевой экземпляр является единственным указателем на свой объект, поэтому копирующий конструктор лишен смысла. Однако же перемещающий конструктор позволяет передавать владение указателем от одного объекта другому, поэтому std::unique_ptr<>
можно использовать в качестве возвращаемого функцией значения — указатель перемещается, а не копируется.
Чтобы явно переместить значение из именованного объекта, который больше заведомо не будет использоваться, мы можем привести его к типу static_cast
, либо путем вызова функции std::move()
:
X x1;
X x2 = std::move(x1);
X x3 = static_cast
Это особенно удобно, когда требуется переместить значение параметра в локальную переменную или переменную-член без копирования, потому что хотя параметр, являющийся ссылкой на
void do_stuff(X&& x_) {
X a(x_); ←
Копируется
X b(std::move(x_)); ←
Перемещается
} │
do_stuff(X());←┘
со ссылкой на
X x; │
Ошибка,
do_stuff(x);←┘
со ссылкой на
Семантика перемещения сплошь и рядом используется в библиотеке Thread Library — и в случаях, когда копирование не имеет смысла, но сами ресурсы можно передавать, и как оптимизация, чтобы избежать дорогостоящего копирования, когда исходный объект все равно будет уничтожен. Один пример мы видели в разделе 2.2, где std::move()
использовалась для передачи экземпляра std::unique_ptr<>
новому потоку, а второй — в разделе 2.3, когда рассматривали передачу владения потоком от одного объекта std::thread
другому.
Ни один из классов std::thread
, std::unique_lock<>
, std::future<>
, std::promise<>
, std::packaged_task<>
не допускает копирования, но в каждом из них имеется перемещающий конструктор, который позволяет передавать ассоциированный ресурс другому экземпляру и возвращать объекты этих классов из функций. Объекты классов std::string
и std::vector<>
можно копировать, как и раньше, но дополнительно они обзавелись перемещающими конструкторами и перемещающими операторами присваивания, чтобы избежать копирования данных из
Стандартная библиотека С++ никогда не делает ничего с объектом, который был явно перемещён в другой объект, кроме его уничтожения или присваивания std::thread
, содержимое которого перемещено, эквивалентен объекту std::thread
, сконструированному по умолчанию, а экземпляр std::string
, бывший источником перемещения, все же находится в согласованном состоянии, хотя не дается никаких гарантий относительно того, что это за состояние (в терминах длины строки или содержащихся в ней символов).
А.1.2. Ссылки на