Adotado a partir daqui .
A maioria dos modelos na biblioteca padrão C ++ exige que eles sejam instanciados com tipos completos. No entanto shared_ptr
e unique_ptr
são exceções parciais . Alguns, mas nem todos os seus membros podem ser instanciados com tipos incompletos. A motivação para isso é oferecer suporte a expressões como pimpl usando ponteiros inteligentes e sem arriscar um comportamento indefinido.
O comportamento indefinido pode ocorrer quando você tem um tipo incompleto e o invoca delete
:
class A;
A* a = ...;
delete a;
O código acima é legal. Ele irá compilar. Seu compilador pode ou não emitir um aviso para o código acima, como o acima. Quando executado, coisas ruins provavelmente vão acontecer. Se você tiver muita sorte, seu programa falhará. No entanto, um resultado mais provável é que seu programa vaze memória silenciosamente, pois ~A()
não será chamado.
Usar auto_ptr<A>
no exemplo acima não ajuda. Você ainda tem o mesmo comportamento indefinido como se tivesse usado um ponteiro bruto.
No entanto, o uso de classes incompletas em certos lugares é muito útil! Aqui é onde shared_ptr
e unique_ptr
ajuda. O uso de um desses ponteiros inteligentes permitirá que você continue com um tipo incompleto, exceto onde for necessário ter um tipo completo. E o mais importante, quando é necessário ter um tipo completo, você recebe um erro em tempo de compilação se tentar usar o ponteiro inteligente com um tipo incompleto nesse ponto.
Não há mais comportamento indefinido:
Se seu código for compilado, você utilizou um tipo completo em todos os lugares que precisar.
class A
{
class impl;
std::unique_ptr<impl> ptr_; // ok!
public:
A();
~A();
// ...
};
shared_ptr
e unique_ptr
exige um tipo completo em lugares diferentes. Os motivos são obscuros, relacionados a um deleter dinâmico versus um deleter estático. As razões precisas não são importantes. De fato, na maioria dos códigos, não é realmente importante que você saiba exatamente onde um tipo completo é necessário. Basta codificar e, se você errar, o compilador lhe dirá.
No entanto, caso seja útil, aqui está uma tabela que documenta vários membros shared_ptr
e unique_ptr
com relação aos requisitos de integridade. Se o membro exigir um tipo completo, a entrada terá um "C", caso contrário, a entrada da tabela será preenchida com "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 |
+------------------------+---------------+---------------+
Quaisquer operações que requeiram conversões de ponteiro requerem tipos completos para ambos unique_ptr
e shared_ptr
.
O unique_ptr<A>{A*}
construtor pode se livrar de um incompleto A
apenas se o compilador não precisar configurar uma chamada para ~unique_ptr<A>()
. Por exemplo, se você colocar o unique_ptr
heap, poderá se livrar de um incompleto A
. Mais detalhes sobre esse ponto podem ser encontrados na resposta de BarryTheHatchet aqui .
shared_ptr
/unique_ptr
" de Howard Hinnant. A tabela no final deve responder à sua pergunta.