pImpl_ My Ride!

pimpl my ride

Ultimamente passo le mie giornate al cubicolo occupandomi di un coso che tokenizza, parsa e sperabilemente esegue delle istruzioni contenute in uno script. Niente di strano, se non che recuperando del codice legacy è saltato fuori un pimpl; vedo di spiegare di cosa si tratta così da fornire una certa autoconsistenza al post…

Il pimpl viene spesso definito “idioma”, un costrutto dalle caratteristiche precise che possiamo usare per risolvere problemi noti; è stato formalizzato da Herb Sutter durante uno dei sui quiz per il Guru Of The Week. Nel foglietto illustrativo del pimpl troviamo le seguenti indicazioni:

  • L’header file che contiene il pimpl non include altri headers di classi che vengono usate “per valore”, il che comporta tempi di compilazione più rapidi.
  • Cambiamenti alla parte privata della classe non comportano la ricomplilazione dei moduli sorgente che dipendendono da questa, anche qui tempi di compilazione più rapidi.
  • Favorisce l’incapsulamento.

Sempre nel foglietto illustrativo troviamo le controindicazioni:

  • Sviluppo di codice aggiuntivo.
  • Il pimpl non può incapsulare dati “protected” eventualmente necessari alle sottoclassi.
  • L’ulteriore livello di indirezione introdotto fra la classe ed il suo pimpl impatta le performances (non di molto a dire il vero) e richiede un pò più di attenzione in fase di manutenzione.
  • Non possiamo spostare funzioni virtual all’interno del pimpl.

Vediamo un pimpl in azione:


class XImpl; // forward declaration
class X
{
public:
  X();
  ~X();
  // costruttore di copia ed altre amenità...
  void metodoPubblico();
private:
  XImpl* pImpl_;
};

Notate lo stile Hungarian usato per la variabile che incidentalmente da il nome all’idioma stesso!

Ora, osservando la dichiarazione della classe X, possiamo notare il puntatore opaco al pimpl che ci mette al riparo da ricompilazioni selvagge (vedi punto uno dei benefici effetti del pimpl). Approfondiamo il discorso e passiamo all’implementazione:


#include "x.hpp"

class XImpl
{
public:
  XImpl(X* x) : self_(x) {}
  void metodoPrivatoDiX() {}
  void altroMetodo()
  {
    // accedo ad un metodo pubblico di X
    self_->metodoPubblico();
  }

  int membroPrivatoDiX_;
};

X::X() : pImpl_(new XImpl(this)) {}
X::~X() {delete pImpl_;}
void X::metodoPubblico() {pImpl_->membroPrivato_ = 1;}

Notiamo l’utilizzo del pimpl all’interno dei metodi di X, questo sostituisce quella che sarebbe stata una chiamata ad un metodo privato della classe stessa. Ora è evidente come la classe XImpl sia sconosciuta agli utenti di X; questo significa che cambiando XImpl non ricompileremo il mondo intero (punto due della lista benefici). Altra cosa da notare è che il pimpl che ho scritto si tiene un puntatore alla classe X, questo nell’eventualità che XImpl dovesse accedere a metodi pubblici o virtuali di X per svolgere i propri compiti; si tratta di un altro livello di indirezione, ma possiamo conviverci…

Ne vale la pena? Se ci serve, si, se deve rimanere un esercizio di stile meglio investire il tempo in attività più proficue, come ad esempio farsi un caffè. Nel caso del mio coso parserizzatore il pimpl ha isolato Flex e Bison dal resto del codice, producendo una serie piuttosto lunga di vantaggi (fra cui vedi punto tre lista benefici).

Per approfondire:
http://c2.com/cgi/wiki?PimplIdiom
http://www.devx.com/cplus/Article/28105
e naturalmente…
http://www.gotw.ca/gotw/024.htm