gcc — C ++ деструктор не вызывается, в зависимости от порядка компоновки

Я столкнулся с этой проблемой в своем приложении после проверки на утечки памяти и обнаружил, что некоторые из моих классов вообще не уничтожаются.

Приведенный ниже код разбит на 3 файла, предполагается, что он реализует шаблон Pimpl. Ожидаемый сценарий состоит в том, чтобы Cimpl конструктор и деструктор печатают свои сообщения. Однако, это не то, что я получаю с g ++. В моем приложении вызывался только конструктор.

classes.h:

#include <memory>

class Cimpl;

class Cpimpl {
std::auto_ptr<Cimpl> impl;
public:
Cpimpl();
};

classes.cpp:

#include "classes.h"#include <stdio.h>

class Cimpl {
public:
Cimpl() {
printf("Cimpl::Cimpl()\n");
}
~Cimpl() {
printf("Cimpl::~Cimpl()\n");
}
};

Cpimpl::Cpimpl() {
this->impl.reset(new Cimpl);
}

main.cpp:

#include "classes.h"
int main() {
Cpimpl c;
return 0;
}

Вот что я смог открыть дальше:

g++ -Wall -c main.cpp
g++ -Wall -c classes.cpp
g++ -Wall main.o classes.o -o app_bug
g++ -Wall classes.o main.o -o app_ok

Похоже, деструктор вызывается в одном из двух возможных случаев, и это зависит от порядка связывания. С app_ok я смог получить правильный сценарий, в то время как app_bug вел себя точно так же, как мое приложение.

Есть ли какая-то мудрость, которой мне не хватает в этой ситуации?
Спасибо за любое предложение заранее!

3

Решение

Цель идиомы pimpl состоит в том, чтобы не указывать определение класса реализации в заголовочном файле. Но все стандартные интеллектуальные указатели требуют, чтобы определение их параметра шаблона было видимым в пункте декларации для того, чтобы работать правильно.

Это означает, что это один из редких случаев, когда вы действительно хотите использовать new, deleteи голый указатель. (Если я ошибаюсь по этому поводу и есть стандарт умный указатель, который можно использовать для прыщей, кто-то, пожалуйста, дайте мне знать.)

classes.h

struct Cimpl;

struct Cpimpl
{
Cpimpl();
~Cpimpl();

// other public methods here

private:
Cimpl *ptr;

// Cpimpl must be uncopyable or else make these copy the Cimpl
Cpimpl(const Cpimpl&);
Cpimpl& operator=(const Cpimpl&);
};

classes.cpp

#include <stdio.h>

struct Cimpl
{
Cimpl()
{
puts("Cimpl::Cimpl()");
}
~Cimpl()
{
puts("Cimpl::~Cimpl()");
}

// etc
};

Cpimpl::Cpimpl() : ptr(new Cimpl) {}
Cpimpl::~Cpimpl() { delete ptr; }

// etc
1

Другие решения

Проблема заключается в том, что в момент определения auto_ptr<Cimpl> объект, Cimpl является неполным типом, то есть компилятор видел только предварительное объявление Cimpl, Это нормально, но, поскольку он в конечном итоге удаляет объект, на который он содержит указатель, вы должны выполнить это требование из [expr.delete] / 5:

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

Таким образом, этот код имеет неопределенное поведение, и все ставки отключены.

1

Кодекс нарушает Правило Единого Определения. Там есть определение класса Cimpl в classes.h, и другое определение класса Cimpl в файле classes.cpp. Результатом является неопределенное поведение. Можно иметь более одного определения класса, но они должны быть одинаковыми.

0

Отредактировано для ясности, оригинал сохранен ниже.

Этот код имеет неопределенное поведение, потому что в контексте main.cpp неявный Cpimpl::~Cpimpl деструктор имеет только предварительное объявление Cimpl, но auto_ptr (или любая другая форма выполнения delete) нуждается в полном определении, чтобы юридически очистить Cimpl, Учитывая, что это неопределенное поведение, дальнейшее объяснение ваших наблюдений не требуется.

Оригинальный ответ:

Я подозреваю, что здесь происходит то, что неявный деструктор Cpimpl генерируется в контексте classes.h а также не имея доступ к полному определению Cimpl, Тогда когда auto_ptr пытается сделать свое дело и очистить содержащийся в нем указатель, удаляет неполный класс, поведение которого не определено. Учитывая, что он не определен, нам не нужно идти дальше, чтобы объяснить, что для него вполне приемлемо работать по-разному в зависимости от порядка ссылок.

Я подозреваю, что явный деструктор для Cpimpl с определением в исходном файле решит вашу проблему.

РЕДАКТИРОВАТЬ: На самом деле теперь, когда я смотрю на это снова, я считаю, что ваша программа нарушает одно правило определения в его нынешнем виде. В main.cpp он видит неявный деструктор, который не знает, как вызвать деструктор Cimpl (потому что у него есть только предварительное объявление). В classes.cpp неявный деструктор делает иметь доступ к Cimplи как его называть деструктором.

0