# Керування пам'яттю в gtkmm-4.0

gtkmm - вільний об'єктно-орієнтований C++ інтерфейс для популярного фреймворку GTK, що поширюється за ліцензією LGPL

Основне призначення gtkmm полягає в отриманні усіх переваг ООП:

* наслідуванні класів GTK
* скороченні конструкцій коду програми (порівняно з ієрархічною реалізацією аналогічної функціональності засобами C)
* вбудованих інструментів перевірок та корекції типізації нащадків дерева віджетів та багато іншого.

Оскільки це абстрагований прошарок над стандартною бібліотекою GTK, при його використанні, може виникнути питання як саме працювати з пам'яттю відповідно документації, адже API gtkmm має певні поліморфічні відмінності, без розуміння яких, є ризик витоку пам'яті або помилок при зверненнях.

Даний матеріал являє собою доповнений переклад офіційної сторінки

=> https://gnome.pages.gitlab.gnome.org/gtkmm-documentation/chapter-memory.html Chapter 25. Memory management

і базується на двох окремих статтях, які представлені розділами нижче:

=> https://gnome.pages.gitlab.gnome.org/gtkmm-documentation/chapter-memory.html#sec-memory-widgets Widgets - Робота з віджетами
=> https://gnome.pages.gitlab.gnome.org/gtkmm-documentation/sec-memory-shared-resources.html Shared resources - Робота зі спільними ресурсами

Сподіваюсь, він допоможе краще зрозуміти базові принципи роботи перед початком роботи з фреймворком: не писати зайві велосипеди для керування пам'яттю і не отримати замість переваг - segmantation fault.

## Робота з віджетами

* Важливою відмінністю gtkmm-4.0 у порівнянні зі старішими версіями є те, що знищення батьківського віджета GTK більше не призводить до знищення його дочірнього віджета, коли той знаходиться в його контейнері. Якщо ви покладалися на таку поведінку в старішій версії, то відтепер, в gtkmm-4.0 потрібно спочатку видалити дочірній віджет, наприклад, через деструктор.

Робота з фреймворком GTK здебільшого передбачає створення та видалення контейнерів та їх дочірніх віджетів. Відповідно, розробник повинен самостійно контролювати виділення пам'яті, а також її очищення після того, як певний її віджет більше не використовується.

Бібліотека gtkmm надає додаткові інструменти, які спрощують таку роботу.

Нижче розглянемо, які способи керування пам'яттю взагалі доступні в gtkmm

### Класичне керування пам'яттю C++

Загалом, gtkmm дозволяє програмісту контролювати тривалість життя будь-якого віджета так само, як і будь-якого іншого об'єкта C++. Ця гнучкість дозволяє використовувати:

* оператори new і delete для створення та знищення динамічних об'єктів
* звичайні деструктори (які виконуються автоматично, коли клас знищується)
* локальні екземпляри об'єктів (які знищуються, коли екземпляр виходить з області видимості)

Існують й інші способи, але розглянемо тільки деякі з них нижче.

#### Віджети в області класу

Якщо не потрібне динамічне виділення пам'яті, можна використовувати дочірні віджети в області батьківського класу. Однією з переваг таких віджетів є те, що керування пам'яттю зосереджено локально. Програма не ризикує отримати витоки пам'яті тому, що дані зберігаються на стеку.

``` cpp
#include <gtkmm/button.h>
#include <gtkmm/window.h>
class Foo : public Gtk::Window
{
private:
  Gtk::Button theButton; // буде знищений, коли об'єкт Foo буде знищений
};
```

Недоліком використання віджетів в області класу - є розкриття реалізації класу раніше, ніж його інтерфейсу в заголовковому файлі.

#### Віджети в області функції

Якщо програмісту не потрібен віджет в області всього класу, можна також використовувати віджет в локальній області функції. Перевагою такого підходу полягає у підвищеній прихованості даних та зменшенні залежностей:

``` cpp
{
  Gtk::Button aButton;
  aButton.set_visible(true);
  ...
  app->run();
}
```

Однак ця техніка рідко буває корисною. Більшість віджетів не можуть бути безпечно створені до того, як програма буде зареєстрована або активована. Їх також не можна безпечно видалити після того, як програму було активовано типовими для GTK 4 засобами Gtk::Application::run() або Gtk::Application::make_window_and_run().

#### Динамічна алокація з new і delete

Доволі часто, у програмах використовується динамічна пам'ять:

``` cpp
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()

``` cpp
MyContainer::MyContainer()
{
  auto pButton = Gtk::make_managed<Gtk::Button>("Test");
  append(*pButton); // додати *pButton до MyContainer
}
```

* замість Gtk::Box::append() можна використовувати й інші методи

Коли об'єкти типу MyContainer знищуються, кнопка також буде видалена. Більше не потрібно видаляти pButton, щоб звільнити пам'ять; контроль за її видаленням було делеговано об'єкту MyContainer.

=> https://github.com/YGGverse/Yoda/commit/02b0b42b6b03f707242f461b0bea8bb4be16d16c Приклад переходу програми на модель керуючих віджетів

## Робота зі спільними ресурсами

Деякі об'єкти, такі як Gdk::Pixbufs і Pango::Fonts, отримуються зі спільного сховища. Тому ви не можете створювати свої власні екземпляри. Ці класи зазвичай успадковуються від Glib::Object.

Gdk::Pixbuf може бути створено лише за допомогою функції create()

``` cpp
auto pixbuf = Gdk::Pixbuf::create_from_file(filename);
```

Замість того, щоб вимагати від вас посилатися на ці об'єкти, gtkmm повертає розумний вказівник (smart-pointer) Glib::RefPtr<>

* наприклад, бібліотека cairomm - має свій власний розумний вказівник Cairo::RefPtr<>

У даному прикладі, pixbuf є розумним вказівником, тому ви можете працювати з ним так само як зі стандартним вказівником std::shared_ptr

``` cpp
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

## Читайте також

=> memory-management-in-gtk-applications.gmi Керування пам'яттю в програмах GTK