Почему деструктор C ++ по умолчанию не уничтожает мои объекты? - PullRequest
28 голосов
/ 08 марта 2010

В спецификации 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 ]

46 голосов
/ 08 марта 2010

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

Редактировать: я посмотрю, смогу ли я сделать это немного яснее.

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

{
    Thing* t = new Thing;

    // do some stuff here

    // no "delete t;"
}

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

16 голосов
/ 08 марта 2010

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

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

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

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

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

8 голосов
/ 08 марта 2010

Деструктор по умолчанию уничтожает указатель. Если вы хотите удалить 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. Это может быть использовано практически везде, где будет использоваться необработанный указатель. Это не самая эффективная конструкция, и у нее есть проблемы с циклическими ссылками, но обычно это безопасная конструкция.

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

6 голосов
/ 08 марта 2010

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

class M
{
    N n;

public:

    M() : n()
    {
    }
};
4 голосов
/ 08 марта 2010

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

Совпадение новых s с delete s является вашей ответственностью (либо вручную, либо с помощью интеллектуальных указателей).

3 голосов
/ 08 марта 2010

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

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

2 голосов
/ 08 марта 2010

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

~M()
{
}

Деструктор по умолчанию не вставляет код, чтобы делать что-либо с указанными объектами. Что если бы у вас было n , указывающее на переменную стека? Автоматическая вставка delete 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
}

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

2 голосов
/ 08 марта 2010

Я думаю, вы можете быть смущены уровнем косвенности здесь. Когда экземпляр уничтожается, каждый элемент данных действительно уничтожается вместе с ним. В вашем случае, когда 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.

1 голос
/ 24 августа 2010

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

1 голос
/ 24 августа 2010

M деструктор должен иметь 'delete n'.

...