библиотека — управление памятьюlibrary — memory management

std::shared_ptr<T[]>

Полноценная поддержка массивов в shared_ptr — operator[] и корректный дефолтный delete[] вместо ручного custom deleter, которого требовала эта специализация с C++11 до C++17.Full array support in shared_ptr — operator[] and a correct default delete[] instead of the hand-written custom deleter this specialization required from C++11 until C++17.

исправление признанной асимметрии с unique_ptra fix for an acknowledged asymmetry with unique_ptr редко используется на практикеrarely used in practice

Что этоWhat it is

C++17 добавляет специализации std::shared_ptr<T[]> два недостающих куска функциональности: operator[] для доступа к элементам массива по индексу и корректный дефолтный deleter, который автоматически вызывает delete[] (а не обычный delete), когда shared_ptr инстанциирован для типа массива. Сама специализация shared_ptr<T[]> существовала с C++11, но до C++17 оба этих куска приходилось обеспечивать вручную.C++17 adds to the std::shared_ptr<T[]> specialization two missing pieces of functionality: an operator[] for indexed access to array elements, and a correct default deleter that automatically calls delete[] (rather than a plain delete) when shared_ptr is instantiated for an array type. The shared_ptr<T[]> specialization itself had existed since C++11, but before C++17 both of these pieces had to be provided by hand.

C++11-C++14 — ручной custom deleterC++11-C++14 — a hand-written custom deleter
// без custom deleter — UB: будет вызван delete вместо delete[]// without a custom deleter — UB: delete gets called instead of delete[]
std::shared_ptr<int[]> bad(new int[10]);

// корректно в C++11/14 — deleter вручную:// correct in C++11/14 — a deleter written by hand:
std::shared_ptr<int[]> arr(new int[10],
    [](int* p) { delete[] p; });

// arr[3] = 42;   — не компилировалось: operator[] отсутствовал// arr[3] = 42;   — didn't compile: operator[] didn't exist
C++17 — работает из коробкиC++17 — works out of the box
std::shared_ptr<int[]> arr(new int[10]);
arr[3] = 42;  // operator[] появился — компилируется и работает// operator[] now exists — compiles and works
// deleter по умолчанию сам вызывает delete[], не delete// the default deleter itself calls delete[], not delete

Зачем ввелиWhy it was introduced

01

unique_ptr<T[]> уже имел эту поддержку с C++11unique_ptr<T[]> already had this support since C++11

std::unique_ptr<T[]> с самого C++11 корректно вызывал delete[] и предоставлял operator[] — специализация была спроектирована полноценно сразу. Для shared_ptr<T[]> аналогичной заботы не случилось: специализация существовала формально, но без этих двух кусков функциональности, которые делают её действительно пригодной для массивов. Асимметрия между двумя умными указателями, решающими родственные задачи, была недосмотром.std::unique_ptr<T[]> had correctly called delete[] and provided an operator[] since C++11 — the specialization was designed properly from the start. No equivalent care happened for shared_ptr<T[]>: the specialization existed formally, but without the two pieces of functionality that make it actually usable for arrays. The asymmetry between two smart pointers solving related problems was an oversight.

02

Похоже на историю make_unique — признанный пробел, закрытый следующим стандартомSimilar to the make_unique story — an acknowledged gap closed by the next standard

Ситуация структурно повторяет историю std::make_unique: там make_shared появился в C++11, а его аналог для unique_ptr — только в C++14, как признанный самим комитетом недосмотр. Здесь наоборот — недоделанной оказалась специализация массива именно для shared_ptr, а не unique_ptr, но паттерн тот же: одна сторона API получила полноценную поддержку сразу, вторая — только следующим стандартом, как заплатка.The situation structurally mirrors the story of std::make_unique: there, make_shared appeared in C++11 while its unique_ptr counterpart arrived only in C++14, as a gap the committee itself acknowledged. Here it's the reverse — it was the array specialization for shared_ptr, not unique_ptr, that ended up unfinished, but the pattern is the same: one side of an API got full support right away, the other only got it as a patch in the next standard.

03

Ручной custom deleter был источником тихих ошибокThe hand-written custom deleter was a source of silent bugs

Забыть указать custom deleter при создании shared_ptr<T[]>(new T[n]) напрямую компилировалось, но приводило к undefined behavior при разрушении объекта (вызывался бы delete вместо delete[]) — ошибка, которую легко не заметить при код-ревью и которая проявляется только в рантайме, иногда далеко не сразу.Forgetting to specify a custom deleter when creating shared_ptr<T[]>(new T[n]) directly still compiled, but led to undefined behavior when the object was destroyed (a plain delete would be called instead of delete[]) — a mistake easy to miss in code review and one that only surfaces at runtime, sometimes long after the fact.

Как это работаетHow it works

#include <memory>

// C++17 — создание всё ещё через new T[n] напрямую, НЕ make_shared:// C++17 — creation is still via new T[n] directly, NOT make_shared:
std::shared_ptr<int[]> arr(new int[10]);

for (int i = 0; i < 10; ++i) {
    arr[i] = i * i;      // operator[] — новое в C++17// operator[] — new in C++17
}

// совместное владение работает как у обычного shared_ptr:// shared ownership works like an ordinary shared_ptr:
std::shared_ptr<int[]> arr2 = arr;   // refcount++, тот же буфер// refcount++, the same buffer
// delete[] вызовется автоматически, когда refcount дойдёт до нуля// delete[] fires automatically once the refcount reaches zero

Стандартная библиотека внутри различает специализацию для массива через частичную специализацию шаблона shared_ptr<T[]> (и shared_ptr<T[N]> для массива известного размера) — именно эта специализация в C++17 получила operator[] и связанный с ней element_type, а также логику деструктора, которая выбирает delete[] вместо delete для дефолтного deleter'а, ровно так, как это уже работало у unique_ptr<T[]>.Internally, the standard library distinguishes the array specialization via a partial specialization of the shared_ptr<T[]> template (and shared_ptr<T[N]> for a known-size array) — it's this specialization that gained operator[] and its associated element_type in C++17, along with destructor logic that picks delete[] over delete for the default deleter, exactly the way unique_ptr<T[]> already worked.

ГраблиGotchas

make_shared<T[]> появился только в C++20, не в C++17make_shared<T[]> only arrived in C++20, not C++17

Частая путаница стандартов: operator[] и корректный delete[] — это C++17, но std::make_shared<T[]>(n) для массивов стандартизирован только в C++20. В C++17 массив-shared_ptr всё ещё нужно создавать явно через shared_ptr<T[]>(new T[n]), а не через make_shared — компилятор с флагом -std=c++17 просто откажется собрать make_shared<T[]>.A common standard-version mix-up: operator[] and correct delete[] are C++17, but std::make_shared<T[]>(n) for arrays was only standardized in C++20. In C++17 an array shared_ptr still has to be created explicitly via shared_ptr<T[]>(new T[n]), not make_shared — a compiler set to -std=c++17 will simply refuse to build make_shared<T[]>.

Практическая нужда в shared_ptr<T[]> в принципе редкаThe practical need for shared_ptr<T[]> is rare to begin with

Для владения буфером в большинстве случаев лучше подходит std::vector (динамический размер, RAII, растущий) или std::array (фиксированный размер, никакой аллокации на куче) — shared_ptr<T[]> нужен только в узком случае, когда именно совместное владение сырым буфером фиксированного размера обязательно, а не просто "нужен массив с указателем".For owning a buffer, in most cases std::vector (dynamic size, RAII, growable) or std::array (fixed size, no heap allocation at all) fits better — shared_ptr<T[]> is only needed in the narrow case where shared ownership of a specifically raw, fixed-size buffer is actually required, not just "an array accessed via a pointer."

Размер массива не хранится в shared_ptr<T[]>The array size isn't stored inside shared_ptr<T[]>

Как и у unique_ptr<T[]>, размер массива нигде не хранится внутри самого умного указателя (кроме случая shared_ptr<T[N]> с размером, известным на этапе компиляции) — вызывающий код обязан отдельно передавать/хранить размер, если он нужен, иначе выход за границы массива через operator[] — такая же undefined behavior, как и для сырого указателя.Like unique_ptr<T[]>, the array size isn't stored anywhere inside the smart pointer itself (except for shared_ptr<T[N]>, where the size is known at compile time) — calling code must separately pass/store the size if it's needed, otherwise going out of bounds via operator[] is the same undefined behavior as with a raw pointer.