Я постараюсь максимально обобщить вариант использования.
У меня есть класс A (определенный в A.hpp):
class A
{
public:
static B& getB();
private:
A();
static std::unique_ptr<B> m_b;
};
который использует класс B (также определен в A.hpp)
class B
{};
В A.cpp мы определяем наш статический член A::m_b как:
std::unique_ptr<B> A::m_b = std::unique_ptr<B>(new B());
Мы также реализовали метод getB() (также в A.cpp) следующим образом:
B& A::getB()
{
if (!m_b)
{
m_b= std::unique_ptr<B>(new B());
}
return *m_b;
}
У меня есть сценарий, в котором другая статическая переменная (назовем ее X) вызывает getB() перед инициализацией переменной m_b. Происходит следующее:
Может кто-нибудь объяснить утечку памяти? И как лучше всего это решить?
а как узнать что есть утечка? В коде, который вы здесь показываете, его нет.
Вы спрашиваете о статическом порядке инициализации? Способ исправить это — не иметь статических членов, вместо этого использовать статику функций для создания синглтонов.
@ 463035818_is_not_an_ai Я знаю, что после запуска ASAN произошла утечка, которая показала, что объект, который мы создаем на шаге 1, не освобождается.
@AlanBirtles Спасибо за ответ. Для меня странной вещью является утечка памяти. Вы правы, код будет изменен, но в данном случае (хотя это и не лучшая практика) почему память, созданная на шаге 1, не уничтожается?
Есть ли вероятность фиаско статического порядка инициализации? Вызывается ли getB
в каком-либо инициализаторе глобального/статического объекта?
Кстати, если вы переместите static
в функцию getB
, вы можете сделать это двухстрочным: B& A::getB() { static std::unique_ptr<B> m_b=std::make_unique<B>(); return *m_b; }
. Это также предотвращает доступ к нему любого другого кода без прохождения getB
.
@Yksisarvinen Действительно, getB вызывается в другом инициализаторе статического объекта.
Я думаю, что SIOF — это неопределенное поведение, поэтому утечка памяти — вполне допустимый результат. Как сказал Кристиан, вместо этого используйте Singleton Мейера (локальная переменная в функции).
@HusseinJaber в этом случае порядок инициализации становится чрезвычайно важным и может оказаться недостаточно управляемым. Используйте альтернативу, которую я предложил, чтобы сделать ее безопасной. И еще, почему unique_ptr
? Можно было бы просто сделать это static B m_b; return m_b;
...
@Yksisarvinen спасибо за помощь. Просто чтобы убедиться, что мы выровнены, SIOF означает, что порядок инициализации не определен, но это заставит программу продолжать работать с неопределенным поведением, хотя с технической точки зрения не является ли неправильный порядок инициализации неправильным?
Глобальные объекты создаются в порядке сверху вниз в одном файле, но для нескольких файлов порядок не навязывается. Если для инициализации глобального объекта требуется другой глобальный объект, а другой объект определен в другом файле, то это чистая удача, если он сработает, иначе произойдет фиаско статического порядка инициализации (и он также может меняться между компиляциями). Как и во всех UB, сложно сказать, что произойдет, компилятору разрешено путешествовать во времени (помимо всего прочего).
@JaMiT Спасибо за комментарий. Вы правы, и он обновлен.
Как отмечают комментарии, это указывает на то, что A::getB
вызывается до инициализации A::m_b
. Следовательно, A::getB
обращается к неинициализированной переменной. И нет, вы не сможете это исправить, «инициализируя» m_b
, как вы сейчас пытаетесь.
Поскольку доступ к неинициализированным переменным является неопределенным поведением, может случиться что угодно, включая утечки памяти. Исправление — это метод Мейерса Синглтона, предложенный Кристианом Штибером:
B& A::getB()
{
static B b;
return b;
}
Поскольку ссылка возвращается, уникальный указатель кажется ненужным: static B s_b{/*init*/}; return s_b;
будет аналогично, если инициализировать немного быстрее.
пожалуйста, прочитайте о минимально воспроизводимом примере и попытайтесь его предоставить. Если
struct B{};
— это все, что нам нужно знать оB
, сделайте эту часть своего минимального примера. Не заставляйте нас предполагать «черные ящики». Проблема где-то в вашем коде, исправление будет заключаться в изменении вашего кода, чтобы помочь нам нужно увидеть код.