Взято из здесь.
Большинство шаблонов в стандартной библиотеке C ++ требуют, чтобы они создавались с полными типами. Однако shared_ptr
и unique_ptr
являются частичными исключениями. Некоторые, но не все их члены могут быть созданы с неполными типами. Мотивация для этого - поддержка таких идиом, как pimpl с использованием интеллектуальных указателей, и без риска неопределенного поведения.
Неопределенное поведение может возникнуть, если у вас есть неполный тип и вы вызываете для него delete
:
class A;
A* a = ...;
delete a;
Выше приведен правовой кодекс. Он будет компилироваться. Ваш компилятор может выдавать или не выдавать предупреждение для вышеуказанного кода, подобного приведенному выше. Когда он выполнится, вероятно, случатся плохие вещи. Если вам очень повезет, ваша программа выйдет из строя. Однако более вероятным исходом является то, что ваша программа будет молча утечь память, поскольку ~A()
не будет вызываться.
Использование auto_ptr<A>
в приведенном выше примере не помогает. Вы по-прежнему получаете такое же неопределенное поведение, как если бы вы использовали необработанный указатель.
Тем не менее, использование неполных занятий в определенных местах очень полезно! Здесь помогают shared_ptr
и unique_ptr
. Использование одного из этих интеллектуальных указателей позволит вам избежать неполного типа, за исключением случаев, когда необходимо иметь полный тип. И, что наиболее важно, когда необходимо иметь полный тип, вы получите ошибку времени компиляции, если попытаетесь использовать интеллектуальный указатель с неполным типом в этот момент.
Больше никакого неопределенного поведения:
Если ваш код компилируется, значит, вы использовали полный тип везде, где вам нужно.
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
. Более подробную информацию по этому поводу можно найти в ответе BarryTheHatchet здесь.
person
Howard Hinnant
schedule
22.05.2011