Ответ 1
Принято здесь.
Большинство шаблонов в стандартной библиотеке С++ требуют, чтобы они были созданы с использованием всех типов. Однако shared_ptr
и unique_ptr
являются частичными исключениями. Некоторые, но не все их члены могут быть созданы с неполными типами. Мотивация заключается в том, чтобы поддерживать идиомы, такие как pimpl с помощью интеллектуальных указателей и без риска поведения undefined.
Undefined поведение может возникать, когда у вас есть неполный тип, и вы вызываете delete
на нем:
class A;
A* a = ...;
delete a;
Вышеупомянутый законный код. Он будет компилироваться. Ваш компилятор может или не может выдавать предупреждение для вышеуказанного кода, как указано выше. Когда он исполнится, вероятно, произойдут плохие вещи. Если вам повезет, ваша программа выйдет из строя. Однако более вероятным результатом является то, что ваша программа будет без проблем протекать в памяти, поскольку ~A()
не будет вызываться.
Использование auto_ptr<A>
в приведенном выше примере не помогает. Вы по-прежнему получаете такое же поведение undefined, как если бы вы использовали необработанный указатель.
Тем не менее, использование неполных классов в определенных местах очень полезно! Здесь shared_ptr
и unique_ptr
помогают. Использование одного из этих интеллектуальных указателей позволит вам уйти с неполным типом, за исключением случаев, когда необходимо иметь полный тип. И самое главное, когда необходимо иметь полный тип, вы получаете ошибку времени компиляции, если вы пытаетесь использовать интеллектуальный указатель с неполным типом в этой точке.
Не более undefined поведение:
Если ваш код компилируется, тогда вы использовали полный тип везде, где вам нужно.
class A
{
class impl;
std::unique_ptr<impl> ptr_; // ok!
public:
A();
~A();
// ...
};
shared_ptr
и unique_ptr
требуется полный тип в разных местах. Причины неясно, что связано с динамическим делетером и статическим делетером. Точные причины не важны. Фактически, в большинстве кодексов вам не обязательно знать, где именно требуется полный тип. Просто код, и если вы ошибетесь, компилятор скажет вам.
Однако, если это вам полезно, вот таблица, которая документирует несколько членов shared_ptr
и unique_ptr
в отношении требований полноты. Если член требует полного типа, тогда запись имеет "C", иначе запись в таблице заполняется "I".
Complete type requirements for unique_ptr and shared_ptr
unique_ptr shared_ptr
+------------------------+---------------+---------------+
| P() | I | I |
| default constructor | | |
+------------------------+---------------+---------------+
| P(const P&) | N/A | I |
| copy constructor | | |
+------------------------+---------------+---------------+
| P(P&&) | I | I |
| move constructor | | |
+------------------------+---------------+---------------+
| ~P() | C | I |
| destructor | | |
+------------------------+---------------+---------------+
| P(A*) | I | C |
+------------------------+---------------+---------------+
| operator=(const P&) | N/A | I |
| copy assignment | | |
+------------------------+---------------+---------------+
| operator=(P&&) | C | I |
| move assignment | | |
+------------------------+---------------+---------------+
| reset() | C | I |
+------------------------+---------------+---------------+
| reset(A*) | C | C |
+------------------------+---------------+---------------+
Для любых операций, требующих преобразования указателей, требуются полные типы для unique_ptr
и shared_ptr
.
Конструктор unique_ptr<A>{A*}
может уйти с неполным A
, только если компилятору не требуется настраивать вызов ~unique_ptr<A>()
. Например, если вы положили unique_ptr
в кучу, вы можете уйти с неполным A
. Более подробную информацию об этом можно найти в .