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

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

16.11.2023, 02:12. Показов 2510. Ответов 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
1673 / 501 / 107
Регистрация: 17.05.2015
Сообщений: 1,518
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
Ответ Создать тему
Новые блоги и статьи
Новый CodeBlocs. Версия 25.03
palva 04.01.2026
Оказывается, недавно вышла новая версия CodeBlocks за номером 25. 03. Когда-то давно я возился с только что вышедшей тогда версией 20. 03. С тех пор я давно снёс всё с компьютера и забыл. Теперь. . .
Модель микоризы: классовый агентный подход
anaschu 02.01.2026
Раньше это было два гриба и бактерия. Теперь три гриба, растение. И на уровне агентов добавится между грибами или бактериями взаимодействий. До того я пробовал подход через многомерные массивы,. . .
Учёным и волонтёрам проекта «Einstein@home» удалось обнаружить четыре гамма-лучевых пульсара в джете Млечного Пути
Programma_Boinc 01.01.2026
Учёным и волонтёрам проекта «Einstein@home» удалось обнаружить четыре гамма-лучевых пульсара в джете Млечного Пути Сочетание глобально распределённой вычислительной мощности и инновационных. . .
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост.
Programma_Boinc 28.12.2025
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост. Налог на собак: https:/ / **********/ gallery/ V06K53e Финансовый отчет в Excel: https:/ / **********/ gallery/ bKBkQFf Пост отсюда. . .
Кто-нибудь знает, где можно бесплатно получить настольный компьютер или ноутбук? США.
Programma_Boinc 26.12.2025
Нашел на реддите интересную статью под названием Anyone know where to get a free Desktop or Laptop? Ниже её машинный перевод. После долгих разбирательств я наконец-то вернула себе. . .
Thinkpad X220 Tablet — это лучший бюджетный ноутбук для учёбы, точка.
Programma_Boinc 23.12.2025
Рецензия / Мнение/ Перевод Нашел на реддите интересную статью под названием The Thinkpad X220 Tablet is the best budget school laptop period . Ниже её машинный перевод. Thinkpad X220 Tablet —. . .
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru