Я работаю во встроенной среде микроконтроллера, которая использует глобальные переменные. Некоторые из этих глобальных переменных имеют нетривиальные конструкторы и деструкторы и страдают от проблемы статического порядка инициализации. Мне нужно, чтобы порядок был четко определен и находился под моим контролем. Обычное решение состоит в использовании функции со статической переменной, определенной внутри:
Type& GetObject()
{
static Type g_Object;
return g_Object;
}
При первом использовании GetObject() будет создана локальная статическая переменная, которая может быть частью четко определенного порядка инициализации:
int main()
{
...
GetObject1();
GetObject2();
...
}
Но что, если я хочу, чтобы разрушение произошло в определенный момент и в четко определенном порядке? (В данном случае, прямо перед входом в режим выключения микроконтроллера.)
Если бы программа должна была нормально завершить работу в размещенной среде, разрушение произошло бы в порядке, противоположном инициализации, и это нормально. Однако в этом случае программа не завершится нормально, поэтому это неприменимо. Возможно, можно использовать std::exit (не пробовал), но он также выполняет дополнительные операции, которые не нужны (например, очистку потоков C).
Потенциальное решение
Это решение включает в себя тип объединения оболочек, который полагается на то, что пользователь выполняет явную инициализацию и уничтожение. Конструктор инициализирует член тривиального типа, что делает его безопасным для инициализации в любом глобальном порядке, а деструктор ничего не делает.
Под капотом используется размещение new для инициализации и вызов деструктора базового типа при уничтожении.
Пользователь должен правильно использовать оболочку — в противном случае поведение неопределенное.
#include
#include
template
union Lazy_t
{
public:
inline Lazy_t() : m_uninitialized{} {}
inline ~Lazy_t() {}
template
inline void Initialize(Args&&... args) { ::new (&m_initialized) T(std::forward(args)...); }
inline void Destroy() { m_initialized.~T(); }
inline T& operator* () { return m_initialized; }
inline const T& operator* () const { return m_initialized; }
inline T* operator->() { return &m_initialized; }
inline const T* operator->() const { return &m_initialized; }
private:
struct Uninitialized_t {};
Uninitialized_t m_uninitialized;
T m_initialized;
};
Его можно использовать следующим образом:
struct NonTrivial_t
{
NonTrivial_t() : m_value{} { m_value = new int{}; }
~NonTrivial_t() { delete m_value; }
int* m_value;
};
Lazy_t g_Object1;
Lazy_t g_Object2;
void Main()
{
// ... microcontroller initialization ...
g_Object1.Initialize();
g_Object2.Initialize();
// ...
*g_Object1->m_value = 1;
*g_Object2->m_value = 2;
// ...
g_Object2.Destroy();
g_Object1.Destroy();
// ... microcontroller de-initialization and enter shutdown ...
}
Есть ли какие-либо проблемы с этим решением?
Приложение: отложенный тип (обертка) с проверками безопасности:
#include
template
class SafeLazy_t
{
public:
inline SafeLazy_t()
: m_lazy{}
#ifndef NDEBUG
, m_initialized{}
#endif
{}
inline ~SafeLazy_t()
{
#ifndef NDEBUG
assert(!m_initialized);
#endif
}
template
inline void Initialize(Args&&... args)
{
#ifndef NDEBUG
assert(!m_initialized);
#endif
m_lazy.Initialize(std::forward(args)...);
#ifndef NDEBUG
m_initialized = true;
#endif
}
inline void Destroy()
{
#ifndef NDEBUG
assert(m_initialized);
#endif
m_lazy.Destroy();
#ifndef NDEBUG
m_initialized = false;
#endif
}
inline T& operator*()
{
#ifndef NDEBUG
assert(m_initialized);
#endif
return *m_lazy;
}
inline const T& operator*() const
{
#ifndef NDEBUG
assert(m_initialized);
#endif
return *m_lazy;
}
inline InnerLazy_t& operator->()
{
#ifndef NDEBUG
assert(m_initialized);
#endif
return m_lazy;
}
inline const InnerLazy_t& operator->() const
{
#ifndef NDEBUG
assert(m_initialized);
#endif
return m_lazy;
}
private:
Lazy_t m_lazy;
#ifndef NDEBUG
bool m_initialized;
#endif
};
Подробнее здесь: https://stackoverflow.com/questions/798 ... -variables