Istnieje bardzo poważny problem z bibliotekami współdzielonymi, który idiom pimpl omija z łatwością, czego nie mogą zrobić zwykli wirtualiści: nie można bezpiecznie modyfikować / usuwać członków danych klasy bez zmuszania użytkowników tej klasy do ponownej kompilacji kodu. Może to być dopuszczalne w pewnych okolicznościach, ale nie np. W przypadku bibliotek systemowych.
Aby szczegółowo wyjaśnić problem, rozważ następujący kod w udostępnianej bibliotece / nagłówku:
// header
struct A
{
public:
A();
// more public interface, some of which uses the int below
private:
int a;
};
// library
A::A()
: a(0)
{}
Kompilator emituje kod w bibliotece współdzielonej, który oblicza adres liczby całkowitej, która ma być zainicjowana, aby była pewnym przesunięciem (prawdopodobnie w tym przypadku zerem, ponieważ jest to jedyny element członkowski) od wskaźnika do obiektu A, o którym wie, że jest this
.
Po stronie użytkownika kodu, a new A
najpierw przydzieli sizeof(A)
bajty pamięci, a następnie przekaże wskaźnik do tej pamięci A::A()
konstruktorowi as this
.
Jeśli w późniejszej wersji biblioteki zdecydujesz się usunąć liczbę całkowitą, powiększyć ją, pomniejszyć lub dodać składowe, wystąpi niezgodność między ilością pamięci przydzielonej przez użytkownika a przesunięciami oczekiwanymi przez kod konstruktora. Prawdopodobnym rezultatem jest awaria, jeśli masz szczęście - jeśli masz mniej szczęścia, oprogramowanie zachowuje się dziwnie.
Dzięki pimpl'owaniu możesz bezpiecznie dodawać i usuwać składowe danych do klasy wewnętrznej, ponieważ alokacja pamięci i wywołanie konstruktora mają miejsce w bibliotece współdzielonej:
// header
struct A
{
public:
A();
// more public interface, all of which delegates to the impl
private:
void * impl;
};
// library
A::A()
: impl(new A_impl())
{}
Wszystko, co musisz teraz zrobić, to uwolnić swój publiczny interfejs od członków danych innych niż wskaźnik do obiektu implementacji, a będziesz bezpieczny przed tą klasą błędów.
Edycja: Powinienem dodać, że jedynym powodem, dla którego mówię tutaj o konstruktorze, jest to, że nie chciałem udostępniać więcej kodu - ta sama argumentacja dotyczy wszystkich funkcji, które mają dostęp do członków danych.