Как определить и реализовать новый GObject?

Шаблонный заголовочный код
Шаблонный код
Конструирование объекта
Уничтожение объекта
Объектные методы
Невиртуальные общие методы
Виртуальные общие методы
Виртуальные закрытые методы
Формирование цепочки

Конечно, в основном люди задаются вопросом: как реализовать подкласс GObject. Иногда потому что нужно создать собственную иерархию класса, иногда потому что нужен подкласс одного из виджетов GTK+. Этот раздел сфокусирован над реализацией подтипов GObject. Пример исходного кода связанного с этим разделом может быть найден в исходниках документации, в каталоге sample/gobject:

Шаблонный код заголовка

Первый шаг написания кода для вашего GObject - написать типовой заголовок который содержит необходимые декларации типа, функций и макросов. Каждый из этих элементов является соглашением которому следует не только код GTK+, но также большинство пользователей GObject. Подумайте дважды прежде чем нарушать правила заявленные ниже:

  • Если пользователи немного привыкли к коду GTK+ или любому коду Glib, они будут удивлены вашими нарушениями и привыкание к тому что вы нарушили соглашения займёт время (деньги) и будет их раздражать (это нехорошо)

  • Вы должны понимать что возможно эти соглашения разрабатывали достаточно умные и опытные люди: возможно они были по крайней мере частично правы. Попробуйте преодолеть своё эго.

Выберете согласованное имя для ваших файлов заголовков и исходного кода и придерживайтесь его:

  • использовать тире для отделения префикса от типового имени: maman-bar.h и maman-bar.c (это соглашение используется Nautilus и большинством библиотек GNOME).

  • использовать символ подчеркивания для разделения префикса и типового имени: maman_bar.h и maman_bar.c.

  • Не отделять префикс от типового имени: mamanbar.h и mamanbar.c. (это соглашение используется в GTK+)

Я лично предпочитаю первое соглашение: оно позволяет легче читать имена файлов людям со слабым зрением, таким как я.

Когда вам необходимы некоторые закрытые (внутренние) декларации в нескольких (под)классах, вы можете определить их в закрытом заголовочном файле который часто называют добавляя ключевое слово private к общему имени заголовочных файлов. Например, можно было бы использовать maman-bar-private.h, maman_bar_private.h или mamanbarprivate.h. Обычно такие закрытые заголовочные файлы не устанавливаются.

Основные соглашения для любых заголовков GType описаны в “Conventions”. Большинство кода основанного на GObject также придерживается следующих соглашений: выберете одно из них и придерживайтесь его.

  • Если вы хотите задекларировать тип с именем bar и префиксом maman, имя типового экземпляра MamanBar и его класс MamanBarClass (имена регистрозависимы). Общепринято декларировать это кодом подобно следующему:

    /* * Информация о правах и лицензионных соглашениях. */ #ifndef MAMAN_BAR_H #define MAMAN_BAR_H /* * Потенциально, включите другие заголовочные файлы от которого зависит этот. */ /* * Напечатайте макроопределения. */ typedef struct _MamanBar MamanBar; typedef struct _MamanBarClass MamanBarClass; struct _MamanBar { GObject parent; /* члены экземпляра */ }; struct _MamanBarClass { GObjectClass parent; /* члены класса */ }; /* используемый MAMAN_BAR_TYPE */ GType maman_bar_get_type (void); /* * Определения метода. */ #endif

  • Большинство GTK+ типов декларируют свои закрытые поля в общих заголовках с комментарием /* private */, рассчитывая на благоразумие пользователей не пытаться играть с этими полями. Поля не отмеченные как закрытые рассматриваются как общие по умолчанию. Комментарий /* protected */ (с семантикой C++) также используются, главным образом в библиотеке GType, в коде написанном Tim Janik.

    struct _MamanBar { GObject parent; /*< private >*/ int hsize; };

  • Весь код Nautilus и большинство библиотек GNOME используют закрытые косвенные члены, как описано Herb Sutter в его статьях Pimpl (смотрите Compilation Firewalls и The Fast Pimpl Idiom : он суммировал разные проблемы лучше меня).

    typedef struct _MamanBarPrivate MamanBarPrivate; struct _MamanBar { GObject parent; /*< private >*/ MamanBarPrivate *priv; };

    Помните

    Не используйте имя private, так как это зарегистрированное ключевое слово c++.

    Закрытая структура определяемая в .c файле, инстанциируется в объектной функции init а уничтожается в объектной функции finalize.

    static void maman_bar_finalize (GObject *object) { MamanBar *self = MAMAN_BAR (object); /* do stuff */ g_free (self->priv); } static void maman_bar_init (GTypeInstance *instance, gpointer g_class) { MamanBar *self = MAMAN_BAR (instance); self->priv = g_new0 (MamanBarPrivate,1); /* do stuff */ }

  • Подобная альтернативная возможность, начиная с версии Glib 2.4, определить закрытую структуру в .c файле, декларируя её как закрытую структуру в maman_bar_class_init используя g_type_class_add_private. Вместо распределения памяти в maman_bar_init указать закрытую область памяти сохранённую в экземпляре позволяя удобный доступ к этой структуре. Таким образом закрытая структура будет прикреплена к каждому вновь созданному объекту системы GObject. Вам не нужно освобождать или распределять закрытую структуру, только объекты или указатели которые она может содержать. Другое преимущество этой версии перед предыдущей в том что это уменьшает фрагментацию памяти, так как общие и закрытые части памяти экземпляра распределяются одновременно.

    typedef struct _MamanBarPrivate MamanBarPrivate; struct _MamanBarPrivate { int private_field; }; static void maman_bar_class_init (MamanBarClass *klass) { ... g_type_class_add_private (klass, sizeof (MamanBarPrivate)); ... } static void maman_bar_init (GTypeInstance *instance, gpointer g_class) { MamanBar *self = MAMAN_BAR (instance); self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MAMAN_BAR_TYPE, MamanBarPrivate); /* do stuff */ }

Наконец, есть различные соглашения включения заголовков. Снова, выберете одно и придерживайтесь ему. Я лично использую любое из двух без разницы, в зависимости от кодовой базы в которой работаю: согласованное правило.

  • Некоторые люди добавляют заголовочный файл с множеством директив #include для перемещения по всем заголовкам необходимым для компиляции кода клиента. Это позволяет коду клиента просто включить #include "maman-bar.h".

  • Другие не делают никаких #include и ожидают что клиент включит #include самостоятельно заголовочные файлы в которых нуждается перед включением вашего заголовочного файла. Это ускоряет компиляцию потому что минимизирует работу препроцессора. Это может быть использовано вместе с повторной декларацией определённых неиспользуемых типов в клиентском коде для минимизации времени компилирования зависимостей и таким образом укорить компиляцию.