wtorek, 10 marca 2015

Przezroczysty wskaźnik (PIMPL), czyli piękno rzeczy prostych

To jeden z moich ulubionych wzorców projektowych. Przydaje się zarówno do uproszczania nagłówka klasy jak i skraca czas kompilacji w dużych projektach. Przezroczysty wskaźnik (lub po prostu PIMPL) to wzorzec tworzenia abstrakcji pewnej struktury/klasy tak, aby (w najczęstszym przypadku) ukryć całą informację o budowie części prywatnej. Z racji, że jakakolwiek zmiana w części prywatnej dzieje się poza nagłówkiem, taka zmiana nie wymusza rekompilacji wszystkich plików zależnych od tego nagłówka. W dużym projekcie to na prawdę duża oszczędność.

Urok przezroczystego wskaźnka bierze się z zasady kompilacji w C/C++. Jeżeli klasa/funkcja posiada/bierze wskaźnik na typ złożny (T*) to dopóki nie próbujesz wyłuskać składowej klasy T lub utworzyć jej instancji, kompilatora nie interesuje budowa typu T. Dlaczego? Bo wskaźnik ma zawsze ten sam rozmiar, więc kwestia alokacji jest jasna. Aby kompilator nie narzekał, że używasz typu nieokreślonego w nagłowkach wystarczy zrobić deklarację wprzódstruct T. Przy deklaracji wprzód, ponieważ nie zamierzamy odnosić się do budowy tego typu, słowo struct i class można stosować zamiennie.

Koncepcja wskaźnika przezroczystego:

  • W nagłówku, części prywatnej, deklarujesz wprzód, że dana klasa ma lokalną strukturę, ale jej nie opisujesz
  • W nagłówku, części prywatnej, określasz że dana klasa ma składową typu wskaźnik na lokalną strukturę (wciąż nieopisaną)
  • W pliku .cpp dostarczasz budowę klasy TwojaKlasa::StrukturaWewnętrzna
  • W konstruktorze swojej klasy tworzysz instancję klasy lokalnej podpinając ją pod widoczny w nagłówku wskaźnik
W kodzie wygląda to tak: klasa.hpp
#ifndef KLASA_H
#define KLASA_H

#include <string>

class Klasa
{
  public:
    Klasa();
    ~Klasa();

    std::string twojeImie() const;

  private:
    struct Pimpl;
    Pimpl* priv;
};

#endif //KLASA_H

klasa.cpp
#include "klasa.hpp"

struct Klasa::Pimpl
{
  std::string imie;

  Pimpl(const std::string& imie)
  {
    this->imie = imie;
  }
};

Klasa::Klasa()
{
  priv = new Pimpl("Imie");
}

Klasa::~Klasa()
{
  delete priv;
}

std::string Klasa::twojeImie() const
{
  return priv->imie;
}

Jedyny dyskomfort związany z użyciem przezroczystego wskaźnika wiąże się z debuggowaniem kodu, bo mimo iż gdb ma dostęp do składowych chronionych klasy, a tym samym struktury lokalnej, to niekiedy z niewiadomej przyczyny GDB traci zdolność wykonania introspekcji budowy klasy lokalnej, na szczęście nie jest to częste. Jeżeli ktoś z Was wie jak temu zaradzić, będę wdzięczny za notkę w komentarzach.