В контейнере хранится набор объектов с единственным владельцем. Определите, что произойдёт с владением ресурсами после перемещения контейнера:
#include <memory>
#include <vector>
int main() {
std::vector<std::unique_ptr<int>> source;
source.push_back(std::make_unique<int>(42));
auto target = std::move(source);
}
Кому принадлежат объекты после этой операции и что гарантируется о состоянии source?
После перемещения контейнера владение объектами переходит к target: каждый ресурс по-прежнему освобождается ровно одним std::unique_ptr, но уже находящимся в target. source остаётся корректным для разрушения и последующего присваивания, однако его точное состояние после перемещения не гарантируется; обычно он пуст, но полагаться на это без проверки нельзя.
Ручное хранение владельцев в контейнерах требовало явно писать циклы удаления, копирования и обработки исключений. Подход RAII и std::unique_ptr позволил связать владение ресурсом со временем жизни объекта-владельца и сделать контейнер безопасным при обычном выходе из области видимости и при исключениях.
Поддержка перемещения в C++11 решила отдельную проблему: объект, владеющий уникальным ресурсом, нельзя копировать, но его можно эффективно передать другому объекту без дублирования ресурса.
std::unique_ptr запрещает копирование, потому что копия создала бы двух владельцев одного ресурса. Поэтому копирование std::vector<std::unique_ptr<int>> также запрещено: контейнер не может скопировать свои элементы.
Перемещение, напротив, допустимо. Если после перемещения продолжить использовать source как будто он содержит прежние элементы, код может логически ошибиться: его состояние действительно, но содержимое не обязано сохраняться.
Выражение std::move(source) само по себе ничего не перемещает: оно лишь преобразует source к ссылке, допускающей перемещение. Затем конструктор target перемещает внутреннее состояние вектора. Для стандартного std::allocator это обычно означает передачу буфера целиком, без перемещения каждого std::unique_ptr по отдельности.
После операции target владеет int со значением 42. source находится в корректном, но неопределённом состоянии: его можно уничтожить, присвоить ему новое значение, а для операций, допускаемых контрактом типа, нельзя предполагать конкретный размер или состав. В распространённой реализации source.empty() возвращает true, но стандартная гарантия должна рассматриваться осторожнее.
Деструктор target уничтожит свои std::unique_ptr, а те вызовут delete для принадлежащих объектов. Деструктор source не должен освобождать эти объекты повторно: после корректного перемещения уникальное владение не дублируется.
Важное ограничение связано с аллокаторами: для некоторых операций перемещения контейнер может быть вынужден перемещать элементы по одному, если нельзя просто передать внутренний буфер. Это не меняет модель владения, но может изменить сложность операции и требования к перемещаемости элементов.
Сервис загружает несколько независимых обработчиков и хранит их в std::vector<std::unique_ptr<Handler>>. При передаче набора обработчиков в другой компонент возможны три решения.
Сырые указатели не выражают владение: потребуется отдельный договор об освобождении, а при исключении легко получить утечку. std::shared_ptr упростит копирование контейнера, но добавит подсчёт ссылок и позволит владеть обработчиками из нескольких мест, хотя такая семантика здесь не нужна.
Перемещение std::vector<std::unique_ptr<Handler>> явно передаёт единственное владение целиком. Это выбранное решение: оно не копирует обработчики, не создаёт общий контрольный блок и автоматически освобождает ресурсы при уничтожении нового владельца.
source после перемещения?Нельзя использовать элементы source с предположением, что они остались на месте. После перемещения сам контейнер валиден, но его содержимое не определено контрактом операции. Разрешены уничтожение, присваивание и другие операции, для которых тип гарантирует работу с объектом в перемещённом состоянии; практический код не должен строиться на том, что source обязательно пуст.
Копирующая операция вектора должна была бы скопировать каждый std::unique_ptr. У std::unique_ptr копирующий конструктор удалён, поскольку копирование нарушило бы единственность владения. Поэтому копирующий конструктор вектора с такими элементами также недоступен, хотя сам вектор можно переместить.
При перемещающем присваивании прежние элементы объекта-получателя сначала должны быть корректно обработаны: их std::unique_ptr уничтожаются или заменяются, поэтому принадлежащие им ресурсы освобождаются. Затем владение элементами исходного контейнера переходит получателю. Это отличается от перемещающего конструктора, где у нового объекта ещё нет собственных элементов.