Автор: Matthias Ettrich
Перевод: Andi Peredri
Библиотека является бинарно совместимой, если программа, собранная с предыдущей версией библиотеки, может работать с ее новой версией без перекомпиляции.
Если для обеспечения работы программы с новой версией библиотеки необходима ее перекомпиляция, но не требуются какие-либо изменения в исходном коде, библиотека является совместимой на уровне исходного кода.
Бинарная совместимость избавляет от множества хлопот. Она максимально упрощает распространение программного обеспечения в пределах одной платформы. Без гарантий бинарной совместимости разработчикам пришлось бы собирать свои программы статически. Статически собранные программы являются плохим решением, потому что они
В проекте KDE мы обеспечиваем бинарную совместимость на протяжении старшего номера версии.
Вы можете ...
Вы не можете ...
void functionname( int a ); void functionname( int a, int b ); //BCI: merge with int b = 0
Самой большой проблемой при написании библиотек является невозможность безопасного добавления новых данных-членов, так как это приведет к изменению размеров и расположения каждого класса, структуры и массива, содержащих объект этого типа, включая наследуемые классы.
Исключением являются битовые поля. Если вы в качестве компонентов структур и объединений используете битовые поля, то можете безопасно изменять их суммарный размер до ближайшего целого байта, минус 1. Класс с битовыми полями
uint m1 : 1; uint m2 : 3; uint m3 : 1;
uint m1 : 1; uint m2 : 3; uint m3 : 1; uint m4 : 2; // new member
Битовые поля и зарезервированные переменные являются хорошим, но не достаточным решением. Настало время рассмотреть технику d-указателей. Термин "d-указатель" ввел Arnt Gulbrandsen ( Trolltech ) для техники, использованной при разработке библиотеки Qt, обеспечив ей одной из первых C++ GUI-библиотек бинарную совместимость на протяжении многих выпусков. Эта техника была быстро адаптирована как основной прием программирования многими разработчиками KDE-библиотек. Это замечательное решение проблемы добавления новых закрытых данных-членов в класс без потери бинарной совместимости.
Примечание: Техника d-указателей неоднократно описывалась в истории информатики под различными именами, такими, как pimpl (pointer to implementation), handle/body, чеширский кот. Он-лайн версии этих документов вы можете найти с помощью Google, добавив "C++" в строку поиска.
В объявление вашего класса Foo добавьте следующую декларацию:
class FooPrivate;
private: FooPrivate* d;
class FooPrivate { public: FooPrivate() : m1(0), m2(0) {}; int m1; int m2; QString s; };
d = new FooPrivate;
delete d;
Естественно, вы не обязаны располагать абсолютно все данные-члены в классе FooPrivate. Для повышения производительности часто используемые данные лучше поместить непосредственно в класс Foo, тем более, что встраиваемые функции не имеют доступа к данным FooPrivate. Заметьте также, что все данные, доступные через d-указатель, являются закрытыми в пределах класса Foo. Чтобы сделать их открытыми или защищенными, необходимо реализовать set- и get- функции, например:
QString Foo::string() const { return d->s; } void setString( const QString& s ) { d->s = s; }
Если у вас нет свободных битовых полей, зарезервированных переменных или d-указателя, но вам непременно нужно добавить новую закрытую переменную, у вас все еще остается возможность сделать это. Если ваш класс является производным от QObject, вы можете поместить дополнительные данные в специальный дочерний объект и затем найти его в списке дочерних объектов. Список дочерних объектов может быть получен с помощью QObject::children(). Однако наиболее быстрым и предпочтительным способом хранения соответствий между вашими объектами и дополнительными данными является использование хеш-таблиц. Для этих целей Qt предлагает словарь на основе указателей QPtrDict.
Для использования этого приема вам необходимо проделать следующие шаги:
// BCI: Add a real d-pointer static QPtrDict<FooPrivate>* d_ptr = 0; static void cleanup_d_ptr() { delete d_ptr; } static FooPrivate* d( const Foo* foo ) { if ( !d_ptr ) { d_ptr = new QPtrDict<FooPrivate> qAddPostRoutine( cleanup_d_ptr ); } FooPrivate* ret = d_ptr->find( (void*) foo ); if ( ! ret ) { ret = new FooPrivate; d_ptr->replace( (void*) foo, ret ); } return ret; } static void delete_d( const Foo* foo ) { if ( d_ptr ) d_ptr->remove( (void*) foo ); }
d(this)->m1 = 5;
delete_d(this);
Источник: Binary Compatibility Issues With C++