std :: auto_ptr ou boost :: shared_ptr para Pimpl idioma?
-
10-07-2019 - |
Pergunta
Ao usar o Pimpl idioma é preferível usar um boost:shared_ptr
em vez de um std::auto_ptr
? Tenho certeza que eu li uma vez que a versão impulso é mais exceção amigável?
class Foo
{
public:
Foo();
private:
struct impl;
std::auto_ptr<impl> impl_;
};
class Foo
{
public:
Foo();
private:
struct impl;
boost::shared_ptr<impl> impl_;
};
[EDIT] É sempre mais seguro para usar std :: auto_ptr <> ou existem situações quando um ponteiro inteligente alternativa impulso é necessária?
Solução
Você não deve realmente usar std :: auto_ptr para isso. O destruidor não será visível no ponto em que declarar o std :: auto_ptr, por isso não pode ser chamado corretamente. Isso supõe que você está a frente declarando sua classe Pimpl, e a criação da instância dentro do construtor em outro arquivo.
Se você usar boost :: scoped_ptr ( não há necessidade de shared_ptr aqui, você não vai estar compartilhando o pimpl com quaisquer outros objetos, e isso é reforçado por ser scoped_ptr noncopyable ), você só precisa do destructor pimpl visível no ponto de chamar o construtor scoped_ptr.
por exemplo.
// in MyClass.h
class Pimpl;
class MyClass
{
private:
std::auto_ptr<Pimpl> pimpl;
public:
MyClass();
};
// Body of these functions in MyClass.cpp
Aqui, o compilador irá gerar o destruidor de MyClass. Que deve chamar destruidor de auto_ptr. No ponto em que o processo de destruição é instanciado auto_ptr, Pimpl é um tipo incompleto. Assim, para o destruidor auto_ptr quando se exclui o objeto Pimpl, ele não sabe como chamar o destruidor Pimpl.
boost :: scoped_ptr (e shared_ptr) não tem esse problema, porque quando você chamar o construtor de um scoped_ptr (ou o método reset) também faz uma função de ponteiro-equivalente, que ele usará em vez de chamar de exclusão . O ponto chave aqui é que ele instancia a função deallocation quando Pimpl não é um tipo incompleto. Como uma nota lateral, shared_ptr permite que você especifique um costume deallocation função, de modo que você pode usar para ele para coisas como alças GDI ou qualquer outra coisa que você pode querer -. mas isso é um exagero para suas necessidades aqui
Se você realmente quiser usar std :: auto_ptr, então você precisa tomar cuidado extra por ter certeza que você define seu MyClass destructor em MyClass.cpp quando Pimpl está totalmente definido.
// in MyClass.h
class Pimpl;
class MyClass
{
private:
std::auto_ptr<Pimpl> pimpl;
public:
MyClass();
~MyClass();
};
e
// in MyClass.cpp
#include "Pimpl.h"
MyClass::MyClass() : pimpl(new Pimpl(blah))
{
}
MyClass::~MyClass()
{
// this needs to be here, even when empty
}
O compilador irá gerar o código destruct todos os membros MyClass efetivamente 'em' destruidor vazio. Então, no momento, o destruidor auto_ptr é instanciado, Pimpl já não é incompleta e o compilador agora sabe como chamar o destruidor.
Pessoalmente, eu não acho que vale a pena o aborrecimento de ter certeza que tudo está correto. Há também o risco de que alguém vai vir mais tarde e arrumar o código removendo o destruidor aparentemente redundante. Portanto, é mais seguro durante todo para ir com boost :: scoped_ptr para este tipo de coisa.
Outras dicas
I tendem a usar auto_ptr
. Não deixe de fazer sua classe noncopyable (declarar cópia ctor privado e operador = ou boost::noncopyable
herdar mais). Se você usar auto_ptr
, uma ruga é que você precisa para definir um destrutor não-inline, mesmo se o corpo está vazio. (Isto porque se você deixar o compilador gerar o destruidor padrão, impl
será um tipo incompleto quando a chamada para delete impl_
é gerado, invocando um comportamento indefinido).
Há pouco a escolher entre auto_ptr
e os ponteiros impulso. Eu não tendem a usar boost por razões estilísticas se uma alternativa biblioteca padrão irá fazer.
O impulso alternativa para std::auto_ptr
é boost::scoped_ptr
. A principal diferença do auto_ptr
é que boost::scoped_ptr
é noncopyable.
desta página para mais detalhes.
boost :: shared_ptr é especialmente adaptado para trabalhar para pimpl idioma. Uma das principais vantagens é que ele permite não para definir o destruidor para o pimpl realização de classe. Compartilhado política de propriedade talvez ambos vantagem e desvantagem. Mas no caso mais tarde você pode definir construtor de cópia corretamente.
Se você está sendo realmente pedante não há garantia absoluta de que usando um membro auto_ptr
não requer uma definição completa do parâmetro do modelo do auto_ptr
no ponto em que ela é usada. Dito isto, eu nunca vi isso não trabalho.
Uma variação é a utilização de um const auto_ptr
. Isso funciona, desde que você pode construir o seu 'pimpl' com uma nova expressão dentro da lista initialiser e garante que o compilador não pode gerar métodos construtor de cópia e de atribuição padrão. Um destrutor não-inline para a classe anexando ainda precisa ser fornecido.
Outras coisas sendo iguais, eu seria a favor de uma aplicação que utiliza apenas as bibliotecas padrão como ele mantém as coisas mais portátil.
Se você quer uma classe copiável, uso scoped_ptr
, que proíbe a cópia, tornando sua classe difícil de usar errado por padrão (em relação ao uso shared_ptr
, o compilador não irá emitir copiar instalações por conta própria, e em caso de shared_ptr
, se você não sabe o que fazer [que é muitas vezes suficiente o caso mesmo para os bruxos], não haveria comportamento estranho quando de repente uma cópia de algo também modifica de que alguma coisa), e depois fora definem uma cópia construtor e cópia -assignment:
class CopyableFoo {
public:
...
CopyableFoo (const CopyableFoo&);
CopyableFoo& operator= (const CopyableFoo&);
private:
scoped_ptr<Impl> impl_;
};
...
CopyableFoo (const CopyableFoo& rhs)
: impl_(new Impl (*rhs.impl_))
{}
shared_ptr é muito preferível a auto_ptr para Pimpl porque a sua classe externa poderia de repente acabar perdendo seu ponteiro quando você copiá-lo.
Com shared_ptr você pode usar um tipo frente-declarada assim que funciona. O auto_ptr não permitir que um tipo frente-declarado. Nem scoped_ptr e se sua classe externa vai ser de qualquer maneira não-copiável e só tem um ponteiro, ele pode muito bem ser uma forma regular.
Há muito a ser dito para usar uma contagem de referência intrusiva na Pimpl e obter a classe externa para chamar a sua cópia e semântica atribuir na sua implementação. Assumindo que este é um verdadeiro fornecedor (fornecimentos a classe) modelo é melhor o fornecedor não forçar o usuário a usar shared_ptr, ou seja, usando a mesma versão do shared_ptr (boost ou std).
Eu tenho sido muito feliz com impl_ptr por Vladimir Batov [modificado] . Isso torna muito fácil criar um Pimpl sem a necessidade de explicitar cópia construtor e cessão operador.
Eu modifiquei o código original, por isso agora se assemelha a um shared_ptr, para que possa ser usado no código epilog, e continua a ser rápida.
Não tente tão difícil de atirar no próprio pé, em C ++ você tem muitas oportunidades :) Não há nenhuma necessidade real para usar ponteiros de automóveis, desde que você sabe perfeitamente quando o seu objeto deve entrar e sair da vida (em seu construtor (s) e destruidor).
Mantenha-o simples.