Подтвердить что ты не робот

Деструктор С++ с возвратом

В С++, если мы определяем деструктор класса как:

~Foo(){
   return;
}

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

Я хочу сделать так, чтобы какой-то объект был уничтожен только через деструктор других объектов, т.е. только тогда, когда другой объект готов к уничтожению.

Пример:

class Class1{
...
Class2* myClass2;
...
};

Class1::~Class1(){
    myClass2->status = FINISHED;
    delete myClass2;
}

Class2::~Class2(){
    if (status != FINISHED) return;
}

Я искал в Интернете и, похоже, не нашел ответа на мой вопрос. Я также попытался выяснить это сам, перейдя через некоторый код шаг за шагом с отладчиком, но не могу получить окончательный результат.

4b9b3361

Ответ 1

Нет, вы не можете не допустить, чтобы объект был уничтожен оператором return, это просто означает, что выполнение dtor-тела закончится в этой точке. После этого он все равно будет уничтожен (включая его элементы и базы), и память все равно будет освобождена.

Вы выбрали исключение переноса.

Class2::~Class2() noexcept(false) {
    if (status != FINISHED) throw some_exception();
}

Class1::~Class1() {
    myClass2->status = FINISHED;
    try {
        delete myClass2;
    } catch (some_exception& e) {
        // what should we do now?
    }
}

Обратите внимание, что это ужасная идея. Лучше пересмотреть дизайн, я уверен, что должен быть лучший.

ИЗМЕНИТЬ

Я допустил ошибку, исключение исключения не остановило разрушение его баз и членов, просто сделайте возможным получить результат процесса Class2 dtor. И что можно сделать с этим, пока неясно.

Ответ 2

~Foo(){
   return;
}

означает то же самое, что:

~Foo() {}

Он похож на функцию void; достижение конца без инструкции return; совпадает с return; в конце.

Деструктор содержит действия, которые выполняются, когда процесс уничтожения Foo уже начат. Невозможно прервать процесс уничтожения без прерывания всей программы.

Ответ 3

[D] явным образом возвращается из деструктора, что мы никогда не хотим его уничтожить?

Нет. Раннее возвращение (через return; или throw ...) означает, что остальная часть тела деструктора не выполняется. База и участники все еще уничтожены, а их деструкторы все еще работают, см. [except.ctor]/3.

Для объекта типа класса любой продолжительности хранения, инициализация или уничтожение которого завершается с помощью исключения, деструктор вызывается для каждого из полностью построенных подобъектов объекта...

Ниже приведены примеры кода этого поведения.


Я хочу сделать так, чтобы какой-то объект был уничтожен только через деструктор других объектов, т.е. только тогда, когда другой объект готов к уничтожению.

Похоже, что вопрос связан с проблемой собственности. Удаление "принадлежащего" объекта только после того, как родитель будет уничтожен в очень распространенной идиоме и достигнут одним из (но не ограничивается);

  • Состав, это автоматическая переменная-член (т.е. "основанная на стеке" )
  • A std::unique_ptr<>, чтобы выразить эксклюзивное владение динамическим объектом
  • A std::shared_ptr<>, чтобы выразить совместное владение динамическим объектом

Учитывая пример кода в OP, std::unique_ptr<> может быть подходящей альтернативой;

class Class1 {
  // ...
  std::unique_ptr<Class2> myClass2;
  // ...
};

Class1::~Class1() {
    myClass2->status = FINISHED;
    // do not delete, the deleter will run automatically
    // delete myClass2;
}

Class2::~Class2() {
    //if (status != FINISHED)
    //  return;
    // We are finished, we are being deleted.
}

Я отмечаю проверку условия if в примере кода. Он намекает на то, что государство привязано к собственности и жизни. Это не одно и то же; конечно, вы можете привязать объект, достигая определенного состояния, к нему "логическое" время жизни (т.е. запустите некоторый код очистки), но я бы избегал прямой ссылки на право собственности на объект. Может быть, лучше подумать о том, чтобы пересмотреть часть семантики, вовлеченной здесь, или позволить "естественному" построению и разрушению диктовать начальное и конечное состояния объекта.

Боковое примечание; если вам нужно проверить какое-либо состояние в деструкторе (или утвердить какое-либо конечное условие), одна из альтернатив throw - вызвать std::terminate (с некоторым протоколированием), если это условие не выполняется. Такой подход похож на стандартное поведение и результат, когда возникает исключение при разматывании стека в результате уже вызванного исключения. Это также стандартное поведение, когда std::thread выходит с необработанным исключением.


[D] явным образом возвращается из деструктора, что мы никогда не хотим его уничтожить?

Нет (см. выше). Следующий код демонстрирует это поведение; здесь и динамическая версия. noexcept(false) необходим, чтобы избежать вызова std::terminate().

#include <iostream>
using namespace std;
struct NoisyBase {
    NoisyBase() { cout << __func__ << endl; }
    ~NoisyBase() { cout << __func__ << endl; }
    NoisyBase(NoisyBase const&) { cout << __func__ << endl; }
    NoisyBase& operator=(NoisyBase const&) { cout << __func__ << endl; return *this; }    
};
struct NoisyMember {
    NoisyMember() { cout << __func__ << endl; }
    ~NoisyMember() { cout << __func__ << endl; }
    NoisyMember(NoisyMember const&) { cout << __func__ << endl; }
    NoisyMember& operator=(NoisyMember const&) { cout << __func__ << endl; return *this; }    
};
struct Thrower : NoisyBase {
    Thrower() { cout << __func__ << std::endl; }
    ~Thrower () noexcept(false) {
        cout << "before throw" << endl;
        throw 42;
        cout << "after throw" << endl;
    }
    NoisyMember m_;
};
struct Returner : NoisyBase {
    Returner() { cout << __func__ << std::endl; }
    ~Returner () noexcept(false) {
        cout << "before return" << endl;
        return;
        cout << "after return" << endl;
    }
    NoisyMember m_;
};
int main()
{
    try {
        Thrower t;
    }
    catch (int& e) {
        cout << "catch... " << e << endl;
    }
    {
        Returner r;
    }
}

Имеет следующий вывод:

NoisyBase
NoisyMember
Thrower
before throw
~NoisyMember
~NoisyBase
catch... 42
NoisyBase
NoisyMember
Returner
before return
~NoisyMember
~NoisyBase

Ответ 4

В соответствии со стандартом С++ (12.4 Destructors)

8 После выполнения тела деструктора и уничтожения любого автоматические объекты, выделенные внутри тела, деструктор для класса X вызывает деструкторы для Xs прямых невариантных нестатических данных членов, деструкторы для прямых базовых классов Xs и, если X - тип самого производного класса (12.6.2), его деструктор вызывает деструкторы для виртуальных базовых классов Xs. Все деструкторы называются как если бы они ссылались на квалифицированное имя, то есть игнорирование любые возможные виртуальные переопределяющие деструкторы в более производных классах. Основания и элементы уничтожаются в обратном порядке завершения их конструктора (см. 12.6.2). Оператор return (6.6.3) в деструктор не может напрямую вернуться к вызывающему абоненту; до передача управления вызывающему, деструкторы для членов и основания называются. Деструкторы для элементов массива называются в обратном порядке их конструкции (см. 12.6).

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

Ответ 5

явно возвращает из деструктора среднее значение, которое мы никогда не хотим уничтожить.

Нет.

Деструктор - это функция, поэтому вы можете использовать ключевое слово return внутри нее, но это не помешает уничтожению объекта, когда вы находитесь внутри деструктора, вы уже уничтожаете свой объект, поэтому любая логика, которая хочет предотвратить это должно произойти раньше.

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

Ответ 6

Все объекты на основе стека будут разрушены, независимо от того, как скоро вы return от деструктора. Если вы пропустите delete динамически выделенные объекты, тогда будет преднамеренная утечка памяти.

В этом вся идея, как будут работать конструкторы move-constructors. Движение CTOR просто взяло бы первоначальную память объекта. Деструктор исходного объекта просто не будет/не может называть delete.

Ответ 7

Нет. return просто означает выход из метода, он не останавливает уничтожение объекта.

Кроме того, зачем вам это нужно? Если объект выделен в стеке, и вам каким-то образом удалось остановить разрушение, объект будет жить на восстановленной части стека, который, вероятно, будет перезаписан следующим вызовом функции, который может записывать всю память вашего объекта и создавать undefined.

Аналогично, если объект выделен в куче, и вам удалось предотвратить разрушение, у вас будет утечка памяти, поскольку код, вызывающий delete, предположил бы, что ему не нужно удерживать указатель на объект в то время как он на самом деле все еще там и занимает память, о которой никто не ссылается.

Ответ 8

Вы можете сделать новый метод, чтобы сделать объект "совершить самоубийство" и оставить деструктор пустым, поэтому что-то вроде этого выполнит задание, которое вы хотели бы сделать:

Class1::~Class1()
{
    myClass2->status = FINISHED;
    myClass2->DeleteMe();
}

void Class2::DeleteMe()
{
   if (status == FINISHED)
   {
      delete this;
   }
 }

 Class2::~Class2()
 {
 }

Ответ 9

Конечно нет. Явный вызов "return" ist 100% эквивалентен неявному возврату после выполнения деструктора.

Ответ 10

Итак, как отмечали все остальные, return не является решением.

Первое, что я хотел бы добавить, это то, что вы не должны беспокоиться об этом. Если ваш профессор явно не спросил. Было бы очень странно, если бы вы не могли доверять внешнему классу, чтобы удалить свой класс только в нужное время, и я полагаю, что никто его не видит. Если указатель передан, указатель, вероятно, будет shared_ptr/weak_ptr, и пусть он уничтожит ваш класс в нужный момент.

Но, хорошо, интересно, как мы будем решать странную проблему, если она когда-нибудь возникнет, если мы узнаем что-то (и не будем тратить время на крайний срок!)

И что для решения? Если вы, по крайней мере, можете доверять деструктору класса 1, чтобы не разрушать ваш объект слишком рано, вы можете просто объявить деструктор класса 2 как private, а затем объявить деструктор класса 1 как друга класса2, например:

class Class2;

class Class1 {

    Class2* myClass2;

public:
    ~Class1();
};

class Class2 {
private:
        ~Class2();

friend Class1::~Class1();
};

Class1::~Class1() {
    delete myClass2;
}

Class2::~Class2() {
}

В качестве бонуса вам не нужен флаг "статус"; что хорошо - если кто-то хотел, чтобы это было плохо с вами, почему бы не установить флаг состояния FINISHED в другом месте, а затем вызвать delete?

Таким образом, у вас есть реальная гарантия того, что объект может быть уничтожен нигде, кроме деструктора класса 1.

Конечно, деструктор класса 1 получает доступ ко всем частным членам класса 2. Это может быть неважно - в конце концов, Class2 все равно будет уничтожен! Но если это так, мы можем вызвать еще более запутанные способы обойти это; почему нет. Например:

class Class2;

class Class1 {
private:
    int status;

    Class2* myClass2;

public:
    ~Class1();
};

class Class2Hidden {
private:
      //Class2 private members
protected:
    ~Class2Hidden();
public:
    //Class2 public members
};

class Class2 : public Class2Hidden {
protected:
        ~Class2();

friend Class1::~Class1();
};

Class1::~Class1() {
    delete myClass2;
}

Class2Hidden::~Class2Hidden() {
}

Class2::~Class2() {
}

Таким образом, публичные члены по-прежнему будут доступны в производном классе, но частные члены будут фактически частными. ~ Class1 получит доступ только к частным и защищенным членам Class2 и к защищенным членам Class2Hidden; которые в этом случае являются только деструкторами. Если вам нужно защитить защищенный член класса 2 от деструктора класса 1... есть способы, но это действительно зависит от того, что вы делаете.

Удачи!

Ответ 11

В этом случае вы можете использовать перегрузку класса для оператора delete. Итак, для вас Class2 вы могли бы что-то вроде этого

class Class2
{
    static void operator delete(void* ptr, std::size_t sz)
    {
        std::cout << "custom delete for size " << sz << '\n';
        if(finished)
            ::operator delete(ptr);
    }

    bool Finished;
}

Затем, если вы установите для конца значение true перед удалением, будет вызываться фактическое удаление. Обратите внимание, что я не тестировал его, я только что изменил код, который я нашел здесь. http://en.cppreference.com/w/cpp/memory/new/operator_delete

Class1::~Class1()
{
    class2->Finished = true;
    delete class2;
}