Как правильно передавать unique_ptr в конструктор или функцию?
Основные паттерны передачи std::unique_ptr
Ваше замешательство абсолютно нормально — это один из самых частых вопросов у новичков при переходе на умные указатели. Ключевой принцип здесь: выбор способа передачи зависит от того, кто будет владеть ресурсом после вызова.
Давайте разберем четыре основных сценария с примерами кода.
1. Передача владения (по значению с std::move)
Если функция должна стать новым владельцем ресурса (например, конструктор класса, который сохраняет указатель). Это ваш случай с MyClass:
class MyClass {
public:
// Явно принимаем по значению — вызывающий знает, что владение передается
MyClass(std::unique_ptr<int> ptr) : data(std::move(ptr)) {}
private:
std::unique_ptr<int> data;
};
auto p = std::make_unique<int>(42);
MyClass obj(std::move(p)); // p теперь пуст, владение у obj
// p.reset() не нужен, std::move сделал это
После вызова p гарантированно nullptr. Это ясный контракт для вызывающего.
2. Только чтение (передача по сырой ссылке или указателю)
Если функция не должна сохранять указатель, а только временно использовать данные — передавайте ссылку или сырой указатель. Это самый частый случай для функций, которые просто обрабатывают данные:
void processData(const int& value) {
std::cout << "Processing: " << value << std::endl;
}
void processDataPtr(const int* value) {
if (value) {
std::cout << "Processing: " << *value << std::endl;
}
}
auto p = std::make_unique<int>(42);
processData(*p); // Разыменовываем unique_ptr
processDataPtr(p.get()); // Получаем сырой указатель
// p остается владельцем!
Это безопасно: сырой указатель не означает владение, а просто "ссылку на объект, который кому-то принадлежит".
3. Условное владение (передача по rvalue-ссылке)
Иногда функция может опционально принять владение (например, в зависимости от условий). Используйте std::unique_ptr&&:
class Container {
public:
void adoptIfLarge(std::unique_ptr<int>&& ptr) {
if (ptr && *ptr > 100) {
data = std::move(ptr); // Забираем владение
}
// Если не забрали — ptr остается валидным у вызывающего
}
private:
std::unique_ptr<int> data;
};
auto p = std::make_unique<int>(50);
Container c;
c.adoptIfLarge(std::move(p)); // p не забрали, он все еще владеет числом 50
if (p) std::cout << "Still owner: " << *p << std::endl;
Обратите внимание: вызывающий обязан использовать std::move, но владение может и не перейти.
4. Модификация без передачи владения (по lvalue-ссылке)
Если нужно изменить сам unique_ptr (например, заменить его содержимое на другой объект), передавайте по неконстантной ссылке:
void resetToDefault(std::unique_ptr<int>& ptr) {
ptr = std::make_unique<int>(0); // Меняем владельца
}
auto p = std::make_unique<int>(42);
resetToDefault(p); // p теперь указывает на 0, без std::move
std::cout << *p << std::endl; // 0
Сводка правил
- По значению (std::unique_ptr
) — передача владения. Всегда используйтеstd::moveпри вызове. - По ссылке (T& или const T&) — только чтение данных. Самый частый и предпочтительный способ.
- По сырому указателю (T* или const T*) — альтернатива ссылке, если нужна проверка на nullptr.
- По rvalue-ссылке (std::unique_ptr
&&) — опциональное/условное принятие владения. - По lvalue-ссылке (std::unique_ptr
&) — модификация самого умного указателя.
Главное правило из C++ Core Guidelines (R.30): "Принимайте умные указатели как параметры только для выражения семантики владения. Для операций, не связанных с владением, используйте сырые указатели или ссылки."
<<Альтернативный взгляд: предпочитайте шаблонные функции
Иногда вместо передачи unique_ptr можно использовать шаблонный подход, особенно если функция не зависит от типа умного указателя. Это делает код более гибким и совместимым с разными типами владения:
template<typename T>
void processData(const T& value) {
std::cout << "Processing: " << value << std::endl;
}
auto p = std::make_unique<int>(42);
processData(*p); // Работает с unique_ptr
auto sp = std::make_shared<int>(100);
processData(*sp); // Работает и с shared_ptr
int raw = 7;
processData(raw); // И с простыми переменными
Этот паттерн особенно полезен в обобщенном коде. Однако помните: он требует, чтобы объект был разыменуем (оператор *). Если же вам нужно передать именно семантику владения, то без std::move не обойтись.
Еще один совет: старайтесь избегать передачи unique_ptr по константной ссылке (const std::unique_ptr<T>&). Это часто приводит к путанице: вызывающий думает, что владение не передается, но функция может вызвать ptr.get() и сохранить сырой указатель, что нарушает RAII. Используйте либо сырой указатель, либо явную передачу владения.
В целом, если вы сомневаетесь — начинайте с передачи по ссылке на объект (const T&). Это самый безопасный и понятный вариант для 90% случаев.
Ответов пока нет.
Войдите чтобы оставить ответ