gtkmm - це вільний об'єктно-орієнтований C++ інтерфейс для популярного GUI фреймворку GTK, що поширюється за ліцензією LGPL.
Основне призначення gtkmm
полягає в отриманні усіх переваг ООП:
- наслідуванні класів GTK
- скороченні конструкцій коду програми (порівняно з ієрархічною реалізацією аналогічної функціональності засобами C)
- вбудованих інструментів перевірок та корекції типізації нащадків дерева віджетів та багато іншого.
Оскільки це абстрагований прошарок над стандартною бібліотекою GTK, при його використанні, може виникнути питання як саме працювати з пам'яттю відповідно документації, адже API gtkmm
має певні поліморфічні відмінності, без розуміння яких, є ризик витоку пам'яті або помилок при зверненнях.
Даний матеріал являє собою доповнений переклад офіційної сторінки Chapter 25. Memory management, який допоможе краще зрозуміти базові принципи роботи перед початком роботи з фреймворком: не писати зайві велосипеди для керування пам'яттю і не отримати замість переваг - segmantation fault.
Переклад базується на двох окремих статтях, які представлені розділами:
- Робота з віджетами (Widgets)
- Робота зі спільними ресурсами (Shared resources)
Робота з віджетами
Важливою відмінністю gtkmm-4.0
у порівнянні зі старішими версіями є те, що знищення батьківського віджета GTK більше не призводить до знищення його дочірнього віджета, коли той знаходиться в його контейнері. Якщо ви покладалися на таку поведінку в старішій версії, то відтепер, в gtkmm-4.0
потрібно спочатку видалити дочірній віджет, наприклад, через деструктор.
Робота з фреймворком GTK здебільшого передбачає створення та видалення контейнерів та їх дочірніх віджетів. Відповідно, розробник повинен самостійно контролювати виділення пам'яті, а також її очищення після того, як певний її віджет більше не використовується.
Бібліотека gtkmm
надає додаткові інструменти, які спрощують таку роботу.
Нижче розглянемо, які способи керування пам'яттю взагалі доступні в gtkmm
Класичне керування пам'яттю C++
Загалом, gtkmm
дозволяє програмісту контролювати тривалість життя будь-якого віджета так само, як і будь-якого іншого об'єкта C++. Ця гнучкість дозволяє використовувати:
- оператори
new
іdelete
для створення та знищення динамічних об'єктів - звичайні деструктори (які виконуються автоматично, коли клас знищується)
- локальні екземпляри об'єктів (які знищуються, коли екземпляр виходить з області видимості)
Існують й інші способи, але розглянемо тільки деякі з них нижче.
Віджети в області класу
Якщо не потрібне динамічне виділення пам'яті, можна використовувати дочірні віджети в області батьківського класу. Однією з переваг таких віджетів є те, що керування пам'яттю зосереджено локально. Програма не ризикує отримати витоки пам'яті тому, що дані зберігаються на стеку.
#include <gtkmm/button.h>
#include <gtkmm/window.h>
class Foo : public Gtk::Window
{
private:
Gtk::Button theButton; // буде знищений, коли об'єкт Foo буде знищений
};
Недоліком використання віджетів в області класу - є розкриття реалізації класу раніше, ніж його інтерфейсу в заголовковому файлі.
Віджети в області функції
Якщо програмісту не потрібен віджет в області всього класу, можна також використовувати віджет в локальній області функції. Перевагою такого підходу полягає у підвищеній прихованості даних та зменшенні залежностей:
{
Gtk::Button aButton;
aButton.set_visible(true);
...
app->run();
}
Однак ця техніка рідко буває корисною. Більшість віджетів не можуть бути безпечно створені до того, як програма буде зареєстрована або активована. Їх також не можна безпечно видалити після того, як програму було активовано типовими для GTK 4 засобами Gtk::Application::run()
або Gtk::Application::make_window_and_run()
.
Динамічна алокація з new
і delete
Доволі часто, у програмах використовується динамічна пам'ять:
auto pButton = new Gtk::Button("Test");
// зробіть щось корисне з pButton
delete pButton;
- на цьому прикладі, програміст вручну видаляє
pButton
, після використання, щоб запобігти витоку пам'яті.
Але у даному випадку, краще надати перевагу автоматичному способу знищення батьківським класом своїх нащадків, створюючи їх за допомогою інтегрованого методу Gtk::make_managed()
.
Це не є обов'язковим, оскільки також можна використовувати оператори new
і delete
, але сучасний стиль C++ відмовляється від них на користь більш безпечних моделей керування пам'яттю, тому краще створювати віджети за допомогою спеціального метода і потім видалити їх автоматично.
Тому gtkmm
й надає метод, який об'єднує створення та позначення в один крок, дозволяючи не писати new
взагалі і чітко для інших розробників виразити намір створити саме віджет з автоматизованим керуванням пам'яті.
Таким чином, коли за допомогою Gtk::make_managed()
ми надаємо певному класу керування його дочірнім віджетом, такий батьківський клас називається керуючим віджетом.
Керуючий віджет
Оскільки тепер ми використовуємо Gtk::make_managed()
, то можемо дозволити контейнеру віджета контролювати, коли його дочірній віджет буде знищено.
Розглянемо практичне використання на прикладі append()
Динамічна алокація з make_managed()
і append()
Щоб делегувати керування тривалістю життя віджета його контейнеру, просто створімо його за допомогою Gtk::make_managed()
і спакуймо результат до батьківського контейнера за допомогою Gtk::Box::append()
:
MyContainer::MyContainer()
{
auto pButton = Gtk::make_managed<Gtk::Button>("Test");
append(*pButton); // додати *pButton до MyContainer
}
-
замість
Gtk::Box::append()
можна використовувати й інші методи - приклад переходу реальної програми на модель керуючих віджетів, можна подивитись тут
Коли об'єкти типу MyContainer
знищуються, кнопка також буде видалена. Більше не потрібно видаляти pButton
, щоб звільнити пам'ять; контроль за її видаленням було делеговано об'єкту MyContainer
.
Робота зі спільними ресурсами
Деякі об'єкти, такі як Gdk::Pixbufs
і Pango::Fonts
, отримуються зі спільного сховища. Тому ви не можете створювати свої власні екземпляри. Ці класи зазвичай успадковуються від Glib::Object
.
Gdk::Pixbuf
може бути створено лише за допомогою функції create()
:
auto pixbuf = Gdk::Pixbuf::create_from_file(filename);
Замість того, щоб вимагати від вас посилатися на ці об'єкти, gtkmm
повертає розумний вказівник (smart-pointer) Glib::RefPtr<>
-
наприклад, бібліотека cairomm - має свій власний розумний вказівник
Cairo::RefPtr<>
У даному прикладі, pixbuf
є розумним вказівником, тому ви можете працювати з ним так само як зі стандартним вказівником std::shared_ptr
:
auto width = 0;
if(pixbuf)
{
width = pixbuf->get_width();
}
Таким чином, коли pixbuf
виходить з області видимості, у фоновому режимі відбудеться unref()
. А оскільки немає оператора new
- отже, немає і delete
.
В оригінальній статті наведено список літератури, щоб дізнатися більше про вказівники:
- Bjarne Stroustrup, "The C++ Programming Language" Fourth Edition - section 34.3
- Nicolai M. Josuttis, "The C++ Standard Library" - section 4.2
Ще немає коментарів