Domanda

Questa domanda ha già una risposta qui:

Background:

IL PIMPL Idioma (Pointer to IMPLementation) è una tecnica per nascondere l'implementazione in cui una classe pubblica avvolge una struttura o una classe che non può essere vista all'esterno della libreria di cui fa parte la classe pubblica.

Ciò nasconde i dettagli e i dati di implementazione interna all'utente della libreria.

Quando implementi questo linguaggio, perché dovresti inserire i metodi pubblici nella classe pimpl e non nella classe pubblica poiché le implementazioni dei metodi delle classi pubbliche verrebbero compilate nella libreria e l'utente ha solo il file di intestazione?

Per illustrare, questo codice inserisce il file Purr() implementazione sulla classe impl e la avvolge anch'essa.

Perché non implementare Purr direttamente nella classe pubblica?

// header file:
class Cat {
    private:
        class CatImpl;  // Not defined here
        CatImpl *cat_;  // Handle

    public:
        Cat();            // Constructor
        ~Cat();           // Destructor
        // Other operations...
        Purr();
};


// CPP file:
#include "cat.h"

class Cat::CatImpl {
    Purr();
...     // The actual implementation can be anything
};

Cat::Cat() {
    cat_ = new CatImpl;
}

Cat::~Cat() {
    delete cat_;
}

Cat::Purr(){ cat_->Purr(); }
CatImpl::Purr(){
   printf("purrrrrr");
}
È stato utile?

Soluzione

  • Perché lo vuoi Purr() poter utilizzare i membri privati ​​di CatImpl. Cat::Purr() non sarebbe consentito tale accesso senza a friend dichiarazione.
  • Perché poi non mescoli le responsabilità:una classe implementa, l'altra classe va avanti.

Altri suggerimenti

Penso che la maggior parte delle persone si riferisca a questo come al linguaggio Handle Body.Vedere il libro di James Coplien Advanced C++ Programming Styles and Idioms (Collegamento ad Amazon).È noto anche come Gatto del Cheshire a causa del carattere di Lewis Caroll che svanisce finché rimane solo il sorriso.

Il codice di esempio dovrebbe essere distribuito su due set di file sorgente.Quindi solo Cat.h è il file fornito con il prodotto.

CatImpl.h è incluso da Cat.cpp e CatImpl.cpp contiene l'implementazione per CatImpl::Purr().Questo non sarà visibile al pubblico utilizzando il tuo prodotto.

Fondamentalmente l'idea è quella di nascondere il più possibile l'implementazione da occhi indiscreti.Ciò è particolarmente utile quando si dispone di un prodotto commerciale che viene spedito come una serie di librerie a cui si accede tramite un'API su cui viene compilato e collegato il codice del cliente.

Lo abbiamo fatto con la riscrittura del prodotto Orbix 3.3 di IONA nel 2000.

Come già detto da altri, l'utilizzo della sua tecnica disaccoppia completamente l'implementazione dall'interfaccia dell'oggetto.Quindi non dovrai ricompilare tutto ciò che utilizza Cat se vuoi solo cambiare l'implementazione di Purr().

Questa tecnica viene utilizzata in una metodologia chiamata progettazione su contratto.

Per quello che vale, separa l'implementazione dall'interfaccia.Questo di solito non è molto importante nei progetti di piccole dimensioni.Ma, in progetti e librerie di grandi dimensioni, può essere utilizzato per ridurre significativamente i tempi di costruzione.

Considerare che l'implementazione di Cat può includere molte intestazioni, può comportare una meta-programmazione del modello che richiede tempo per essere compilata da sola.Perché un utente che desidera semplicemente utilizzare il file Cat devo includere tutto questo?Pertanto, tutti i file necessari vengono nascosti utilizzando l'idioma pimpl (da cui la dichiarazione anticipata di CatImpl) e l'utilizzo dell'interfaccia non obbliga l'utente a includerli.

Sto sviluppando una libreria per l'ottimizzazione non lineare (leggi "un sacco di brutti calcoli"), che è implementata nei modelli, quindi la maggior parte del codice è nelle intestazioni.Ci vogliono circa cinque minuti per compilare (su una CPU multi-core decente) e semplicemente analizzare le intestazioni in un file altrimenti vuoto .cpp dura circa un minuto.Quindi chiunque utilizzi la libreria deve attendere un paio di minuti ogni volta che compila il proprio codice, il che rende lo sviluppo tranquillo noioso.Tuttavia, nascondendo l'implementazione e gli header, si include semplicemente un semplice file di interfaccia, che si compila istantaneamente.

Non ha necessariamente nulla a che fare con la protezione dell'implementazione dalla copiatura da parte di altre società - cosa che probabilmente non accadrebbe comunque, a meno che il funzionamento interno dell'algoritmo non possa essere indovinato dalle definizioni delle variabili membro (se è così, è probabilmente non molto complicato e non vale la pena proteggerlo in primo luogo).

Se la tua classe utilizza l'idioma pimpl, puoi evitare di modificare il file header sulla classe pubblica.

Ciò ti consente di aggiungere/rimuovere metodi alla classe pimpl, senza modificare il file di intestazione della classe esterna.Puoi anche aggiungere/rimuovere #include anche al brufolo.

Quando modifichi il file di intestazione della classe esterna, devi ricompilare tutto ciò che #include (e se qualcuno di questi è file di intestazione, devi ricompilare tutto ciò che #include e così via)

In genere, l'unico riferimento alla classe Pimpl nell'intestazione della classe Owner (Cat in questo caso) sarebbe una dichiarazione anticipata, come hai fatto qui, perché ciò può ridurre notevolmente le dipendenze.

Ad esempio, se la tua classe Pimpl ha ComplicatedClass come membro (e non solo un puntatore o un riferimento ad essa), allora dovresti avere ComplicatedClass completamente definito prima del suo utilizzo.In pratica, ciò significa includere "ComplicatedClass.h" (che includerà indirettamente anche tutto ciò da cui dipende ComplicatedClass).Ciò può portare al riempimento di una singola intestazione che inserisce moltissime cose, il che è negativo per la gestione delle dipendenze (e dei tempi di compilazione).

Quando usi pimpl idion, devi solo #includere il materiale utilizzato nell'interfaccia pubblica del tuo tipo Owner (che qui sarebbe Cat).Ciò migliora le cose per le persone che utilizzano la tua libreria e significa che non devi preoccuparti che le persone dipendano da qualche parte interna della tua libreria, sia per errore, sia perché vogliono fare qualcosa che non permetti, quindi #define private public prima di includere i file.

Se si tratta di una classe semplice, di solito non c'è motivo di usare un Pimpl, ma nei momenti in cui i tipi sono piuttosto grandi, può essere di grande aiuto (soprattutto per evitare lunghi tempi di costruzione)

Beh, non lo userei.Ho un'alternativa migliore:

foo.h:

class Foo {
public:
    virtual ~Foo() { }
    virtual void someMethod() = 0;

    // This "replaces" the constructor
    static Foo *create();
}

foo.cpp:

namespace {
    class FooImpl: virtual public Foo {

    public:
        void someMethod() { 
            //....
        }     
    };
}

Foo *Foo::create() {
    return new FooImpl;
}

Questo modello ha un nome?

Essendo anche un programmatore Python e Java, mi piace molto di più dell'idioma pImpl.

Utilizziamo l'idioma PIMPL per emulare la programmazione orientata agli aspetti in cui gli aspetti pre, post ed errore vengono chiamati prima e dopo l'esecuzione di una funzione membro.

struct Omg{
   void purr(){ cout<< "purr\n"; }
};

struct Lol{
  Omg* omg;
  /*...*/
  void purr(){ try{ pre(); omg-> purr(); post(); }catch(...){ error(); } }
};

Usiamo anche il puntatore alla classe base per condividere aspetti diversi tra molte classi.

Lo svantaggio di questo approccio è che l'utente della libreria deve tenere conto di tutti gli aspetti che verranno eseguiti, ma vede solo la sua classe.Richiede la consultazione della documentazione per eventuali effetti collaterali.

Posizionando la chiamata a impl->Purr all'interno del file cpp significa che in futuro potresti fare qualcosa di completamente diverso senza dover modificare il file di intestazione.Forse l'anno prossimo scopriranno un metodo di supporto che avrebbero potuto chiamare invece e così potranno cambiare il codice per chiamarlo direttamente e non usare affatto impl->Purr.(Sì, potrebbero ottenere la stessa cosa aggiornando anche il metodo impl::Purr, ma in quel caso sei bloccato con una chiamata di funzione aggiuntiva che non ottiene altro che chiamare a sua volta la funzione successiva)

Significa anche che l'intestazione ha solo definizioni e non ha alcuna implementazione che consenta una separazione più netta, che è il punto centrale del linguaggio.

Ho appena implementato la mia prima lezione sui brufoli negli ultimi due giorni.L'ho usato per eliminare i problemi che riscontravo includendo winsock2.h in Borland Builder.Sembrava che stesse rovinando l'allineamento della struttura e poiché avevo dei socket nei dati privati ​​della classe, quei problemi si stavano diffondendo a qualsiasi file cpp che includeva l'intestazione.

Utilizzando pimpl, winsock2.h è stato incluso in un solo file cpp in cui ho potuto mettere un coperchio sul problema e non preoccuparmi che tornasse a tormentarmi.

Per rispondere alla domanda originale, il vantaggio che ho trovato nell'inoltro delle chiamate alla classe pimpl è che la classe pimpl è la stessa di quella che sarebbe stata la tua classe originale prima di pimpl, inoltre le tue implementazioni non sono distribuite su 2 lezioni in qualche modo strano.È molto più chiaro implementare i public per inoltrarli semplicemente alla classe pimpl.

Come ha detto l'onorevole Nodet, una classe, una responsabilità.

Non so se sia una differenza degna di nota ma...

Sarebbe possibile avere l'implementazione nel proprio spazio dei nomi e avere uno spazio dei nomi wrapper/libreria pubblico per il codice che l'utente vede:

catlib::Cat::Purr(){ cat_->Purr(); }
cat::Cat::Purr(){
   printf("purrrrrr");
}

In questo modo tutto il codice della libreria può utilizzare lo spazio dei nomi cat e quando si presenta la necessità di esporre una classe all'utente è possibile creare un wrapper nello spazio dei nomi catlib.

Trovo significativo che, nonostante quanto sia noto il linguaggio brufolo, non lo vedo emergere molto spesso nella vita reale (ad es.nei progetti open source).

Mi chiedo spesso se i "benefici" siano eccessivi;sì, puoi rendere alcuni dettagli della tua implementazione ancora più nascosti e sì, puoi modificare la tua implementazione senza cambiare l'intestazione, ma non è ovvio che questi siano grandi vantaggi nella realtà.

Vale a dire, non è chiaro se sia necessario che la tua implementazione lo sia Quello ben nascosto, e forse è abbastanza raro che le persone cambino davvero solo l'implementazione;non appena è necessario aggiungere nuovi metodi, ad esempio, è necessario modificare comunque l'intestazione.

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