Існує дуже реальна проблема спільних бібліотек, що ідіома pimpl акуратно обходить, що чисті віртуали не можуть: ви не можете безпечно змінювати / видаляти дані членів класу, не змушуючи користувачів класу перекомпілювати їх код. Це може бути прийнятно за деяких обставин, але, наприклад, для системних бібліотек.
Щоб детально пояснити проблему, врахуйте наступний код у вашій спільній бібліотеці / заголовку:
// header
struct A
{
public:
A();
// more public interface, some of which uses the int below
private:
int a;
};
// library
A::A()
: a(0)
{}
Компілятор висилає код у спільній бібліотеці, який обчислює адресу цілого числа, яке потрібно ініціалізувати, для певного зміщення (можливо, нуль у цьому випадку, тому що це єдиний член) з вказівника на об'єкт A, яким він знає this
.
З боку користувача коду, a new A
спочатку виділить sizeof(A)
байти пам'яті, а потім передасть вказівник на цю пам'ять A::A()
конструктору якthis
.
Якщо в подальшому перегляді вашої бібліотеки ви вирішите скинути ціле число, збільшити його, зменшити чи додати членів, то буде виявлено невідповідність кількості виділеного коду пам'яті користувача та компенсацій, які очікує код конструктора. Ймовірний результат - збій, якщо пощастить - якщо вам менше щастить, ваше програмне забезпечення поводиться дивно.
За допомогою pimpl'ing ви можете безпечно додавати та видаляти члени даних до внутрішнього класу, оскільки розподіл пам'яті та виклик конструктора відбуваються у спільній бібліотеці:
// 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())
{}
Все, що вам потрібно зробити зараз, - це захистити ваш публічний інтерфейс без членів даних, окрім вказівника на об’єкт реалізації, і ви захищені від цього класу помилок.
Редагувати: Я, можливо, варто додати, що єдиною причиною, про яку я тут говорю про конструктор, є те, що я не хотів надавати більше коду - така ж аргументація стосується всіх функцій, які мають доступ до членів даних.