Почему деструктор C++ по умолчанию не уничтожает мои объекты?

В спецификации C++ сказано, что деструктор по умолчанию удаляет все нестатические элементы. Тем не менее, мне не удается этого добиться.

У меня есть это:

class N {
public:
    ~N() {
        std::cout << "Destroying object of type N";
    }
};

class M {
public:
    M() {
        n = new N;
    }
//  ~M() { //this should happen by default
//      delete n;
//  }
private:
    N* n;
};

Тогда это должно напечатать данное сообщение, но это не так:

M* m = new M();
delete m; //this should invoke the default destructor

13 ответов

Решение

Что заставляет вас думать объект n указывает на должны быть удалены по умолчанию? Деструктор по умолчанию уничтожает указатель, а не то, на что он указывает.

Изменить: я посмотрю, смогу ли я сделать это немного более ясным.

Если бы у вас был локальный указатель, и он вышел из области видимости, вы ожидаете, что объект, на который он указывает, будет уничтожен?

{
    Thing* t = new Thing;

    // do some stuff here

    // no "delete t;"
}

t указатель очищен, но Thing это указывает на нет. Это утечка. По сути, то же самое происходит в вашем классе.

Представьте себе что-то вроде этого:

class M {
public:
    M() { }
//  ~M() {        // If this happens by default
//      delete n; // then this will delete an arbitrary pointer!
//  }
private:
    N* n;
};

Вы сами по себе с указателями в C++. Никто не будет автоматически удалять их для вас.

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

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

Деструктор по умолчанию уничтожает указатель. Если вы хотите удалить N с Mдеструктор по умолчанию, используйте умный указатель. + Изменить N * n; в auto_ptr<N> n; а также n будет уничтожен

Изменить: как указано в комментариях, auto_ptr<> не лучший умный указатель для всех целей, но он выглядит так, как здесь. Это определенно представляет собственность: N в M существует в течение M и больше не существует. Копирование или назначение auto_ptr<> представляет собой смену владельца, что обычно не то, что вы хотите. Если вы хотите передать указатель из M, вы должны передать N * получил от n.get(),

Более общее решение будет boost::shared_ptr<>, который будет в стандарте C++0x. Это можно использовать практически везде, где будет использоваться необработанный указатель. Это не самая эффективная конструкция, и она имеет проблемы с циклическими ссылками, но в целом это безопасная конструкция.

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

Есть ли какая-то причина, по которой вы используете указатель, когда указанный объект, по-видимому, принадлежит содержащемуся объекту? Просто сохраните объект по значению:

class M
{
    N n;

public:

    M() : n()
    {
    }
};

Неверно утверждать, что деструктор удаляет участников. Он вызывает деструктор каждого члена (и базового класса), что для встроенных типов (таких как указатели) означает ничего не делать.

Ответственность за сопоставление новых s и delete s лежит на вас (вручную или с помощью умных указателей).

Ваш аргумент может показаться обоснованным, но это не так, как работают указатели.

n на самом деле разрушается, но это означает, что N* деструктор вызывается который, он не разрушает какой-либо объект n указывает на. Думать о N*деструктор, как будто это было intдеструктор. Он удаляет его значение, то же самое происходит с указателем, он удаляет адрес, на который он указывает, но ему не нужно удалять какой-либо объект, расположенный по адресу, который вы только что удалили.

Деструктор по умолчанию выглядит так:

~M()
{
}

Деструктор по умолчанию не вставляет код, чтобы делать что-либо с указанными объектами. Что если у вас есть n, указывающая на переменную стека? Автоматическая вставка удаления n приведет к сбою.

Деструктор по умолчанию вызывает деструктор для каждого члена класса (member. ~ T ()). Для указателя это не работает (ничего не делает), точно так же, как myint.~ Int() ничего не делает, но для классов-членов с определенными деструкторами вызывается деструктор.

Вот еще один пример:

struct MyClass {
public:
    MyClass() { .. } // doesn't matter what this does

    int x;
    int* p;
    std::string s;
    std::vector<int> v;
};

Деструктор по умолчанию в реальности делает это:

MyClass::~MyClass()
{
    // Call destructor on member variables in reverse order
    v.~std::vector<int>(); // frees memory
    s.~std::string();      // frees memory
    p.~int*();             // does nothing, no custom destructor
    x.~int();              // does nothing, no custom destructor
}

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

Я думаю, что вы можете быть смущены уровнем косвенности здесь. Когда экземпляр уничтожается, каждый элемент данных действительно уничтожается вместе с ним. В вашем случае, когда M разрушен и M::~M() называется, его переменная n действительно разрушен. Проблема в том, что n это N * Таким образом, пока указатель уничтожен, на что он указывает, нет.

delete не работает, как это. Рассмотрим ваше простое утверждение:

delete n;

Вышеприведенное утверждение разрушает то, что n указывает на, который является объектом типа N, Не разрушает n сам, который является N * указатель.

Есть очень веская причина, по которой M::~M() не звонит автоматически delete n; что это: N упомянутый объект может быть разделен между несколькими M объекты, и если один M были уничтожены, остальные потеряли бы N они указывали на, оставляя повсюду ужасные свисающие указатели. C++ не пытается интерпретировать то, что вы хотели сделать с вашими указателями, он просто делает то, что вы сказали делать.

Короче, M на самом деле уничтожает всех своих членов, когда он уничтожен, просто это разрушение не делает то, что, как вы думаете, оно должно делать. Если вам нужен тип указателя, который становится владельцем объекта и уничтожает его при уничтожении указателя, посмотрите на std::auto_ptr,

М деструктор должен иметь "удалить n".

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

Я думаю, что вы могли бы извлечь выгоду из очень простого примера:

int main(int argc, char* argv[])
{
  N* n = new N();
} // n is destructed here

Это тоже ничего не напечатает.

Зачем? Поскольку pointer (n) разрушен, а не объект, на который указывает *n,

Конечно, вы не хотели бы, чтобы он уничтожил объект, на который указывают:

int main(int argc, char* argv[])
{
  N myObject;
  {
    N* n = &myObject;
  } // n is destructed here, myObject is not

  myObject.foo();
} // myObject is destructed here

Вы должны помнить, что в отличие от таких языков, как C# или же JavaЕсть два способа создания объектов в C++: напрямую N myObject (в стеке) или через new как в new N() в этом случае объект помещается в кучу, и вы несете ответственность за его освобождение в более позднее время.

Таким образом, ваш деструктор уничтожает указатель, но не объект, на который указывает. Выделите объект без нового (и без использования указателя) или используйте Smart Pointer если вы хотите, чтобы это было автоматически.

Старайтесь избегать использования указателей. Они являются последним средством.

class N {
public:
    ~N() {
        std::cout << "Destroying object of type N";
    }
};

class M {
public:
    M() {
       // n = new N; no need, default constructor by default
    }
//  ~M() { //this should happen by default
//      delete n;
//  }
private:
    N n; // No pointer here
};

Тогда используйте это так

main(int, char**)
{
    M m;
}

Это отобразит Уничтожающий объект типа N

class N {
public:
    ~N() {
        std::cout << "Destroying object of type N";
    }
};

class M {
public:
    M() {
        n = new N;
    }
//  ~M() { //this should happen by default
//      delete n;
//  }
private:
    N* n;
};

и теперь ожидание:

M* m = new M();
delete m; //this should invoke the default destructor

Это произойдет, только если класс M получен из N:

class M: Class N {
...

Только в этой ситуации

M* m = new M()

вызовет конструктор N, а затем конструктор M, где как

delete m;

сначала автоматически вызовет деструктор M, а затем N

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