Технология D-Pointer

10 октября 2011 г.

Вступление

Термин d-pointer впервые ввел Arnt Gulbrandsen ( Trolltech ) для техники, которая обеспечивала бинарную совместимость библиотек. Библиотеки бинарно-совместимы, если приложение может работать с новой версией библиотеки без перекомпиляции самого приложения.

Описание технологии

Допустим, у нас есть библиотека с классом Person:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
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;
};

Если мы решим в новой версии библиотеки добавить/удалить поле,

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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 битной системы) и добавление новых полей не изменит его размера.

Хочу заметить, что если объект копируется, то мы должны добавить объявления конструктора копирования и оператора присваивания в заголовочный файл:

1
2
Person(const Person &person);  
    const Person &operator=(const Person &person); 

Также их определения в срр файле:

01
02
03
04
05
06
07
08
09
10
11
12
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.

О изменениях, которые могут поломать совместимость можно почитать тут.

Теги: рубрика Интернет