Утечка памяти с использованием статического члена unique_ptr

Я постараюсь максимально обобщить вариант использования.

У меня есть класс 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. Происходит следующее:

  1. getB() вызывается, и m_b (все еще nullptr) инициализируется к новому B().
  2. инициализация статической переменной вызывается снова, и m_b переназначается новому B().
  3. Выделение, выполненное на шаге 1, не освобождается и, следовательно, происходит утечка памяти.

Может кто-нибудь объяснить утечку памяти? И как лучше всего это решить?

пожалуйста, прочитайте о минимально воспроизводимом примере и попытайтесь его предоставить. Если struct B{}; — это все, что нам нужно знать о B, сделайте эту часть своего минимального примера. Не заставляйте нас предполагать «черные ящики». Проблема где-то в вашем коде, исправление будет заключаться в изменении вашего кода, чтобы помочь нам нужно увидеть код.

463035818_is_not_an_ai 23.05.2024 11:01

а как узнать что есть утечка? В коде, который вы здесь показываете, его нет.

463035818_is_not_an_ai 23.05.2024 11:03

Вы спрашиваете о статическом порядке инициализации? Способ исправить это — не иметь статических членов, вместо этого использовать статику функций для создания синглтонов.

Alan Birtles 23.05.2024 11:10

@ 463035818_is_not_an_ai Я знаю, что после запуска ASAN произошла утечка, которая показала, что объект, который мы создаем на шаге 1, не освобождается.

Hussein Jaber 23.05.2024 11:10

@AlanBirtles Спасибо за ответ. Для меня странной вещью является утечка памяти. Вы правы, код будет изменен, но в данном случае (хотя это и не лучшая практика) почему память, созданная на шаге 1, не уничтожается?

Hussein Jaber 23.05.2024 11:11

Есть ли вероятность фиаско статического порядка инициализации? Вызывается ли getB в каком-либо инициализаторе глобального/статического объекта?

Yksisarvinen 23.05.2024 11:12

Кстати, если вы переместите static в функцию getB, вы можете сделать это двухстрочным: B& A::getB() { static std::unique_ptr<B> m_b=std::make_unique<B>(); return *m_b; }. Это также предотвращает доступ к нему любого другого кода без прохождения getB.

Christian Stieber 23.05.2024 11:13

@Yksisarvinen Действительно, getB вызывается в другом инициализаторе статического объекта.

Hussein Jaber 23.05.2024 11:15

Я думаю, что SIOF — это неопределенное поведение, поэтому утечка памяти — вполне допустимый результат. Как сказал Кристиан, вместо этого используйте Singleton Мейера (локальная переменная в функции).

Yksisarvinen 23.05.2024 11:17

@HusseinJaber в этом случае порядок инициализации становится чрезвычайно важным и может оказаться недостаточно управляемым. Используйте альтернативу, которую я предложил, чтобы сделать ее безопасной. И еще, почему unique_ptr? Можно было бы просто сделать это static B m_b; return m_b;...

Christian Stieber 23.05.2024 11:18

@Yksisarvinen спасибо за помощь. Просто чтобы убедиться, что мы выровнены, SIOF означает, что порядок инициализации не определен, но это заставит программу продолжать работать с неопределенным поведением, хотя с технической точки зрения не является ли неправильный порядок инициализации неправильным?

Hussein Jaber 23.05.2024 11:27

Глобальные объекты создаются в порядке сверху вниз в одном файле, но для нескольких файлов порядок не навязывается. Если для инициализации глобального объекта требуется другой глобальный объект, а другой объект определен в другом файле, то это чистая удача, если он сработает, иначе произойдет фиаско статического порядка инициализации (и он также может меняться между компиляциями). Как и во всех UB, сложно сказать, что произойдет, компилятору разрешено путешествовать во времени (помимо всего прочего).

Yksisarvinen 23.05.2024 11:34

@JaMiT Спасибо за комментарий. Вы правы, и он обновлен.

Hussein Jaber 27.05.2024 11:00
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
13
109
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как отмечают комментарии, это указывает на то, что 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; будет аналогично, если инициализировать немного быстрее.

Red.Wave 23.05.2024 11:36

Другие вопросы по теме