279 / 156 / 52
Регистрация: 30.06.2011
Сообщений: 1,712

Базовый класс и идиома RAII

19.07.2020, 11:53. Показов 2217. Ответов 15
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Приветствую всех. Есть базовый абстрактный класс TAdapter, у которого два наследника: TSerialAdapter и TBluetoothAdapter. В приложении пользователь выберет какой-то один адаптер. Будет создан объект соответствующего класса и сохранится указатель на него в переменной базового класса. Классы-наследники созданы согласно идиоме RAII.
Возникает вопрос: как оставить объект "живым"? Ведь при завершении функции созданный объект выйдет из области видимости и, согласно RAII, уничтожится.

Примитивный пример, иллюстрирующий проблему:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Глобальная область */
TAdapter* g_Adapter = nullptr;
 
/* Функция создания адаптера */
void CreateSerialAdapter()
{
  TSerialAdapter serialAdapter;
  /* Настройка адаптера */
  g_Adapter = &serialAdapter;
 
  /* Здесь переменная serialAdapter выйдет из области функции
     и при этом будет вызван ее деструктор. В результате  в g_Adapter
     будет невалидный указатель */
}
0
Лучшие ответы (1)
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
19.07.2020, 11:53
Ответы с готовыми решениями:

typeid определяет тип указателя на базовый класс, как тип "базовый класс". Вне зависимости от присвоенного ему значения
Вот код: #include <iostream> #include <string> #include <conio.h> #include <windows.h> #include <typeinfo> using...

Базовый класс Complex и производный класс для реализации квадратных матриц
1) Создайте базовый класс Complex (комплексное число) для реализации комплексных чисел в алгебраической форме и основных операций с ними:...

Описать базовый класс колоды карт и производный класс пасьянс
Здраствуйте! товарищи-программисты, помогите пожалуйста со следующим заданием: Создать колоду карт. Конструкторы колоды должны...

15
6771 / 4565 / 1843
Регистрация: 07.05.2019
Сообщений: 13,726
19.07.2020, 11:57
Цитата Сообщение от d7d1cd Посмотреть сообщение
Возникает вопрос: как оставить объект "живым"? Ведь при завершении функции созданный объект выйдет из области видимости и, согласно RAII, уничтожится.
Очевидно, создать его динамически
C++
1
2
3
4
5
/* Функция создания адаптера */
void CreateSerialAdapter()
{
  /* Настройка адаптера */
  g_Adapter = new TSerialAdapter();
1
279 / 156 / 52
Регистрация: 30.06.2011
Сообщений: 1,712
19.07.2020, 13:52  [ТС]
Цитата Сообщение от oleg-m1973 Посмотреть сообщение
Очевидно, создать его динамически
oleg-m1973, но тогда нарушится идиома RAII. Ведь согласно ей выделением памяти для объекта и ее освобождением должен заниматься сам объект. Или я неверно понимаю RAII?
0
6771 / 4565 / 1843
Регистрация: 07.05.2019
Сообщений: 13,726
19.07.2020, 14:00
Цитата Сообщение от d7d1cd Посмотреть сообщение
oleg-m1973, но тогда нарушится идиома RAII. Ведь согласно ей выделением памяти для объекта и ее освобождением должен заниматься сам объект. Или я неверно понимаю RAII?
Скорее всего, ты не понимаешь о чём там идёт речь.
Здесь - создавай объект динамически. И сделай у класса TAdapter виртуальный деструктор, и не забывай делать delete g_Adapter, когда это нужно.
1
279 / 156 / 52
Регистрация: 30.06.2011
Сообщений: 1,712
19.07.2020, 14:26  [ТС]
Цитата Сообщение от oleg-m1973 Посмотреть сообщение
Скорее всего, ты не понимаешь о чём там идёт речь.
А ты понимаешь? Если да, то объясни.

Цитирую:
Важный случай использования RAII — «умные указатели»: классы, инкапсулирующие владение памятью. Например, в стандартной библиотеке шаблонов языка C++ для этой цели существует класс auto_ptr (заменён на unique_ptr в C++11).
Динамическое создание (и ручное удаление) - это как раз то, от чего освобождает RAII. Поэтому, в моем случае, применять динамику неправильно, раз уж я избрал путь RAII. Решением вижу заведение счетчика ссылок в TAdapter и перегрузку его оператора operator= и конструктора копии TAdapter(TAdapter&).
0
"C with Classes"
2022 / 1404 / 523
Регистрация: 16.08.2014
Сообщений: 5,885
Записей в блоге: 1
19.07.2020, 14:31
Цитата Сообщение от d7d1cd Посмотреть сообщение
g_Adapter = &serialAdapter;
нужно адаптер динамически создавать а не брать адрес локального объекта.
1
Неэпический
 Аватар для Croessmah
18128 / 10712 / 2063
Регистрация: 27.09.2012
Сообщений: 27,002
Записей в блоге: 1
19.07.2020, 14:36
Цитата Сообщение от d7d1cd Посмотреть сообщение
Поэтому, в моем случае, применять динамику неправильно, раз уж я избрал путь RAII.
Ты создаешь автоматический объект, задача которого помереть при выходе из блока.
Если объект должен переживать блок, то здесь нужна динамика.
Цитата Сообщение от d7d1cd Посмотреть сообщение
Решением вижу заведение счетчика ссылок в TAdapter и перегрузку его оператора operator= и конструктора копии TAdapter(TAdapter&).
То, что ты зашьешь её внутрь RAII объекта, не убережет этот автоматический объект от смерти и твой указатель также станет невалидным.

C++
1
2
3
4
5
6
7
8
9
/* Глобальная область */
std::unique_ptr<TAdapter> g_Adapter;
 
/* Функция создания адаптера */
void CreateSerialAdapter()
{
  auto serialAdapter = std::make_unique<TSerialAdapter>();
  /* Настройка адаптера */
  g_Adapter = std::move(serialAdapter);
Если тебе нужно совместное владение адаптером, можешь использовать std::shared_ptr.
1
279 / 156 / 52
Регистрация: 30.06.2011
Сообщений: 1,712
19.07.2020, 18:15  [ТС]
Croessmah, правильно ли я понимаю, если управление объектом планируется через указатель на его абстрактный класс-родитель, то здесь можно воспользоваться только динамическим созданием объекта?
0
"C with Classes"
2022 / 1404 / 523
Регистрация: 16.08.2014
Сообщений: 5,885
Записей в блоге: 1
19.07.2020, 18:26
Цитата Сообщение от d7d1cd Посмотреть сообщение
если управление объектом планируется через указатель на его абстрактный класс-родитель, то здесь можно воспользоваться только динамическим созданием объекта
не всегда, главное что бы объект (адрес которого ты используешь) был жив.
допустим:
C++
1
2
3
4
5
6
7
8
9
10
/* Глобальная область */
TAdapter* g_Adapter = nullptr;
 
/* Функция создания адаптера */
void CreateSerialAdapter()
{
  static TSerialAdapter serialAdapter;
  /* Настройка адаптера */
  g_Adapter = &serialAdapter;
}
или
C++
1
2
3
4
5
6
7
void Function(TAdapter* g_Adapter)
{
    // ...
}
 
TSerialAdapter serialAdapter;
Function(&serialAdapter);
1
279 / 156 / 52
Регистрация: 30.06.2011
Сообщений: 1,712
19.07.2020, 18:52  [ТС]
Наверняка, я буду не прав, если предложу следующее решение. Что если в базовом классе будет счетчик ссылок, а так же метод Assign, который, в частности, будет увеличивать этот счетчик. Тогда, на основании значения этого счетчика, можно избежать "умирания" объекта при выходе из области видимости.
Например:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Глобальная область */
class TAdapter
{
  public:
  TAdapter() : refcnt(1) {}
  ~TAdapter() { refcnt--; }
  void Assign(TAdapter& adapter) { adapter.refcnt++; }
 
  private:
  int refcnt;
};
 
 
class TSerialAdapter : public TAdapter
{};
 
TAdapter* g_Adapter = nullptr;
 
/* Функция создания адаптера */
void CreateSerialAdapter()
{
  TSerialAdapter serialAdapter;
  /* Настройка адаптера */
  g_Adapter.Assign(serialAdapter);
}
0
6771 / 4565 / 1843
Регистрация: 07.05.2019
Сообщений: 13,726
19.07.2020, 18:55
Цитата Сообщение от d7d1cd Посмотреть сообщение
Наверняка, я буду не прав, если предложу следующее решение. Что если в базовом классе будет счетчик ссылок, а так же метод Assign, который, в частности, будет увеличивать этот счетчик. Тогда, на основании значения этого счетчика, можно избежать "умирания" объекта при выходе из области видимости.
Нет, serialAdapter у тебя в любом случае будет уничтожен при выходе из CreateSerialAdapter(). Он же распределён на стеке, а стек сворачивается при выходе из функции
0
"C with Classes"
2022 / 1404 / 523
Регистрация: 16.08.2014
Сообщений: 5,885
Записей в блоге: 1
19.07.2020, 19:02
d7d1cd, тебе нужен внешний объект для хранения счетчика ссылок
0
279 / 156 / 52
Регистрация: 30.06.2011
Сообщений: 1,712
19.07.2020, 19:03  [ТС]
Цитата Сообщение от oleg-m1973 Посмотреть сообщение
serialAdapter у тебя в любом случае будет уничтожен при выходе из CreateSerialAdapter()
В моем примере не все расписано. У serialAdapter сработает деструктор, который сначала уменьшит счетчик refcnt и посмотрит, стал ли он 0. Если он не будет равен 0, то освобождения захваченных в конструкторе ресурсов не произойдет.
Хотя тогда не понятно, как эти ресурсы потом освобождать, ведь класс TAdapter о них ничего не знает...
0
6771 / 4565 / 1843
Регистрация: 07.05.2019
Сообщений: 13,726
19.07.2020, 19:59
Цитата Сообщение от d7d1cd Посмотреть сообщение
Хотя тогда не понятно, как эти ресурсы потом освобождать, ведь класс TAdapter о них ничего не знает...
Там у тебя много чего непонятного будет.
Здесь не надо ничего придумывать, всё уже давно придумано. Просто воспользуйся std::unique_ptr или std::shared_ptr.
0
279 / 156 / 52
Регистрация: 30.06.2011
Сообщений: 1,712
19.07.2020, 20:22  [ТС]
Получается, что в моем случае не получится применить идиому RAII, то есть, захватить ресурс при создании объекта и освободить его при уничтожении. Так как создание происходит в одном месте, а удаление в другом. Верно?
0
6771 / 4565 / 1843
Регистрация: 07.05.2019
Сообщений: 13,726
19.07.2020, 20:28
Лучший ответ Сообщение было отмечено d7d1cd как решение

Решение

Цитата Сообщение от d7d1cd Посмотреть сообщение
Получается, что в моем случае не получится применить идиому RAII, то есть, захватить ресурс при создании объекта и освободить его при уничтожении. Так как создание происходит в одном месте, а удаление в другом. Верно?
RAII здесь не при чём - это о конструкторах и деструкторах.
Т.е. твои объекты, TSerialAdapter и TBluetoothAdapter, должны открывать порт в конструкторе и закрывать в деструкторе, а не в отдельных методах, тогда и будет RAII.
А каким образом ты будешь вызывать эти конструкторы/деструкторы, т.е. создавать/удалять объекты, это не имеет значение. Главное, чтоб не забывал удалять и не обращался к удалённым объектам.
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
19.07.2020, 20:28
Помогаю со студенческими работами здесь

Класс: как обратиться к методу производного класса через итератор на базовый класс?
Есть абстрактный и два порожденных. Хочу создать например list&lt;Base*&gt; list1; затем добавляю себе в список: ...

Класс: Дописать производный класс, дополняющий базовый и содержащий минимум 2 функции-члена...
Составьте программу на языке С#, которая должна содержать: 1) базовый класс в соответствии с вариантом; 2) производный класс,...

Создать базовый класс Car (машина) и производный класс Lorry (грузовик): ООП ошибки
Создать базовый класс Car (машина), характеризуемый торговой маркой (строка), числом цилиндров, мощностью. Определить методы переназначения...

Создать базовый класс - Array и производный класс - Money для работы денежной суммы
ПОМОГИТЕ, ПОЖАЛУЙСТА, С ЗАДАЧЕЙ Создать базовый класс - Array и производный класс - Money для работы денежной суммы

Создать иерархию классов Figure
Создать абстрактный базовый класс Figure с виртуальными методами вычисления площади и периметра. Создать производные классы: Rectangle...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
16
Ответ Создать тему
Опции темы

Новые блоги и статьи
Чем асинхронная логика (схемотехника) лучше тактируемой, как я думаю, что помимо энергоэффективности - ещё и безопасность.
Hrethgir 14.05.2025
Помимо огромного плюса в энергоэффективности, асинхронная логика - тотальный контроль над каждым совершённым тактом, а значит - безусловная безопасность, где безконтрольно не совершится ни одного. . .
Многопоточные приложения на C++
bytestream 14.05.2025
C++ всегда был языком, тесно работающим с железом, и потому особеннно эффективным для многопоточного программирования. Стандарт C++11 произвёл революцию, добавив в язык нативную поддержку потоков,. . .
Stack, Queue и Hashtable в C#
UnmanagedCoder 14.05.2025
Каждый опытный разработчик наверняка сталкивался с ситуацией, когда невинный на первый взгляд List<T> превращался в узкое горлышко всего приложения. Причина проста: универсальность – это прекрасно,. . .
Как использовать OAuth2 со Spring Security в Java
Javaican 14.05.2025
Протокол OAuth2 часто путают с механизмами аутентификации, хотя по сути это протокол авторизации. Представьте, что вместо передачи ключей от всего дома вашему другу, который пришёл полить цветы, вы. . .
Анализ текста на Python с NLTK и Spacy
AI_Generated 14.05.2025
NLTK, старожил в мире обработки естественного языка на Python, содержит богатейшую коллекцию алгоритмов и готовых моделей. Эта библиотека отлично подходит для образовательных целей и. . .
Реализация DI в PHP
Jason-Webb 13.05.2025
Когда я начинал писать свой первый крупный PHP-проект, моя архитектура напоминала запутаный клубок спагетти. Классы создавали другие классы внутри себя, зависимости жостко прописывались в коде, а о. . .
Обработка изображений в реальном времени на C# с OpenCV
stackOverflow 13.05.2025
Объединение библиотеки компьютерного зрения OpenCV с современным языком программирования C# создаёт симбиоз, который открывает доступ к впечатляющему набору возможностей. Ключевое преимущество этого. . .
POCO, ACE, Loki и другие продвинутые C++ библиотеки
NullReferenced 13.05.2025
В C++ разработки существует такое обилие библиотек, что порой кажется, будто ты заблудился в дремучем лесу. И среди этого многообразия POCO (Portable Components) – как маяк для тех, кто ищет. . .
Паттерны проектирования GoF на C#
UnmanagedCoder 13.05.2025
Вы наверняка сталкивались с ситуациями, когда код разрастается до неприличных размеров, а его поддержка становится настоящим испытанием. Именно в такие моменты на помощь приходят паттерны Gang of. . .
Создаем CLI приложение на Python с Prompt Toolkit
py-thonny 13.05.2025
Современные командные интерфейсы давно перестали быть черно-белыми текстовыми программами, которые многие помнят по старым операционным системам. CLI сегодня – это мощные, интуитивные и даже. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru