Domanda

Quando si utilizza pImpl idiom è preferibile utilizzare un boost:shared_ptr anziché un std::auto_ptr? Sono sicuro di aver letto una volta che la versione boost è più favorevole alle eccezioni?

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 sicuro usare std :: auto_ptr < > o ci sono situazioni in cui è richiesto un puntatore intelligente boost alternativo?

È stato utile?

Soluzione

Non dovresti davvero usare std :: auto_ptr per questo. Il distruttore non sarà visibile nel punto in cui dichiari lo std :: auto_ptr, quindi potrebbe non essere chiamato correttamente. Ciò presuppone che tu dichiari in avanti la tua classe pImpl e crei l'istanza all'interno del costruttore in un altro file.

Se si utilizza boost :: scoped_ptr ( non c'è bisogno di shared_ptr qui, non condividerai il pimpl con nessun altro oggetto, e questo è imposto da scoped_ptr essendo non copiabile ), hai solo bisogno del distruttore del pimpl visibile nel punto in cui chiami il costruttore scoped_ptr.

per es.

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
};

// Body of these functions in MyClass.cpp

Qui, il compilatore genererà il distruttore di MyClass. Che deve chiamare il distruttore di auto_ptr. Nel punto in cui viene istanziata il distruttore auto_ptr, Pimpl è un tipo incompleto. Quindi, nel distruttore auto_ptr quando elimina l'oggetto Pimpl, non saprà come chiamare il distruttore Pimpl.

boost :: scoped_ptr (e shared_ptr) non hanno questo problema, perché quando chiami il costruttore di scoped_ptr (o il metodo reset) rende anche un equivalente puntatore-funzione che userà invece di chiamare delete . Il punto chiave qui è che crea un'istanza della funzione di deallocazione quando Pimpl non è un tipo incompleto. Come nota a margine, shared_ptr consente di specificare una deallocazione personalizzata , quindi puoi usarla per cose come handle GDI o qualsiasi altra cosa tu possa desiderare - ma questo è eccessivo per le tue esigenze qui.

Se vuoi davvero usare std :: auto_ptr, allora devi fare molta attenzione assicurandoti di definire il tuo distruttore MyClass in MyClass.cpp quando Pimpl è completamente definito.

// 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
}

Il compilatore genererà il codice per distruggere tutti i membri di MyClass in modo efficace "nel" distruttore vuoto. Quindi nel momento in cui il distruttore auto_ptr viene istanziato, Pimpl non è più incompleto e il compilatore ora sa come chiamare il distruttore.

Personalmente, non penso che valga la pena di assicurarsi che tutto sia corretto. C'è anche il rischio che qualcuno arrivi più tardi e riordini il codice rimuovendo il distruttore apparentemente ridondante. Quindi è più sicuro andare avanti con boost :: scoped_ptr per questo tipo di cose.

Altri suggerimenti

Tendo a usare auto_ptr. Assicurati di rendere la tua classe non copiabile (dichiarare copia privata ctor & Amp; operator =, oppure ereditare boost::noncopyable). Se si utilizza impl, una ruga è che è necessario definire un distruttore non in linea, anche se il corpo è vuoto. (Questo perché se si lascia che il compilatore generi il distruttore predefinito, delete impl_ sarà di tipo incompleto quando viene generata la chiamata a <=>, invocando un comportamento indefinito).

C'è poco da scegliere tra <=> & amp; i puntatori boost. Tendo a non usare la spinta per motivi stilistici se lo farà un'alternativa standard alla biblioteca.

boost :: shared_ptr è appositamente progettato per funzionare con idioma pimpl. Uno dei principali vantaggi è che consente di non definire il distruttore per la classe che tiene il pimpl. La politica di proprietà condivisa potrebbe essere sia vantaggi che svantaggi. Ma in un secondo momento puoi definire correttamente il costruttore di copie.

Se sei veramente pedante, non vi è alcuna garanzia assoluta che l'uso di un membro auto_ptr non richieda una definizione completa del parametro del modello const auto_ptr nel momento in cui viene utilizzato. Detto questo, non l'ho mai visto non funzionare.

Una variante è usare un <=>. Funziona finché puoi costruire il tuo 'pimpl' con una nuova espressione all'interno dell'elenco degli inizializzatori e garantisce che il compilatore non può generare metodi di assegnazione e costruttori di copie predefiniti. È necessario fornire un distruttore non inline per la classe che lo racchiude.

A parità di altre condizioni, preferirei un'implementazione che utilizza solo le librerie standard in quanto mantiene le cose più portatili.

Se si desidera una classe copiabile, utilizzare scoped_ptr, che vieta la copia, rendendo così difficile l'uso errato della classe per impostazione predefinita (rispetto all'utilizzo di shared_ptr, il compilatore non emetterà funzioni di copia da solo; e in caso di <=>, se non sai cosa fai [il che è abbastanza spesso il caso anche per i maghi], ci sarebbe un comportamento strano quando improvvisamente una copia di qualcosa modifica anche quel qualcosa), e quindi fuori definizione un costruttore di copie e un incarico di copia:

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 è molto preferibile a auto_ptr per pImpl perché la tua classe esterna potrebbe improvvisamente perdere il puntatore quando la copi.

Con shared_ptr è possibile utilizzare un tipo dichiarato in avanti affinché funzioni. auto_ptr non consente un tipo dichiarato in avanti. Né scoped_ptr e se la tua classe esterna non sarà comunque copiabile e ha solo un puntatore, potrebbe anche essere una normale.

C'è molto da dire sull'utilizzo di un conteggio di riferimenti intrusivo in pImpl e ottenere la classe esterna per chiamare la sua copia e assegnare la semantica nella sua implementazione. Supponendo che si tratti di un modello di fornitore reale (fornisce la classe), è meglio che il fornitore non imponga all'utente di utilizzare shared_ptr o di utilizzare la stessa versione di shared_ptr (boost o std).

Sono stato davvero contento di impl_ptr di Vladimir Batov [modificato] . Rende davvero facile creare un pImpl senza la necessità di creare espliciti costruttori di copie e operatori di assegnazione.

Ho modificato il codice originale, quindi ora assomiglia a shared_ptr, quindi può essere utilizzato nel codice epilog e rimane veloce.

Non sforzarti così tanto di spararti ai piedi, in C ++ hai molte opportunità :) Non è necessario utilizzare entrambi i puntatori automatici, poiché sai perfettamente quando il tuo oggetto deve entrare e uscire dalla vita (nel tuo costruttore / i e distruttore).

Mantienilo semplice.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top