Ответ
std::unique_ptr реализует исключительное владение (exclusive ownership). Ресурсом владеет ровно один unique_ptr в каждый момент времени. Копирование запрещено, перемещение — разрешено. Это легковесный указатель с нулевыми накладными расходами (по размеру и производительности сравним с сырым указателем).
std::shared_ptr реализует разделяемое владение (shared ownership) через подсчет ссылок. Ресурс уничтожается, когда удаляется последний shared_ptr, ссылающийся на него. Поддерживает копирование.
Сравнение:
| Критерий | std::unique_ptr<T> |
std::shared_ptr<T> |
|---|---|---|
| Владение | Эксклюзивное | Разделяемое |
| Накладные расходы | Минимальные (указатель) | Указатель + контрольный блок со счетчиком ссылок |
| Копирование | Запрещено | Разрешено (инкрементирует счетчик) |
| Перемещение | Разрешено (передача владения) | Разрешено |
Примеры:
// unique_ptr: фабричная функция возвращает объект, которым должен владеть только вызывающий.
std::unique_ptr<MyClass> createResource() {
return std::make_unique<MyClass>();
}
auto resource = createResource(); // Владение передано в resource
// auto resource2 = resource; // Ошибка компиляции: нельзя копировать
auto resource2 = std::move(resource); // OK: владение передано resource2, resource теперь nullptr
// shared_ptr: несколько компонентов системы используют один кэшированный ресурс.
auto cachedConfig = std::make_shared<Config>();
auto processor1 = cachedConfig; // Оба shared_ptr владеют одним объектом Config
auto processor2 = cachedConfig; // Счетчик ссылок = 3
processor1.reset(); // Счетчик ссылок = 2
// Объект Config будет уничтожен, когда выйдут из области видимости cachedConfig и processor2.
Важные замечания:
- Циклические ссылки:
shared_ptrмогут создавать циклические зависимости, приводящие к утечкам памяти. Для их разрыва используетсяstd::weak_ptr. - Правило: Всегда предпочитайте
unique_ptrкак более простой и эффективный инструмент. Используйтеshared_ptrтолько тогда, когда семантика разделяемого владения действительно необходима.
Ответ 18+ 🔞
Давай разберем эту тему, как будто объясняю за рюмкой чая, а не на лекции по квантовой механике. Сиди, слушай.
Представь, что у тебя есть машина. std::unique_ptr — это как будто ты один-единственный владелец. Ключи только у тебя, доверия ебать ноль. Ты можешь отдать ключи другу (переместить), но тогда у тебя их уже не будет. А вот скопировать ключи и дать копию кому-то ещё — хуй тебе, а не копия. Компилятор тебе этого не позволит. Это легковесная штука, накладных расходов — как с сырым указателем, только безопаснее, потому что когда ты выходишь из тачки (указатель разрушается), она сама, блядь, на свалку едет (память освобождает). Удобно.
А вот std::shared_ptr — это уже общага. Тачка одна, а рулят ей все, кому не лень. У каждого по ключу (копия указателя). Пока хоть один ключ на руках — тачка никуда не денется. Как только последний чувак выкинул свой ключ в урну — всё, тачке пиздец, её утилизируют. За эту идиллию приходится платить: за каждым таким указателем тянется хвост — «контрольный блок», где сидит счетчик, кто сколько ключей на руках имеет. Тяжеловато, но иногда без этого никак.
Короче, табличка для тех, кто любит структуру:
| Что сравниваем | std::unique_ptr<T> |
std::shared_ptr<T> |
|---|---|---|
| Кто хозяин? | Один царь и бог. Эксклюзив. | Коммуналка. Всё общее. |
| Сколько весит? | Почти ничего. Как голый указатель. | Указатель + целый блок управления с счетчиком. Овердохуища метаданных. |
| Можно скопировать? | Ни в коем случае. Копирование запрещено нахуй. | Да запросто. Счетчик ссылок подкрутится и всё. |
| Можно передать? | Да, но только перемещением. Старый владелец после этого — пустое место. | Да, хоть копированием, хоть перемещением. |
Примеры из жизни, то есть кода:
// unique_ptr: Ты сходил в магазин, купил хлеб. Он теперь только твой.
std::unique_ptr<MyClass> createResource() {
return std::make_unique<MyClass>(); // Испекли булку
}
auto myBread = createResource(); // Владение передано тебе. Ты ешь.
// auto friendBread = myBread; // Ошибка! Хуй тебе, а не половина буханки. Не делится.
auto friendBread = std::move(myBread); // Ок, отдал всю булку другу. У тебя теперь пусто (nullptr).
// shared_ptr: Вы с соседом по общаге купили одну бутылку колы на двоих.
auto sharedCola = std::make_shared<Config>(); // Купили.
auto neighbor1 = sharedCola; // Сосед взял свою кружку.
auto neighbor2 = sharedCola; // Его девушка тоже хочет. Счетчик ссылок = 3.
neighbor1.reset(); // Первый сосед ушел на пары. Счетчик = 2.
// Бутылка будет стоять, пока sharedCola и neighbor2 не решат, что пить больше не хотят.
Важные нюансы, про которые забывают, а потом охуевают:
- Циклические ссылки — это пиздец. Представь, что
shared_ptrА указывает на Б, аshared_ptrБ указывает на А. Они друг друга держат, как два пьяных друга, и никто никогда не упадет. Утечка памяти гарантирована. Для таких хитрожопых случаев естьstd::weak_ptr— он как слабенькая ссылочка, которая не мешает объекту умереть, когда пора. - Главное правило, ёпта: Если нет явной, острой необходимости делить один объект между двадцатью потоками — бери
unique_ptr. Он проще, быстрее и не выест тебе мозг циклами.shared_ptr— это тяжёлая артиллерия, а не молоток для всего подряд. Используй его только когда семантика разделяемого владения реально нужна, а не «на всякий случай, авось пригодится».