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?

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top