Разработчик мобильных приложений
0,7
рейтинг
24 ноября 2009 в 20:37

Разработка → Что такое Pimpl по версии Qt, и с чем его едят!

Qt*

Вступление.



Часто в документации от Qt встречается термин Pimpl. Кроме того, те кто хоть немного копался в исходном коде Qt часто видел такие макросы как: Q_DECLARE_PRIVATE, Q_D. А также встречал так называемые приватные заголовочные файлы, название которых заканчивается на "_p.h".
В этой статье я попробую приоткрыть ширму за всей это структурой.

Pimpl, что это?


Pimpl — Pointer to private implementation. Это одно из названий паттерна программирования. Еще его называют чеширским котом — «Cheshire Cat» (это название мне больше нравится). В чем суть этого паттерна? Основная идея этого паттерна — это вынести все приватные члены класса и, в не которых случаях, функционала в приватный класс.
Отсюда название «чеширский кот» — видно только улыбку, а все остальное остается невидимым, но оно несомненно есть :-) Кто не помнит этого замечательного кота, может обратится к первоисточнику, к книге Льюиса Кэрролла «Алиса в стране чудес». Очень интересная книга, особенно если читать в оригинале.
Что это дает?

  1. Если иерархия классов «широкая» или «глубокая» получается более комплексная структура классов и тем самым повышается удобство «переиспользование — reuse» кода.
  2. Также это дает возможность спрятать платформо-зависимую реализацию от конечного пользователя.
  3. Одно из основных назначений этого паттерна — это предоставление механизма для реализации бинарной совместимости библиотеки при изменение ее реализации(достигается за счет того что вся реализация находится в приватном классе). Более подробно о бинарной совместимости и бинарном интерфейсе приложения (ABI) можно ознакомится здесь (Itanium C++ ABI) и здесь (an article about calling conversion). И основные правила о бинарной совместимости от KDE разработчиков Binary Compatibility Issues With C++ с кратким сборником того что нужно и чего нельзя делать для сохранения бинарной совместимости.
  4. Следующим достоинством является то что количество экспортируемых символов в классе становится меньше и скорость загрузки библиотеки увеличивается, ну и плюс меньший размер конечно.
  5. Увеличивается скорость сборки приложения (что очень актуально).
  6. Прячется вся ненужная реализация от клиента, в отличие от приватных методов, pimpl объявление и реализацию не видно вообще.
  7. Этот паттерн очень сильно облегчает реализацию механизма implicit-sharing (не буду переводить, чтоб не плодить лишней терминологии). Механизм при котором при копировании классов не происходит копирование данных, а копирование происходит лишь тогда, когда копии класса потребуется изменить эти данные. Implicit-sharing реализован во всех контейнерных классах Qt. Для его реализации используется реализация Pimpl под названием «shared D-pointers». Вообще это емкая тема и требует отдельной статьи.


Но ведь должны же быть и минусы, скрывать их не буду, выложу как есть:
  1. Каждый конструктор, деструктор вызывают выделение и освобождение памяти, что увеличивают время создания класса.
  2. Каждый метод, который содержит доступ к приватной реализации добавляет плюс одну экстра инструкцию.


Учитывая эти недостатки, по мнению автора, использование этого паттерна для классов, которые будут содержатся в коде в большом количестве и создаваться/удалятся в процессе жизнедеятельности программы нецелесообразно. Допустим примером такой реализации может служить загрузка и хранение неких данных, вот классы, которые реализуют эти данные лучше сделать как можно проще. А любое изменение в таких классах предполагает что изменяется протокол самой передачи этих данных. Но могут быть и другие примеры такого рода классов. Поэтому основное правило при использование любого паттерна распространяется и на этот тоже: использовать необходимо оптимально, там где это действительно необходимо.
То есть этот паттерн необходимо использовать в том случае, если вы пишете библиотеку или планируете вынести этот функционал в будущем в отдельную библиотеку. В других случаях, по личному мнению автора, использование такого подхода избыточно.

Как это реализовано в библиотеках Qt.


В Qt коде используется подход d-указателей. Смысл в том что объявляется класс XXXPrivate и переменная публичного класса в защищенной секции. В отдельном заголовочном файле или в .cpp файле уже пишется реализация приватного класса (в чем разница я объясню позже).
Иерархия классов идет как по публичным так и по приватным классам. Для этого объявление приватного класса обычно делается в отдельном .h файле, который называется так-же как публичный, но добавляется приставка _p: qclassname_p.h. И эти классы не устанавливаются вместе с библиотекой, а служат лишь для сборки библиотеки. Поэтому вы их не найдете в пути, куда установились библиотеки QT (Prefix-PATH).

В чем достоинства подхода d-указателей (d-pointers) от Qt?


Этот подход с первого взгляда может показаться немного запутанным, но я вас уверяю, что он в действительности очень простой и наглядный, и даже облегчает читабельность кода (это свойство я отношу к субъективным, поэтому спорно, все зависит от восприятия конкретного человека).
Достоинства:
  1. Простой и наглядный.
  2. Прямой доступ ко всей иерархии приватных классов (в действительности они не приватные :-), а защищенные).
  3. Возможность доступа к публичному классу из приватного.
  4. Возможность реализовать систему сигналов и слотов для приватного класса и спрятать их от внешнего использования с помощью Q_PRIVATE_SLOT макроса (тема для отдельной статьи).

Хочу такую же, но с перламутровыми пуговицами! (с) х/ф «Брильянтовая рука»


Если я вас убедил, что Pimpl — это хорошо, и вы хотите попробовать и посмотреть как это работает, тогда давайте я вас посвящу в реализацию Pimpl по версии Qt.
Что нужно сделать:
1. Сделать forward — объявление вашего приватного класса перед объявлением публичного класса:
class MyClassPrivate;
class MyClass: public QObject
{ ..............


2. Далее в первом классе иерархии в защищенной секции необходимо объявить переменную, ссылающуюся на приватный класс:
protected:
  MyClassPrivate * const d_ptr;


Обратите внимание, что указатель константный, во избежании всяких там случайных нелепостей.
3. Также в защищенной секции (как первого класса иерархии так и всех его наследников) необходимо объявить конструктор, принимающий в качестве параметра приватный член класса. Это необходимо для того чтобы обеспечить возможность создание наследников приватного класса и использование их как приватных классов во всей иерархии:
protected:
  MyClass(MyClassPrivate &&d, QObject *parent);


4. В приватной секции объявляем макрос доступа к d-указателю:
Q_DECLARE_PRIVATE(MyClass);

Вот теперь разберемся что он из себя представляет:
#define Q_DECLARE_PRIVATE(Class) \
  inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } \
  inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } \
  friend class Class##Private;


Как мы видим тут объявляется функция d_func() и d_func() const, с помощью которой мы получаем указатель на приватный класс и константный приватный класс соответственно. Причем получаем его уже приведенным к типу приватного класса этого объекта. Ну и объявляем наш приватный класс другом для публичного.
Также существует макрос Q_DECLARE_PRIVATE_D. Разница в том что в качестве второго параметра вы указываете переменную d-указатель, таким образом вместо d_ptr в качестве D-указателя может быть использована переменная с любым именем. Но название функции d_func остается неизменным.
Реализация макроса выглядит таким образом:
#define Q_DECLARE_PRIVATE_D(Dptr, Class) \
  inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(Dptr); } \
  inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(Dptr); } \
  friend class Class##Private;


5. Теперь необходимо объявить наш приватный класс в .cpp или _p.h файле. Если вы предполагаете дальнейшее наследование от вашего класса или собираетесь использовать приватные слоты, то необходимо вынести все в отдельный _p.h файл. Если же нет, то достаточно объявить приватный файл в .cpp файле. Также имейте ввиду, что в .pro файле .h файл должен идти до _p.h файла и до всех файлов, которые его включат в себя. Это можно взять вообще за правило, так как облегчает работу компилятору.
class MyClassPrivate
{
  MyClassPrivate();
  virtual ~MyClassPrivate();

  int i;
}


Также рекомендую сделать деструктор виртуальным, если вы планируете строить иерархию приватных классов. Почему? Это тема отдельной статьи и таких статей уже написано достаточно, ну и конечно если не верите или не доверяете интернету, то обратитесь к Страуструпу, у него подробно излагается эта тема.
7. Реализация конструктора из защищенной секции будет выглядеть примерно таким образом:
MyClass::MyClass(MyClassPrivate &dd, QObject* parent)
       :QObject(parent)
       ,d_ptr(&dd)
{ .....


Ну и обычный конструктор с таким объявлением (обратите внимание на ключевое слово explicit, если не знаете что это и зачем, поинтересуйтесь — это полезно):
explicit MyClass(QObject * parent);

Будет выглядеть таким образом:
MyClass::MyClass(QObject * parent)
     :QObject(parent)
     ,d_ptr(new MyClassPrivate())
{........


В наследнике реализация такого конструктора будет выглядеть таким образом:
MyClassDerived::MyClassDerived(QObject * parent)
     :MyClass(*new MyClassDerivedPrivate(),parent)
{........


Как вы видите соответствующий конструктор наследника передает экземпляр своего приватного класса во все базовые классы по цепочке иерархии наследования (так же устроено и в Qt иерархии классов; самым первым классом в иерархии приватных классов является QObjectData, который содержит в себе родителя, состояние объекта и другие базовые свойства).
8. Для доступа к экземпляру приватного класса из метода публичного класса существует макрос Q_D().Вот что он из себя представляет:
#define Q_D(Class) Class##Private * const d = d_func()
Как вы видите мы получаем константный указатель на наш приватный класс в виде переменной «d» (вот он D-указатель :-)!!! ).
int MyClass::foo() const
{
  Q_D(const MyClass);
  return d->i;
}


Обратите внимание, что в константных методах необходимо в Q_D макросе дописывать const перед именем класса, чтобы получить константный указатель на константный экземпляр приватного класса (если вас эта формулировка напугала или не до конца ясна, обратитесь к документации по «const», поверьте — это очень важно ).
9. Теперь погрузимся глубже. Разрешите представить еще одного зверя ;-): Q-указатель. Q-pointer (он же Q-указатель) — это тот же D-pointer (он же D-указатель), только с точностью наоборот. Он служит для доступа из методов приватного класса к экземпляру публичного (используется обычно в тех случаях, если логика тоже вынесена в приватный класс, или планируется это сделать в дальнейшем по цепочке иерархии).
Для его реализации необходимо в самом первом классе приватной иерархии объявить переменную-указатель на базовый класс:
class MyClassPrivate
{
public:
  MyClassPrivate();
  virtual ~MyClassPrivate();

  int i;
  MyClass *q_ptr;
}


И во всех классах иерархии объявить макрос Q_DECLARE_PUBLIC, в который планируется использовать Q-указатель.
class MyClassPrivate
{
  Q_DECLARE_PUBLIC(MyClass);
public:
  MyClassPrivate();
  virtual ~MyClassPrivate();

  int i;
  MyClass *q_ptr;
}


Вот что из себя представляет макрос Q_DECLARE_PUBLIC:
#define Q_DECLARE_PUBLIC(Class)                  \
  inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
  inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
  friend class Class;


Как вы можете видеть, все тоже самое, как и в Q_DECLARE_PRIVATE, кроме названий. Ну и макроса для альтернативного названия q_ptr, наподобие Q_DECLARE_PRIVATE_D нет.
Важно: не забудьте позаботится во всех конструкторах первого класса публичной иерархии о инициализации переменной q_ptr:
MyClass::MyClass(QObject * parent)
     :QObject(parent)
     ,d_ptr(new MyClassPrivate())
{
  Q_D(MyClass);
  d->q_ptr = this;
.......
}



10. Для доступа из методов приватного класса к публичному классу (например чтобы сделать вызов сигнала публичного класса ) существует макрос Q_Q, вот как он выглядит:
#define Q_Q(Class) Class * const q = q_func()


Логика та же что и для D-указателей, и те же правила. Ну и в коде он будет выглядеть таким образом:

void MyClassPrivate::foo()
{
  Q_Q(MyClass);
  q->foo();
  emit(q->signal(i));
}



Заключение.



Имейте ввиду, что все эти макросы не являются частью публичного API, и могут быть в любой момент изменены. Но могу вас успокоить. Во первых это всё является настолько базовым фундаментом, что как минимум поменяется это к новой мажорной версии, но в этом случае все равно придется портировать приложение. А во вторых, очень многие крупные проекты используют эти макросы: например KDE. Ну а если вы являетесь убежденным параноиком и никому не доверяете, то можете объявить в своем глобальном файле похожие макросы, изменив их имя и использовать их в коде, тогда боятся точно нечего (кроме изменения поведения компиляторов по отношения к макросам :-), ибо настоящему параноику всегда есть чего боятся :-) ).
Также имейте ввиду что в моем примере я наследуюсь от QObject, который использует те же макросы для построения своей иерархии как публичных так и приватных классов. Но моя иерархия приватных классов ничего общего не имеет с приватной иерархией классов Qt. Они стоят в стороне и не мешают друг-другу. Так как я перекрыл переменную d_ptr в своем классе, и для всех наследников от моего класса d_ptr будет указателем на мою иерархию, а для QObject нет. Для него d_ptr будет Qt иерархией приватных классов (точнее указателем на QObjectPrivate).
Может возникнуть резонный вопрос, а почему бы наш приватный класс не унаследовать от QObjectPrivate. Ответ: можно, но во первых потеряется бинарная совместимость с библиотекой Qt(нужно будет иметь нашу библиотеку для каждой версии приватной реализации в Qt (она тоже меняется, QWidget точно) ).И вторым аргументом против является то, что для сборки нашей библиотеки потребуются приватные заголовочные файлы библиотеки Qt, которые находятся в папке src исходников, а не include установленных библиотек Qt. Да и трудно себе представить зачем это нужно(может кто-нибудь представит контр-аргументы, буду рад).

В дальнейшем я вам расскажу еще немного интересных вещей:
что такое QFlag, в чем его преимущество и что с ним едят.
Правила оформления кода в Qt-style.
Как реализовать Implicit Sharing и что такое Shared D-pointer.
Полезные макросы в QT.
Qt Creator снаружи и изнутри.
Как с минимальными усилиями написать приложение для 7 платформ.
И много всего другого интересного.

PS: Новые и интересные статьи я собираюсь выкладывать на своем сайте erudenko.com, правда на буржуйском языке. По возможности буду это переводить на русский и выкладывать здесь.

PPS: Приношу свои извинения за мой русский язык, возможные неверные обороты речи и ошибки (как орфографические так и синтаксические) — это не мой родной язык :-)

Фуууууух, ну вот вроде и все. В качестве дополнения простой пример реализации, а то я написал много букв, а на примере всегда наглядней:

.pro файл:
  1. TEMPLATE = lib
  2. HEADERS += myclass.h \
  3.   myclass_p.h \
  4.   myclassderived.h \
  5.   myclassderived_p.h
  6. SOURCES += myclass.cpp \
  7.   myclassderived.cpp


файл myclass.h:

  1. #ifndef MYCLASS_H
  2. #define MYCLASS_H
  3.  
  4. #include <QObject>
  5.  
  6. class MyClassPrivate;
  7. class MyClass : public QObject
  8. {
  9. Q_OBJECT
  10. public:
  11.   explicit MyClass(QObject *parent = 0);
  12.   int foo() const;
  13. signals:
  14.   void signal(int);
  15. protected:
  16.   MyClassPrivate * const d_ptr;
  17.   MyClass(MyClassPrivate &dd, QObject * parent);
  18. private:
  19.   Q_DECLARE_PRIVATE(MyClass);
  20. };
  21.  
  22. #endif // MYCLASS_H


файл myclass_p.h
  1. #ifndef MYCLASS_P_H
  2. #define MYCLASS_P_H
  3. #include "myclass.h"
  4.  
  5. class MyClassPrivate
  6. {
  7.   Q_DECLARE_PUBLIC(MyClass);
  8. public:
  9.   MyClassPrivate();
  10.   virtual ~MyClassPrivate();
  11.  
  12.   void foo();
  13.  
  14.   int i;
  15.   MyClass * q_ptr;
  16. };
  17.  
  18. #endif // MYCLASS_P_H


файл myclass.cpp
  1. #include "myclass.h"
  2. #include "myclass_p.h"
  3.  
  4.  
  5. MyClassPrivate::MyClassPrivate()
  6. {
  7.   i = 5;
  8. }
  9.  
  10. MyClassPrivate::~MyClassPrivate()
  11. {
  12.   //nothing to do
  13. }
  14.  
  15. void MyClassPrivate::foo()
  16. {
  17.   Q_Q(MyClass);
  18.   emit(q->signal(i));
  19. }
  20.  
  21.  
  22.  
  23. MyClass::MyClass(QObject *parent)
  24.   :QObject(parent)
  25.   ,d_ptr(new MyClassPrivate())
  26. {
  27.   Q_D(MyClass);
  28.   d->q_ptr = this;
  29. }
  30.  
  31. MyClass::MyClass(MyClassPrivate &dd, QObject * parent)
  32.   :QObject(parent)
  33.   ,d_ptr(&dd)
  34. {
  35.   Q_D(MyClass);
  36.   d->q_ptr = this;
  37. }
  38.  
  39.  
  40. int MyClass::foo() const
  41. {
  42.   Q_D(const MyClass);
  43.   return d->i;
  44. }


файл myclassderived.h
  1. #ifndef MYCLASSDERIVED_H
  2. #define MYCLASSDERIVED_H
  3. #include "myclass.h"
  4.  
  5. class MyClassDerivedPrivate;
  6. class MyClassDerived : public MyClass
  7. {
  8. Q_OBJECT
  9. public:
  10.   explicit MyClassDerived(QObject *parent = 0);
  11. signals:
  12.   void signal2(int);
  13. protected:
  14.   MyClassDerived(MyClassDerivedPrivate &dd, QObject * parent);
  15. private:
  16.   Q_DECLARE_PRIVATE(MyClassDerived);
  17. };
  18.  
  19. #endif // MYCLASSDERIVED_H


файл myclassderived_p.h
  1. #ifndef MYCLASSDERIVED_P_H
  2. #define MYCLASSDERIVED_P_H
  3.  
  4. #include "myclassderived.h"
  5. #include "myclass_p.h"
  6.  
  7. class MyClassDerivedPrivate: public MyClassPrivate
  8. {
  9.   Q_DECLARE_PUBLIC(MyClassDerived);
  10. public:
  11.   MyClassDerivedPrivate();
  12.   virtual ~MyClassDerivedPrivate();
  13.  
  14.   void foo2();
  15.   int j;
  16. };
  17.  
  18. #endif // MYCLASSDERIVED_P_H


файл myclassderived.cpp
  1. #include "myclassderived.h"
  2. #include "myclassderived_p.h"
  3.  
  4.  
  5.  
  6. MyClassDerivedPrivate::MyClassDerivedPrivate()
  7. {
  8.   j=6;
  9.   i=7;
  10. }
  11.  
  12. MyClassDerivedPrivate::~MyClassDerivedPrivate()
  13. {
  14.  
  15. }
  16.  
  17. void MyClassDerivedPrivate::foo2()
  18. {
  19.   Q_Q(MyClassDerived);
  20.   emit(q->signal2(j));
  21.   emit(q->signal(j));
  22. }
  23.  
  24. MyClassDerived::MyClassDerived(QObject *parent)
  25.   :MyClass(*new MyClassDerivedPrivate(), parent)
  26. {
  27. //Empty
  28. }
  29.  
  30. MyClassDerived::MyClassDerived(MyClassDerivedPrivate &dd, QObject * parent)
  31.     :MyClass(dd, parent)
  32. {
  33. //Empty
  34. }
Евгений @rule
карма
157,0
рейтинг 0,7
Разработчик мобильных приложений
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (70)

  • +1
    Товарищ, вы — монстр :)
    Огромное спасибо за эту статью — с ней можно намного легче разбираться в коде Qt. Помнится, я сколько раз пытался понять, что значит этот Q_D, но так и не догнал :)
    Вроде как, там был еще забавный макрос Q_Q, который похож на плачущий смайлик.
    • 0
      Спасибо, старался.
      По поводу Q_Q — смотри пункт 9 — там он описан (Q-указатель (он же Q-pointer)).
  • 0
    Когда ковырял QTabWidget обратил внимание на всю эту кухню. Сейчас в Кутиме для синглтонов похожие штуки юзаем, но только в несколько упрощенном виде.
    ИМХО идеальный паттерн для сигнлтонов
    • 0
      Вот тут не очень понимаю? какое преимущество в реализации синглетонов это дает?
      Вот в implicit sharing например понятно: создаем копию текущего класса, а в качестве приватного берем указатель (вернее шаред указатель) на экземпляр того от которого копируемся, и держим до тех пор, пока не был вызван метод модификации, вот тогда происходит реальное копирование. Вот тут «кота» в руки, так сказать. А вот как с синглетону это поможет?
      • 0
        Ну чтобы скрыть от плагинов всё, о чем им знать нельзя.
        • +2
          хм. Вариант кстати. А если его сделать шаред. то вообще можно получить самоудаляемый синглетон.
          Нада подумать на эту тему. Для малоресурсных систем достаточно актуально.
          Типа не нуждается в нем никто — с глаз долой из памяти вон! :-)
          Сделаю брейншторм на эту тему, может хорошый подпаттерн появится :-)
          • +2
            Аха, можно и так. Идея хороша, а так как Qt часто работает в условиях ограниченных ресурсов, то будет полезно. Кстати в Qt 4.6 еще ряд хороших указателей появился, например QScopedPointer

            The QScopedPointer class stores a pointer to a dynamically allocated object, and deletes it upon destruction.

            Managing heap allocated objects manually is hard and error prone, with the common result that code leaks memory and is hard to maintain. QScopedPointer is a small utility class that heavily simplifies this by assigning stack-based memory ownership to heap allocations, more generally called resource acquisition is initialization(RAII).

            QScopedPointer guarantees that the object pointed to will get deleted when the current scope dissapears.

            doc.trolltech.com/4.6-snapshot/qscopedpointer.html
            • +1
              Т.е. они изобрели auto_ptr/scoped_ptr?
              • 0
                они много поинтеров изобрели. Их около 6 штук :-)
  • 0
    >В дальнейшем я вам расскажу еще немного интересных вещей:
    что такое QFlag, в чем его преимущество и что с ним едят.

    Обожаю эту штуку, особенно в инкарнации QFlags и макроса Q_DECLARE_FLAGS :)
    Можно резво минимизировать использование памяти и время на поиск и улучшить читабельность прог

    • 0
      у него еще есть ряд полезностей, особенно для кросс-платформенной рзаработки. Найду время — обязательно напишу.
    • 0
      да и плюс многие кто переходит с С++ на кьют (у нас в конторе это все «новенькие» :-) ибо на рынке труда практически нет кьют специалистов) начинают выдумывать велосипеды :-) А был один смешной кадр, он вообще заявил «без буста кодить нельзя», а когда я ему сказал что у нас на таргет платформы СТЛя нету, он был ошарашен и не мог понят как же ему теперь писать код :-) Ну в догонку можно сказать что было у него еще заявление такого рода «const — это балавство», после того как я у него поинтересовался, почему же он нехороший человек поубирал const из методов. Типа не собиралось у него :-)
      • +1
        Const баловство? О.о я видимо что-то непонял в этой жизни, это блин как минимум спасатель от глупых ошибок
      • 0
        Что это за платформа, где есть Qt, но нету STL?
        • 0
          это встраиваемые системы. Можно засунуть туда СТЛ, но в нашем случае он там не нужен.Там и так счет идет не на мегабайты а на сотни килобайт. Сто килобайт плюс — уже нужны внятные пояснения почему.
          • 0
            поэтому вы используете Qt? странный конечно выбор…
            • 0
              да именно поэтому. Все Qt библиотеке на устройстве у нас весят окол 5 метров.
              Есть еще варианты реализовать портируемую платформу для графического интерфейса без Иксов (нереально большой оверхед дают даже TinyX  и подобные ему)?
              А ну и чтоб это влазило в 5 метров?
              С учетом того что один сам по себе libstdc++ весит порядка полутора метров. Я молчу об возможных прослойках (DFb, Microwindows, tinyX) и библиотека виджетов сколько будет весить да и еще чтоб это с пол-тычка портировалось под какую-нибудь архитектуру с ядром 2.4 и процессором от «Дядя Вася Анлимитед». У нас уже наша система портирована под 6 различных платформ, плюс пару эксперементальных. Смею заметить на некоторых платформах используются gcc 2.x.x компиляторы.
              Или Вы думаете мы не проводили масштабный ресеч по поводу возможных вариантов? Был рассмотрен вариант даже OpenTV — у них приложение крутится в мультикаст потоке в виде карусели и загружается на клиента по сети по кусочкам (у НТВ на приставках такая штука стоит). Я даже немного покодил на этом кошмаре. Хотя идея просто колосальная.

              Я буду очень рад, если предложите вариант лучше.
              ЗЫ: Кьют дает нереально большие возможности оптимизации при графической отрисовке в QWS-windows system.
              ЗЫЫ: Плюс ко всему мы имеем огромный бонус в виде возможности кодинга и сборки приложения на десктопе и отображение в QVFb — что почти всегда соотвествет действительному результату, за исключением платформо-зависимых багов видео подсистемы на каждом отдельно взятом типе устройств.
              • 0
                Хм, а где вы работаете?
                • 0
                  достаточно неприметная небольшая компания SmartLabs (http://smartlabs.tv). Кстати есть один продукт популярный, который мой коллега написал — TsMuxeR называется. Служит для пересборки контейнеров для видео. Тоесть по факту можно из ts файла получить mpeg4 обычный без длительного перекодирования, либо собрать BlueRay диск. Ну вобщем вроде популярная утилитка.
                  Вообще мы занимаемся IpTv. Сейчас одну из наших поделок можно увидеть на приставках у StreamTv. Вот если у кого есть — это все сделано как раз на Qt. Причем Амина 110 достаточно старая и там стоит PowerPC 405 процессор с 16 Mb постоянной памяти (NORFLASH) и 32 оперативной. Туда мы вместили наше приложение операционку драйверы и кути и еще кучу поделок. Не могу выдавать тайны а на каком этапе у нас внедрение я не знаю.
                  • 0
                    Хм, очень интересно…
                    А вам там случайно не нужен студент с непонятным графиком работы, но готовый работать за еду?
                    =)
                    • 0
                      боюсь нет :-) компания маленькая, задачи емкие и у нас набирают только ведущих разработчиков. Простых даже не берут а про джуниоров я даже спрашивать боюсь :-)
          • 0
            А, в этом плане — имея qt, без большей части STL и некоторой части буста пожалуй действительно можно обойтись, мысль понял.

            Я просто подумал что у вас там STLPort не заводится, а Qt есть — это было бы странно :)
            • 0
              Стл естественно есть. Но он абсолютно не нужен и поддержка его отключена в нашей сборке Qt. лишние 200 кб знаете ли. Мы убрали СТЛ из устройств так как им абсолютно не пользуемся. Не нужен он вообще в нашем случае.
  • 0
    Еще вопросец, а как вы относитесь ко всяким динамическим свойствам(качествам, особенностям, незнаю как лучше перевести) aka property(""); и setProperty(«hello»,true);
    Мне вот лично очень понравилась эта фишка, можно без разбухания API увеличивать функционал, без лишней магии манипулировать объектами через яваскрипт и т.д.
    • 0
      Есть плюсы, есть минусы. Как минимум виджеты должны иметь их для того чтобы можно было реализовать плагин для дезайнера и для получения метаинформации из объекта.
      Тка что в некоторых случаях это необходимо, но в некоторых избыточно. Как минимум это увеличивает скорость доступа к свойствам.
      Если тема интересана — то попробую осветить ее поподробней.
      • 0
        > Как минимум это увеличивает скорость доступа к свойствам.

        Видимо имелось в виду время? Нуда, преобразовывать QVariant туда сюда требует некоторого времени, но в некоторых случаях, в основном в GUI, где нету обработки большого объема данных это очень применимо
        • 0
          естественно в гуи в качестве дергания свойства в ответ на реакцию нажатия — это очень гут.
          А вот использовать свойства для выполнения сортировке на милионном массиве данных — не очень хорошо :-)
          Всему есть свое место применения :-) Для этого думалка и нужна :-)
          • +1
            Ну так в некоторых случаях для обработки больших объемов данных и вовсе можно перейти на Си-style, у нас вот в проге парсилка json'а так и вовсе хоть и использует контейнеры Qt для хранения, но написана в чисто сишном стиле, поэтому работает реактивно, но заглядывать туда без подготовки конечно страшно :) В коде уже нету той стройности и красоты, все принесено в жертву в угоду скорости.
            • 0
              Ну я пару раз даже на асме писал :-) дело то такое :-)
  • 0
    /* Если иерархия классов «широкая» или «глубокая» получается более комплексная структура классов и тем самым повышается удобство «переиспользование — reuse» кода. */

    Просветите, что значит «комплексная структура классов»?
    • 0
      Это значит что публичный интерфейс у нас всегда в публичном классе а свойства объекта и логика в приватном. Это можно назвать функциоанльной структуризацией. А так как у нас этих классов целая иерархия то получаем целый комплекс, который построен по общему принципу — Pimpl. Да выражение действительно у меня получилось непонятным. Подумаю как сказать проще.
  • +3
    Кстати вот Соглашения по стилю написания кода в Qt. И спасибо за статью.
  • 0
    Эмс. Я, конечно, понимаю, что хардкорным Qt'шникам и C++'никам всё понятно, но без примера использования в заключении статьи как-то не очень понятно: а зачем это всё и как это использовать?

    И другая непонятка — а при чём тут бинарная совместимость? Как бы это, если вдруг поменяется интерфейс этого самого private-класса, то всё-равно генерируемый ассемблерный код должен поменяться. Ведь, d_func — просто вытаскивает указатель, а потом же в выражениях идёт работа с классом через этот указатель, и через интерфейс, задаваемый типом MyPrivateClass. Вроде, ведь, так. Чего я не понимаю? Объясните, please.
    • 0
      По поводу примера использования?
      ну вот давайте на примере Qt рассмотрим.
      Ребята разрабатывают библиотку или фреймворк (вернее набор библиотек, в мак ос для этого есть термин — «зонтик», а вот остальные не позаботились :-) ). Буду называть ее как одну библиотеку, пусть даже модульную.
      И так они выплевывают релиз 4.0.0 — все дружно начинают присать свои приложения для него и динамически с ним линкуются. Тут выходит библиотека 4.0.1 и баз все приложения которые были собраны с предыдущей версией просто тупо падают, сегментятса или просто отказываются запускатся. В этом случае прийдется пересобирать абсолютно все приложения использующие эту библиотеку, чтобы перелинковать.

      Это типо общее описание проблеммы. Qt одни из первых, кто в своих библиотеках обзаботился об ABI. Они гарантируют что все приложения собраные с любой из 4 версией библиотеку будут работать с любой другой библиотекой версии 4. Тоесть я собрал с Qt 4.6 а работать она будет с 4.1 (правда лишь в том случае, если вы не используете новые возможности, которых нет в 4.1. Но даже в этом случае можно разрулить ситуацию програмно, в Qt есть макросы чтобы узнать какой версии библиотеки сейчас в рантайме и либо сообщить юзеру что они сильно старые либо решить ту же задачу но менее оптимальным путем через другую реализацию).

      Вот Pimpl для этого и нужен. Что по поводу второй части вопроса. Да конечный машинный код библиотеки изменится, но!!! ABI публичного класса останется тот же. Изменится ABI приватного, но вы же линковались с ABI публичным. Так что все нормально.
      Вообще так если кратко, в любой программе (библиотека как один из вариантов) есть два интерфейса — API и ABI. Ну что такое API я думаю объяснять нет необходимости. Это функции с параметрами, переменные и тд. API используется в «Design Time» — при написании кода, а ABI в «Run Time». ABI позволяет ОС правильно запускать приложения и библиотеки и он зависит от ОС (Windows, Linux ...), архитектуры ( I386, PPC, SPARC) и от конкретной реализации (в линукс например есть две популярные реализации загрузчика — Elf и a.out, ABI у них разный). Все сказаное выше очень упрощено, в действительности все немножечко не так :-)

      Ну вот начал писать и оказалось что мыслей так много что необходимо выместить их в отдельной статье.
      • 0
        Хм… а разве a.out где-нить, кроме самописных лаб на Си используется? Вообще помоему требование бинарной совместимости это ещё и часть LGPL лицензии
        • +1
          Да на ядре 2.4 встречал системы с монопольной поддержкой a.out. Зачем это так сделано — сам удивлялся, но спросить было не с кого. :-)
          Вот по поводу LGPL — не знаю, все может быть. Я честно пытался прочитать LGPL, но всякий раз засыпал. Там все так нечетко написано. Нельзя вот было взять и вынести по пунктам очень кратко. Что-то вроде этого:
          1. Можно использовать в комерческом ПО.
          2. Можно линковать статически и динамически.
          3. Можете не выкладывать в общий доступ при изменении кишков.
          4…
          ну и так далее. То-есть для простых людей а не для юристов там разных. Для юристов пусть отдельный документ будет. Так как они любят — со всякими уточнениями изъянами и защитой от левопроходства :-)
      • 0
        Вот Pimpl для этого и нужен. Что по поводу второй части вопроса. Да конечный машинный код библиотеки изменится, но!!! ABI публичного класса останется тот же. Изменится ABI приватного, но вы же линковались с ABI публичным. Так что все нормально.

        А можно с этого места подробнее? Как в самой программе-то эти два — открытый и закрытый — класса используются? Верно ли я понимаю, что работа со вложенным объектом происходит через d_ptr. Или нет? Вся работа происходит исключительно через класс-обёртку? Но если так, то… Эмс. А чем это лучше чем просто некий стандартный интерфейсный набор функций, и различные реализации их в том же ELF Shared Object? Ну, или dll'ки? Где тут 'фишка'?
        • 0
          Ну упрощенно это так выглядит:
          foo.h

          class FooPrivate;
          class Foo
          {
          public:
            int somePublic();
            Foo();
          private:
             //FooPrivate *m_foo_private; вариант по старинке
             QScopedPointer<FooPrivate> m_foo_private; //но лучше теперь делать так
          }

          foo.cpp

          class FooPrivate
          {
            int someMethod ()
            {
               return 1;
            };
          }

          Foo::Foo() : m_foo_private(new FooPrivate)
          {
          }

          int Foo::somePublicMethod()
          {
            return m_foo_private->someMethod();
          }


          * This source code was highlighted with Source Code Highlighter.

          • 0
            ps
            только не забудьте в обьекте соблюсти все необходимые требования от QScopedPointer'а а то иначе не соберется
        • 0
          Да все обращения происходят через d_ptr. Не вся работа происходит через класс-обвертку. Объем этих работ вы можете устанавливать сами, есть три варианта:
          1. Все приватные данные храните в приватном классе — вся логика в публичном классе.
          2. Все приватные данные храните в приватном классе — та логика, которая перекрывается в дереве наследования выносится в приватный класс а собственная остается в публичном.
          3. Вся логика и данные уносятся в приватный класс. — в основном Qt придерживаются этого подхода.
          3 метод используется при обычной схеме разработки ПО, когда сначала проектируют модель, интерфейсы в виде схемы (обычно на UML)и потом на основании этого пишется код. Тоесть какие методы должны быть в публичном классе на момент начала написания кода уже известны. А как это реализовано уже прячется в приватный класс. И изменение этой реализации никак снаружи видно не будет.
          В итоге с публичным классом можно делать некоторые изменения. Но очень аккуратно. Допустим добавить в конец виртуальный метод. От этого таблица виртуальных методов не поломается, просто в конец добавится метод.

          Вот не понял что имеется ввиду под выражением «стандартный интерфейсный набор функций»
          Почему не реализовать через dll или so? потому как ломается ABI. Вам всегда нада иметь публичную библиотеку подходящую под ваши приватные библиотеки. В принципе их можно таскать вместе. Но зачем ??? Тут как раз оверхед достаточно большой получается при подходе использовать пару dll. Pimpl очень простой — дописал пару макросов и сделал дополнительный хедер (не обязательно).
          • 0
            Вот не понял что имеется ввиду под выражением «стандартный интерфейсный набор функций»
            Почему не реализовать через dll или so? потому как ломается ABI. Вам всегда нада иметь публичную библиотеку подходящую под ваши приватные библиотеки.


            Вот я и пытаюсь выяснить, а чем именно ломается ABI-то? ABI же — это calling convention для функций (подпрограмм), методы же, если смотреть в коде, это обычные функции, у которых просто this идёт первым аргументом (ну, условно говоря, на самом деле, это специальный регистр, но не суть). Так вот… Интерфейс спроектирован, public-методы определены, то есть, определён набор функций для взаимодействия с объектом. Ну и всё, что ещё нужно? Если класс будет реализовывать именно такой интерфейс, то ABI никак не должно ломаться. Как бы, в этом вся суть дизайна С++. Иначе, у меня программ *цать работают весьма загадочным образом, потому что некоторые классы реализованы в *.so, которые периодически обновляются, и без всяких pimpl всё линкуется.

            Или тут речь идёт о той ситуации, когда нужно часто менять интерфейсы в классе, реализующем функциональность?
            • 0
              this не обязательно идет первым — не забывайте что есть little endian и big endian архитектуры. Но это так к слову. Теперь по сути.
              ABI не только calling conversion. Естественно, если интерфейс менятся не будет, а будет менятся только код в cpp файле — то все будет очень хорошо. Имейте ввиду что это не относится к коду, который находится в заголовочном файле. Кстати одна из причин почему не нужно писать его в заголовочном файле, даже если он пустой, потому как надумаете наконец его реализовать, то потеряете эту злощастную бинарную совместимость.
              Если вы допустим добавите виртуальный метод в класс и поставите его на первом месте — то виртуальные адреса всех других методов сдвинутся и ваш код, который был слинкован попадет уже в другое место. Если же вы добавите виртуальный метод после всех остальный, то все будет хорошо.
              Pimpl — это не панацея и не единственный вариант сохранить бинарную совместимость, я уже приводил ссылку на KDE TechBase где рассказывается что можно делать и останется бинарная совместимость и в каких случаях она поломается. Вот эта статья: techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++#The_Do.27s_and_Don.27ts

              Я планируя посвятить этому тему. Так как это необходимо знать каждому, кто разрабатывает библиотеки для «общего пользования». И расскажу подробней обо всех ньюансах.

              Часто встречаются проблеммы с бинарной совместимостью. Как пример — Qt на линукс в бинарниках идет без поддержки Phonon, потому что ни GStreamer ни Xine (которые сейчас поддерживаются как бэкэнды) не заботятся о бинарной совместимости. Поэтому необходимо кути собирать непосредственно с теми библиотеками, которые стоят именно на вашем компьютере. А самое интересно начинается когда вы собираетесь поставлять свою программу со своими кастомными библиотеками Qt на Linux — тут уже наступает «ахтунг», либо тянуть GStreamer с собой или Xine. Либо в качестве зависимостей тянуть девелоперские пакеты для одной из этих библиотек и собирать фонон уже в скрипте при установке либо требовать чтобы пользователь об этом позаботился сам ну или использовать системный фонон и прописать его в зависимости. Все варианты не очень. И все из-за того что кто-то не позаботился о бинарной совместимости. Хотя я слышал что и Xine и GStreaner пообещали впредь относится серьезней и далее ситуация должна изменится.
              Проблемма бинарной совместимости есть и ее просто необходимо знать, но увы очень мало документации по этой теме. Так что я собираюсь пятую статью посвятить именно этому вопросу.
              • 0
                Угу. Понятно. Thnx за разъяснение. Только эта :) big и little endian — это к порядку битов в машинном слове.
              • 0
                Вот кстати когда поставил Qt 4.6 то радостно наступил на проблему бинарной совместимости с Xine'ом. Пришлось старую версию бэкенда ставить, ибо пересборкой небыло никакого желания заниматься
  • 0
    Хорошая статья, однако:
    1) фраза «Одним из самых основных достоинств — это сохранение бинарной совместимости» не согласована, подправьте плиз.
    2) там ещё скобочки впритык к словам встречаются

    3) Хорошо бы упомянуть для понятности, что всякие беседы про скорость компиляции за счёт сокращения объёма включаемых библиотек — это касается С-образного подхода к модульности с их вечными инклюдами всяких там *.h-файлов, которые не требуют инклюдов реализации. А тех, кто пишет на других языках — типа наследников Паскаля, например, или php — в котором линкуемых библиотек нету, и всё что есть, инклюдится целиком, а не заголовками — этот бонус ну совершенно не касается. То есть, следование такому паттерну им не так полезно, и патерн получается более ограниченной области применения, чем у Вас описано. Вообще не помешало бы поделить в части описания бонусов подхода, какие из них проявляются только в С, а какие действительно универсальны.
    • 0
      Спасибо за критику. Первые два пункта абсолютно согласен.

      Вот третий — блог посвящен Qt, речь идет об внутренностях и подходах Qt и речь идет именно о чисто Qt-style подходу реализации этого паттерна. Речь идет о паттерне Pimpl d-pointer. Ввиду вышеуказанных обстоятельств можно предположить что это все касается исключительно C++ или гибридных языков, или языков которые в себя могут включать C++ (например тот же самый Objective-C).

      По поводу php — я даже не знаю зачем этот паттерн там вообще нужен :-). Может потому-что я в php не силен, а может потому-что он там действительно не нужен. Интерпретатор и ABI вещи не совместимые :-)
      • 0
        Ну я думаю, чтобы можно было разнести фронтенды и бэкенды, типа публичный класс — фронтенд, а приватный — бэкенд, но мне кажется в контексте php это конь пятиногий
        • 0
          ну вот и я о том. Pimpl по версии Qt интересен там где интерфейс и реализация вынесены в отдельные файлы. Ну кстати вот в Objective-C — это тоже не очень то нужно. Ибо там все по другому и таких проблемм не возникнет :-) берешь и спрашиваешь у класса:«а скажи ка мне тварищь, а есть ли у тебя метод „а“? Если есть то дерни ка мне его с вот таким вот списком параметров.Если нету то добавь ка его с вот такой реализацией и дерни его с вот таким вот списком параметров :-) ».
      • 0
        Qt портирован под многие языки, и пара слов про сишную направленность статьи и описываемых бонусов лично меня бы избавила от целого ряда недоумений.
        • 0
          Ну вообще то Qt не был портирован ни на один из языков. Он существует, существовал и будет существовать только на C++. Что касается QtJambi, PyQt, QT-D, Qt# и им подобных — это биндинг — тоесть привязка. Само же Qt на С++. В большей степени эти биндинги используются как инструменты для этих хост-языков для реализации GUI (отсюда и миф, что Qt — это GUI Framework).
          Но для ясности допишу в начале.
  • 0
    Поправьте пример с форвардом, а то у вас там идет форвард привата, а на следующей строке объявление привата (должен быть паблик).

    Статья хорошая, спасибо. В свое время приходилось с этим разбиратсья по исходникам куте и документам от троллтеха, а тут все собрано в одном месте (есть куда направить спрашивающих что-же такое этот д-поинтер).
    • 0
      спсибо за поправку.
  • 0
    Интересная метода с параллельными иерархиями.
    Жаль только, что в макросе Q_DECLARE_PRIVATE используется reinterpret_cast.
    Мне почему-то даже кажется, что есть способ извратиться с шаблонами и получить аналогичный результат без макросов и безопасный по отношению к типам.

    Немного занудства:
    Смысл в том что объявляется класс XXXPrivate и переменная публичного класса в защищенной секции. В отдельном заголовочном файле или в .cpp файле уже пишется объявление приватного класса

    Во втором предложении точнее будет сказать «определение» (definition против declaration).

    А вообще, интересная, похоже, штука — эта Qt. Надо будет обязательно тоже попробовать.
    • 0
      в интернете есть переписка по поводу почему именно reinterpret_cast вот тут
      lists.trolltech.com/qt-interest/2007-09/thread00325-0.html

      за поправку спасибо.
    • 0
      Не так этот reinterpret_cast и страшен, как кажется, его можно абсолютно спокойно использовать, например, если указатель просто является ключем в каком нить хэш массиве, тогда к указателю нет нужды обращаться и абсолютно всеравно, на что он указывает, а вот компилятору в общем то нет, тут как раз reinterpret_cast и спасает, я через него логику убирания удалённых элементов из хэш массива делал. Объект при удалении выбрасывает сигнал, в котором есть указатель на область памяти, где он находился, а потом уже можно его из массива смело удалить
      • 0
        никто не говорит что reinterpret_cast так страшен, просто нада внятно думать прежде чем им пользоватся. Вот вроде у Страуструпа читал что такое длинное название вместо сишых простых скобок именно для того чтобы человек четко понимал что он делает. Если бы reinterpret_cast был явным злом его бы и небыло бы :-)
        • 0
          А он не совсем сишный каст, он еще константность контролирует хоть как то. Но его нужно делать в тех случаях, когда точно уверен, что перед тобой за объект, а то можно получить очень интересные эффекты и падение будет далеко не самым скучным из последствий
  • 0
    Может просто ночь уже, но вроде лучше так:
    Это необходимо для того чтобы обеспечить возможность создания наследников приватного класса и использования их как приватных классов во всей иерархии

    И еще, почему бы не отдать инициализацию q_ptr приватному классу:
    MyClass::MyClass(QObject* parent)
        :QObject(parent)
        ,d_ptr(NULL)
    { 
        d_ptr = new MyClassPrivate(this);
        .......
    }
    
    С соответствующим конструктором MyClassPrivate.
    • 0
      Раз уж на то пошло, можно делать сразу:

      MyClass::MyClass(QObject* parent)
        : QObject(parent), d_ptr(new MyClassPrivate(this))
      { ... }
      
      ...
      
      MyClassPrivate::MyClassPrivate(MyClass* parent)
        : q_ptr(parent)
      { ... }
      • 0
        ну как по мне, так то что написал std, и Вы cyberbobs, не очень удобно, так как требует дополнительного параметра в конструктор приватного объекта.

        По производительности никаких бонусов тут нет. Но строки
        Q_D(MyClass);
        d->q_ptr = this;


        будут присутствовать только в конструкторе самого базового объекта, в остальных же ничего подобного фигурировать не будет. А параметр конструктора приватного класса придется передавать во всей иерархии.
        Поэтому в моем подходе конструктор наследника будет выглядеть таким образом:
        MyClassDerived::MyClassDerived(QObject *parent)
        :MyClass(*new MyClassDerivedPrivate(), parent)


        А в вашем:
        MyClassDerived::MyClassDerived(QObject *parent)
        :MyClass(*new MyClassDerivedPrivate(this), parent)


        поэтому достаточно спорно тут как лучше, мне кажется в моем варианте лучше, так как необязательно создавать конструктор, пусть будет конструктор по умолчанию, если необходимо просто проинициализировать q_ptr (и то если он нужен). А если он нужен, то это действие делается только в конструкторе одного объекта а не тянется вся эта эпопея с инициализацией в них. как минимум снижается риск, что в конструктор подчиненного объекта будет помещен не тот объект. То есть в моем случае код более «ошибкостойкий», опять же ИМХО. Хорошо, что С++ дают простор для фантазии :-)
        • 0
          По большей части, у меня в коде до сокрытия деталей реализации доходит тогда, когда в Private-классе нужно в любом случае делать инициализацию ряда его полей. В конечном итоге — это сугубое дело вкуса :)
        • 0
          бонус есть: явно указываем в приватном классе, что ему нужен «родитель» и указываем ему «родителя» сразу при создании
          • 0
            ну как раз это нужно только самому первому приватному классу, все остальные унаследуют у него. Получается позаботиться об этом нужно всего один раз, и особенно это актуально, если наследников будет писать кто-нибудь другой (например индус), и будет жаловаться что у него там не работает, а он допустим в приватный класс передал (0), так как было лень читать документацию что туда нужно ложить this. Так как нам в любом случае нужно в приватный класс положить именно этот this то не вижу необходимости это делать везде в иерархии, лучше сделать один раз в вершине дерева и забыть про это.
          • 0
            да вот кстати как это делается в Qt:
            qt.gitorious.org/qt/qt/blobs/master/src/corelib/kernel/qobject.cpp#line756

            как мы видим стиль немного другой, но в общем подход тот-же.
      • 0
        … и получить варнинг за использование this в списке инициализации. Не фатально конечно, но неприятно
        • 0
          не понял где мы тут получаем ворнинг? и за что?
          • 0
            в строчке инициализации d_ptr, такой вот:
            warning C4355: 'this': used in base member initializer list
            • 0
              А ну это естественно если идти не по мной предложенному пути. В моем случае опять же такой штуки не будет (вернее Qt-style way).
              Проверю это в GCC. Вроде он молчит в данном случае даже при -Wall

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.