Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.85/13: Рейтинг темы: голосов - 13, средняя оценка - 4.85
2 / 2 / 0
Регистрация: 16.08.2013
Сообщений: 86

Как избежать использования разрушенной переменной?

16.11.2023, 02:12. Показов 2767. Ответов 27
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Никак не могу найти приемлимое решение следующей проблемы.

Допустим, мы имеем логирующую функцию log(), которая может быть вызвана из любого места программы. Например:

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
26
27
#include <iostream>
#include <mutex>
 
void log(const char* s);
 
class A {
public:
    A() { }
    ~A() { log("~A"); }
} a;
 
class B {
public:
    B() { log("B"); }
    ~B() { }
} b;
 
void log(const char* s) {
    static std::shared_ptr<std::mutex> m = std::make_shared<std::mutex>();
    std::unique_lock<std::mutex> lock(*m);
    std::cout << s << "\n";
}
 
 
int main() {
    return 0;
}
Проблема в том, что второй по времени вызов log(), который произойдёт из ~A(), приведёт к неопределённому поведению или крашу, так как статическая переменная "m" уже разрушена. Как выходить из таких ситуаций?

Вижу следующие варианты, которые мне не совсем нравятся:
* Не создавать в деструкторах зависимость от других глобальных переменных. В данном примере - не вызывать log() из ~A(). Но ведь можно представить ситуацию, что в деструкторе ожидается завершение параллельного потока, который как раз может использовать log()...
* Переместить в самый верх кода подобные глобальные переменные, делая их последними в очереди на разрушение. В данном примере - переместить "m" наверх. Но тогда реализация функции log() будет разделена на 2 части...
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
16.11.2023, 02:12
Ответы с готовыми решениями:

Как избежать использования оператора goto?
typedef struct BOOK { char *name; } book; int main() { FILE *fp; char *fname = &quot;sometxt&quot;; book nbook; ...

Как избежать использования кучи проверок?
В общем есть файл конфига, в котором хранятся переменные, так вот, я хотел бы узнать, можно ли избежать кучи проверок, ведь при загрузке...

Как избежать утечки памяти: нюансы использования QList
class T { QList&lt;bitset&lt;32&gt; &gt; bits; QList&lt;bitset&lt;32&gt; &gt; newList() { return QList&lt;bitset&lt;32&gt; &gt;(); } void...

27
 Аватар для eva2326
1685 / 513 / 107
Регистрация: 17.05.2015
Сообщений: 1,524
17.11.2023, 13:08
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от Paket236 Посмотреть сообщение
Если бы можно было все "создающие" вызовы (такие, как log()) разместить в каком-нибудь callback'е и передать его в OS-зависимую функцию, которая вызовет этот callback до main() и до создания всех глобальных переменных
Такое тоже можно.
Есть такая палочка-выручалочка, которая позволяет разруливать самые разные грабли, связанные с единицами трансляции:

1. Нужно приготовить правильный предварительно скомпилированный заголовок.
Правильный - это когда вам не нужно вручную писать всякую вредительскую дрянь наподобие #include "stdafx.h"
Все современные компиляторы имеют опции, с помощью которых можно задать автоматическое подключение предварительно скомпилированного заголовка к каждой ед. трансляции.

2. В предваритально скомпилированном заголовке, первой же строкой подключаем хедер, с таким содержимым:

C++
1
2
// pch.hpp (предварительно скомпилированный заголовок)
#include "glob_init.hpp"
Где:
C++
1
2
3
4
5
// glob_init.hpp
 
// Подключение данного хедера приводит к инициализации
// всех глобальных точек доступа
static auto state  = glob::initialize();
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
#include "logger.hpp"
#include "thread_pool.hpp"
#include "singleton.hpp"
 
// glob_init.сpp
namespace glob
{
    struct initial
    {
        // Здесь указываем порядок вызова глобальных точек доступа        
        initial()
        {
            log("started");        // Запускаем логгер
            thread::pool::start(); // Запускаем пул потоков
            singleton::get();      // Запускаем ещё что нибудь
        }
    };
    
    const int initialize()
    {
        static initial obj;
        return OK;
    }
}
Инициализация глобального объекта state, приводит к запуску функции glob::initialize(), которая в свою очередь инициализирует локальный статический объект класса initial, который в свою очередь организует обращение к глобальным точкам доступа, которые в свою очередь приводят к их инициализации.
В итоге получаем инициализацию всех глобальных точек доступа в нужной последовательности.

Таким образом, предварительно скомпилированный заголовок гарантирует заданный порядок инициализации глобальных точек доступа ещё до начала main, а так же, до начала динамической инициализации глобальных переменных, описанных в файлах.cpp

Единственное слабое место: глобальные точки доступа не должны ссылаться друг на друга.
Если у вас есть два статических объекта: A и B, и при этом они ссылаются друг на друга в конструкторе, тогда произойдет зацикливание при инициализации.


А в самих ед. трансляции уже даже заголовки всяких логгеров подключать не нужно.
Можно сразу использовать:

C++
1
2
3
4
5
6
7
8
9
// main.cpp
 
// Делать явный  #include "logger.hpp" больше не нужно
 
int main()
{
    // Можно сразу использовать
    log("hello word");
}
1
2 / 2 / 0
Регистрация: 16.08.2013
Сообщений: 86
17.11.2023, 13:33  [ТС]
eva2326, вот за это огромное спасибо!
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
17.11.2023, 14:06
Paket236,
Можно сделать проще.
1) Создать класс Logger
2) Создать экземпляр Logger через std::shared_ptr
3) Передавать std::shared_ptr<Logger> туда где нужно логировать
Далее в деструкторе можно свободно логировать, так как механизм подсчета ссылок будет гарантировать сохранность объекта.
0
7804 / 6568 / 2988
Регистрация: 14.04.2014
Сообщений: 28,705
17.11.2023, 14:12
Undisputed, где и как создать-то?
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
17.11.2023, 15:05
Цитата Сообщение от nmcf Посмотреть сообщение
где и как создать-то?
Место создания объекта зависит от архитектуры приложения. Мое предложение заключается в избавлении от глобального состояния в пользу dependency injection (далее будут причины).

C++
1
2
3
4
5
6
int main()
{
    auto logger = std::make_shared<my_logger>(...);
    A a(logger);
    B b(logger);
}
имхо, это решение проще и более гибкое...
1) проще потому что время жизни объекта гарантируется std::shared_ptr вместо того что бы полагаться на порядок разрушения объектов, который требует аккуратного создания объектов в строго определенном порядке а так же знание соответствующих деталей языка (даже безобидная перестановка вызовов может привести к тому что все поломается... а как насчет надежности решения?). Это так же усложняет поддержку, так как многие знают что такое std::shared_ptr, а вот эти order-ы создания и уничтожения объектов сходу не каждый помнит/поймет что на них и полагается автор кода.

2) более гибкое как минимум потому, что при передачи логгера как аргумента, появляется возможность использовать разного типа логгеры (можно передавать как аргумент file_logger, console_logger, databse_logger и так далее) а не зависеть только от одного способа логирования

В итоге получаем более гибкое, и более простое для понимания решение. В общем если ТС нужно более надежное, простое и гибкое решение, рекомендую забросить текущую идею и принять во внимание этот пост.
0
7804 / 6568 / 2988
Регистрация: 14.04.2014
Сообщений: 28,705
17.11.2023, 16:19
Undisputed, тогда a, b придётся передавать как параметры везде, где они нужны.
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
17.11.2023, 16:43
Цитата Сообщение от nmcf Посмотреть сообщение
тогда a, b придётся передавать как параметры везде, где они нужны.
Кто-то видит в этом проблему, а кто-то возможности По возможности желательно зависимости передавать через аргументы, это более гибко, так как это позволяет передавать функции разные аргументы, и не завязывать функцию на один конкретный объект.

Но если очень хочется глобальности, то выход есть. Можно в глобальном неймспейсе создать std::unique_ptr<A>, затем reset-нуть его создав реальный объект (например, в main), и при создании объекта через reset передать ему логгер, и таким образом не нужно будет передавать A и B как аргумент везде... Но в целом лучше конечно же через аргументы как минимум исходя из вышеупомянутых соображений.

На самом деле разные варианты есть, как получить желаемое. Например, можно создать класс app, и что бы выполнение программы начиналось с его метода run и путь app хранит необходимые "глобальные" объекты в качестве полей. Пока не завершится run, все объекты будут в строю.
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
18.11.2023, 11:53
Вот пример решения через класс app
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
26
27
28
#include <iostream>
 
struct my_app
{
    A a;
    B b;
 
    void run()
    {
        // здесь всё то, что хотели реализовать в main
    }
};
 
// для глобального доступа
// просто теперь к A и B мы обращаемся не напрямую,
// а вот так: app->a, app->b
my_app* app;
 
int main()
{
    app = new my_app;
    app->run();
    // при delete A и B уже уничтожаются,
    // но в их деструкторах можно смело вызывать функцию log
    // так как статический объект, созданный в функции log все еще не уничтожен
    // потому что main не закончился еще
    delete app;
}
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
18.11.2023, 11:53

Как избежать изменения одной переменной ссылочного типа, когда изменяется значение другой переменной
Подскажите, пожалуйста, как избежать изменения одной переменной ссылочного типа, когда изменяется значение другой переменной ссылочного...

Как избежать глобальной переменной
Можно ли как-то так делать не применяя глобальных переменных procedure TForm1.Button1Click(Sender: TObject); var S:TStringList; ...

Можно ли избежать использования курсора?
Есть такие таблицы: CREATE TABLE . AS TABLE( NOT NULL, NULL, PRIMARY KEY CLUSTERED ( ASC )WITH (IGNORE_DUP_KEY =...

Избежать использования WildCard символов в Like запросах
Всем доброго времени суток. Возникла следующая трудность: спецсимволы % и _ в like запросе. Имеется mybatis маппер и следующий...

Как получить счетчик использования переменной?
Понимаю, вопрос конечно риторический ... но все-же ... среди нас есть светлые головы ... Как получить счетчик использования переменной?


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

Или воспользуйтесь поиском по форуму:
28
Ответ Создать тему
Новые блоги и статьи
FPS контроллер с анимированными Y-Bot и 3D-звуком miniaudio (Box3D, Recast, BehaviorTree.CPP, ozz-animation, ImGui, OpenGL и SDL3)
8Observer8 18.10.2026
Содержание блога | English Version Полное мультиплатформенное руководство по стартовому проекту с FPS-контроллером игрока, анимированными роботами Y-Bot с 4-слойным блендингом, 3D-физикой Box3D,. . .
Локальный http сервер
Argus19 17.10.2026
Локальный http сервер На работе, на двух локальных компьютерах круглосуточно работают программы опроса приборов с записью в книги Excel,которые раз в сутки записываются на сервер компании. Т. к. . . .
Копировать Windows на новый диск на UEFI
damix 17.10.2026
https:/ / uriesk. wordpress. com/ 2016/ 11/ 01/ move-window-10-to-ssd-on-a-uefi-dualboot-system-with-basic-linux-tools/ TL DR Вставляем диск, а старый подключаем через переходник, грузимся с лайва. . .
ИИ не может найти нужный язык в списке
Supersumestria 05.10.2026
Я ему даю вот такое изображение и прошу найти и подчеркнуть немецкий язык. Возвращает он вот это: https:/ / i. **********/ vqBWLe2. png Нужную строчку в 3й колонке просто выдумал. . Это. . .
Новая последняя моя музыка в SUNO
zorxor 05.10.2026
Здравствуйте, дорогие мои друзья! С большой радостью я хотел бы представить вам свою новую последнею музыку, которую сгенерировала мне по моей просьбе нейросеть SUNO. С уважением, zorxor. Это. . .
Программный домашний кинотеатр
russiannick 27.09.2026
Сподобился на программный домашний кинотеатр. В качестве ЯВУ по традиции выбрал js. В помощники взял Яндекс-Алису. Было создано три зала на разные интересы. исторические и ретро сериал Хичкок. . .
Беседа с ИИ о программистах, недопускающих к созданию и правке кода генеративные ИИ и причины этого
zorxor 21.09.2026
Раньше я радовался или получал некоторые эмоции, пусть небольшие, но всё же, от самого процесса написания кода, рекомпиляции и запуска, видя постепенное развитие программы и прочее. А теперь лень. . .
Мобильное приложение ColorStep
pavlinmavlin 17.09.2026
Реализовал приложение Красный, Зеленый, Синий в Unity3d + c#. Название изменил на ColorStep. Приложение прошло модерацию и теперь доступно для скачивания. Делал его сам, шаг за шагом — и вот,. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru