Принято с здесь .
Для большинства шаблонов в стандартной библиотеке 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 здесь .