Почему деструктор вызывает себя бесконечно (вызывая переполнение стека)? - PullRequest
3 голосов
/ 11 ноября 2019

Меня смущает, почему деструктор бесконечно много раз вызывает сам себя, когда я пытаюсь создать объект, скажем LeakySingleton, в куче с помощью статического вызова функции create_instance(), а затем пытаюсь впоследствии удалить его явно черезdelete операция.

Насколько я понимаю, учитывая списки источников снизу, переменная leaky_singleton внутри main() указывает на выделенный ресурс кучи, возвращаемый create_instance(). Таким образом, мы определили объект LeakySingleton в куче косвенно через функцию create_instance. Теперь, если я явно вызываю оператор удаления или функцию удаления в leaky_singleton, то сначала он вызывает деструктор и проверяет, удовлетворяет ли он условию instance != nullptr, а затем следует удалить объект, на который указывает instance. удален. Если этот объект LeakySingleton::instance удален, то у dtor нет причин снова вызывать себя, или я что-то здесь упускаю?

Вызов его с использованием и без использования valgrind приводит к ошибке сегментации (неверный доступ к памятииз-за переполнения стека):

Segmentation fault (core dumped)

Прохождение через отладчик приводит к бесконечным вызовам деструкторов (виновник переполнения стека).

Из cplusplus. com (http://www.cplusplus.com/forum/general/40044/):

Если вы удаляете свой объект, он пытается удалить себя, что приведет к попытке удаления самого себя, что приведет к его удалению, что приведет к ...

Почему он пытается удалить себя, когда я просто использую оператор / функцию delete для освобождения объекта кучи LeakySingleton, на который указывает статическая переменная-член класса LeakySingleton::instance? Распределенная кучана ресурс указывает переменная-указатель LeakySingleton::instance, которая указывает на объект LeakySingleton. Так почему явный вызов функции delete не удаляет или скорее освобождает выделениеd куча объектов и вместо этого бесконечно повторяется? Что мне здесь не хватает?

(Мое текущее понимание dtor и ctor: функция / оператор new выделяет память для объекта в куче и вызывает конструктор, а функция delete вызывает деструктор ив моем случае также вызывает delete оператор / функцию внутри.)

Источник:

main.cpp :

class Singleton final
{
    public:
        static Singleton & create_instance(int);
        ~Singleton() = default;
    private:
        int x;
        Singleton(int);

        Singleton(Singleton &) = delete;
        Singleton(Singleton &&) = delete;
        Singleton & operator=(Singleton &) = delete;
        Singleton & operator=(Singleton &&) = delete;
};

Singleton::Singleton(int t_x) : x{t_x}
{}

Singleton & Singleton::create_instance(int t_x)
{
    static Singleton instance{t_x};
    return instance;
}

// Potential endless dtor calls inside:
class LeakySingleton final
{
    public:
        static LeakySingleton * create_instance(int);
        ~LeakySingleton();
    private:
        int x;
        static LeakySingleton * instance;
        LeakySingleton(int);

        LeakySingleton(LeakySingleton &) = delete;
        LeakySingleton(LeakySingleton &&) = delete;
        LeakySingleton & operator=(LeakySingleton &) = delete;
        LeakySingleton & operator=(LeakySingleton &&) = delete;
};

LeakySingleton * LeakySingleton::instance = nullptr;

LeakySingleton::LeakySingleton(int t_x) : x{t_x}
{}

LeakySingleton::~LeakySingleton()
{
    if (instance != nullptr)
    {
        delete instance;
        instance = nullptr;
    }
}

LeakySingleton * LeakySingleton::create_instance(int t_x)
{
    if (instance == nullptr)
    {
        instance = new LeakySingleton{t_x};
    }
    return instance;
}

int main()
{ 
    // The correct implementation with no issues:
    {
        Singleton & singleton = Singleton::create_instance(42);
    }

    // The faulty implementation causing the dtor to recurse endlessly and resulting in a segfault:
    {
        LeakySingleton * leaky_singleton = LeakySingleton::create_instance(42);
        delete leaky_singleton;
    }

    return 0;
}

Makefile :

CC = g++
CFLAGS = -g -Wall -Wextra -pedantic -std=c++11
SRC = main.cpp
TARGET = app
RM = rm -rf

.PHONY: all clean

all: $(TARGET)

clean:
    $(RM) $(TARGET)

$(TARGET): $(SRC)
    $(CC) $(CFLAGS) $^ -o $@

Ответы [ 4 ]

2 голосов
/ 11 ноября 2019

У вас неприятный цикл, в LeakySingleton::create_instance у вас есть:

instance = new LeakySingleton{t_x};

, затем в деструкторе LeakySingleton у вас есть:

delete instance;

, который будет вызыватьДеструктор LeakySingleton перед тем, как установить что-либо на null:

instance = nullptr;

, поэтому у вас есть реинфект infinte, вызывающий переполнение стека.

2 голосов
/ 11 ноября 2019

В C ++ delete вызовет деструктор класса.

Оператор delete в вашей функции main вызывает LeakySingleton::~LeakySingleton, который, в свою очередь, пытается удалить статический указатель экземпляра, которыйзатем снова вызывает деструктор. У вашего кода никогда не было возможности установить статический указатель на ноль. Там у вас есть бесконечный цикл.

PS ИМХО, вообще плохая практика - модифицировать статические элементы в нестатических методах. Я полагаю, что вы можете поместить статическую логику очистки в другой статический метод.

class LeakySingleton final {
public:
  static LeakySingleton& create_instance(int);
  static void destroy_instance();
  ~LeakySinglton() = default;
private:
  static LeakySingleton *instance;
  ...
};

void LeakySingleton::destroy_instance() {
  if (instance != nullptr) {
    delete instance;
    instance = nullptr;
  }
}
0 голосов
/ 11 ноября 2019

Во-первых, поскольку LeakySingleton не должен создаваться напрямую, он также не должен уничтожаться напрямую:

  • Поэтому его деструктор должен быть закрытым, точно так же, как и его конструктор.
  • если синглтон может быть удален: он должен быть удален с помощью публичной функции delete_instance(), которая удаляет экземпляр
  • , деструктор не должен удалять себя (бесконечное восстановление)
  • эта конструкция должна избегать бесконечной рекурсии деструктора

Если вы хотите, чтобы указатель экземпляра протекал и позволял его уничтожить, вы не должны делать это дважды (один раз снаружи и один раз внутри деструктора), но только один раз снаружидеструктор. Поскольку существует только один экземпляр, деструктор снаружи означает, что нет необходимости в вызове удаления внутри:

LeakySingleton::~LeakySingleton()
{
    if (instance != nullptr)
    {
         instance = nullptr;  // since there's only one, it's the instance and
    }                         // the instance pointer shall be reset
    // and do what's needed to clean the object
}   

Примечание: эта реализация не является поточно-ориентированной.

Примечание 2: эта статья может быть вам интересна. Он также предостерегает от публичного деструктора, поскольку это может привести к зависанию указателей.

0 голосов
/ 11 ноября 2019

Удалите instace в деструкторе, инициируйте вызов деструктора.

...