Вступление
Термин d-pointer впервые ввел Arnt Gulbrandsen ( Trolltech ) для техники, которая обеспечивала бинарную совместимость библиотек. Библиотеки бинарно-совместимы, если приложение может работать с новой версией библиотеки без перекомпиляции самого приложения.
Описание технологии
Допустим, у нас есть библиотека с классом Person:
Person.h
class Person
{
public:
void setName(const std::string &name);
std::string name() const;
void setFam(const std::string &fam);
std::string fam() const;
private:
std::string name;
std::string fam;
};
Если мы решим в новой версии библиотеки добавить/удалить поле,
Person.h
class Person
{
public:
void setINN(const std::string &INN);
std::string INN() const;
void setName(const std::string &name);
std::string name() const;
void setFam(const std::string &fam);
std::string fam() const;
private:
std::string INN;
std::string name;
std::string fam;
};
то приложение с новой версией библиотеки без перекомпиляции работать не будет. Это происходит потому что, доступ к полям класса осуществляется со смещением и добавление нового поля изменияет адресацию полей name и fam, а также из-за увеличения размера самого объекта.
Для решения данной проблемы была придумана технология d-pointer.
Person.h
class Person
{
public:
Person();
~Person();
void setName(const std::string &name);
std::string name() const;
void setFam(const std::string &fam);
std::string fam() const;
private:
Person(const Person &person);
const Person& operator=(const Person &person);
struct PrivData;
PrivData* const d;
};
Person.cpp
#include "Person.h"
struct Person::PrivData
{
std::string name;
std::string fam;
};
Person::Person():
d(new PrivData())
{
}
Person::~Person()
{
delete d;
}
void Person::setName(const std::string &name)
{
d->name = name;
}
std::string Person::name() const
{
return d->name;
}
void Person::setFam(const std::string &fam)
{
d->fam = fam;
}
std::string Person::fam() const
{
return d->fam;
}
Теперь размер объекта на стеке будет равен 4 байта(для 32-x битной системы) и добавление новых полей не изменит его размера.
Хочу заметить, что если объект копируется, то мы должны добавить объявления конструктора копирования и оператора присваивания в заголовочный файл:
Person(const Person &person);
const Person &operator=(const Person &person);
Также их определения в срр файле:
Person::Person(const Person &person):
d(new PrivData(*person.d))
{
}
const Person& Person::operator =(const Person &person)
{
if (&person != this)
*d = *person.d;
return *this;
}
Помимо бинарной совместимости технология d-pointer имеет и другие преимущества:
- Скрывает реализацию в cpp файле.
- Заголовочный файл занимает меньше размера и выглядит более аккуратно.
- Из-за меньшего размера заголовочных файлов, быстрей происходит компиляция приложения.
Заключение
Данная технология широко используется в таких проектах как Qt и KDE. В проекте KDE бинарная совместимость обеспечивается на протяжении старшего номера версии.
Для проверки библиотек, написанных на C/C++ есть скрипт, написанный на perl.
О изменениях, которые могут поломать совместимость можно почитать тут.