Библиотека является бинарно совместимой, если программа, собранная с предыдущей версией библиотеки, может работать с ее новой версией без перекомпиляции.
Если для обеспечения работы программы с новой версией библиотеки необходима ее перекомпиляция, но не требуются какие-либо изменения в исходном коде, библиотека является совместимой на уровне исходного кода.
Бинарная совместимость избавляет от множества хлопот. Она максимально упрощает распространение программного обеспечения в пределах одной платформы. Без гарантий бинарной совместимости разработчикам пришлось бы собирать свои программы статически. Статически собранные программы являются плохим решением, потому что они
В проекте 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; |
и d-указатель в закрытую секцию класса:
private: FooPrivate* d; |
Сам класс FooPrivate целиком и полностью определяется в файле реализации класса ( обычно *.cpp ), например:
class FooPrivate {
public:
FooPrivate()
: m1(0), m2(0)
{};
int m1;
int m2;
QString s;
};
|
Все, что вам теперь осталось сделать, это создать в конструкторе или инициализирующей функции объект FooPrivate:
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); |