Когда называется деструктор С++?

Основной вопрос: когда программа вызывает метод деструктора класса в С++? Мне сказали, что он вызывается всякий раз, когда объект выходит из области видимости или подвергается delete

Более конкретные вопросы:

1) Если объект создается с помощью указателя, и этот указатель позже удаляется или получает новый адрес, на который указывает, ссылается ли объект, который он указывает на его деструктор (при условии, что ничто иное не указывает на него)?

2) В ответ на вопрос 1, что определяет, когда объект выходит за рамки (не относится к тому, когда объект покидает данный {блок}). Итак, другими словами, когда деструктор называется объектом в связанном списке?

3) Вы когда-нибудь захотите вызвать деструктор вручную?

Ответ 1

1) Если объект создается с помощью указателя, и этот указатель позже удаляется или получает новый адрес, на который указывает, ссылается ли объект, который он указывает на его деструктор (при условии, что ничто иное не указывает на него)?

Это зависит от типа указателей. Например, интеллектуальные указатели часто удаляют свои объекты при их удалении. Обычные указатели этого не делают. То же самое верно, когда указатель делается для указания на другой объект. Некоторые интеллектуальные указатели уничтожают старый объект или уничтожают его, если у него больше нет ссылок. У обычных указателей нет таких умений. Они просто содержат адрес и позволяют выполнять операции над объектами, на которые они указывают, специально сделав это.

2) В ответ на вопрос 1, что определяет, когда объект выходит за рамки (не относится к тому, когда объект покидает данный {блок}). Итак, другими словами, когда деструктор называется объектом в связанном списке?

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

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

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

3) Вы когда-нибудь захотите вызвать деструктор вручную?

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

// pointer is destroyed because it goes out of scope,
// but not the object it pointed to. memory leak
if (1) {
 Foo *myfoo = new Foo("foo");
}


// pointer is destroyed because it goes out of scope,
// object it points to is deleted. no memory leak
if(1) {
 Foo *myfoo = new Foo("foo");
 delete myfoo;
}

// no memory leak, object goes out of scope
if(1) {
 Foo myfoo("foo");
}

Ответ 2

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

Ответ: да. @DavidSchwartz дал один пример, но он довольно необычный. Я приведу пример, который под капотом того, что много программистов на С++ все время использует: std::vectorstd::deque, хотя он не используется совсем так).

Как известно большинству людей, std::vector выделяет больший блок памяти, когда/если вы добавляете больше элементов, чем может удерживать текущее распределение. Однако, когда он делает это, у него есть блок памяти, способный удерживать больше объектов, чем в настоящее время в векторе.

Чтобы справиться с этим, то, что vector делает под обложками, выделяет необработанную память через объект Allocator (который, если вы не укажете иначе, означает, что он использует ::operator new). Затем, когда вы используете (например) push_back для добавления элемента в vector, внутри вектор использует placement new для создания элемента в (ранее) неиспользуемой части своего пространства памяти.

Теперь, что происходит, когда/если вы erase элемент из вектора? Он не может просто использовать delete - который освободит весь блок памяти; он должен уничтожить один объект в этой памяти, не уничтожая других, или освобождая любой из блока памяти, который он контролирует (например, если вы erase 5 элементов из вектора, а затем push_back еще 5 элементов, это гарантировано что вектор не перераспределяет память, когда вы это делаете.

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

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

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

#ifndef CBUFFER_H_INC
#define CBUFFER_H_INC

template <class T>
class circular_buffer {
    T *data;
    unsigned read_pos;
    unsigned write_pos;
    unsigned in_use;
    const unsigned capacity;
public:
    circular_buffer(unsigned size) :
        data((T *)operator new(size * sizeof(T))),
        read_pos(0),
        write_pos(0),
        in_use(0),
        capacity(size)
    {}

    void push(T const &t) {
        // ensure there room in buffer:
        if (in_use == capacity) 
            pop();

        // construct copy of object in-place into buffer
        new(&data[write_pos++]) T(t);
        // keep pointer in bounds.
        write_pos %= capacity;
        ++in_use;
    }

    // return oldest object in queue:
    T front() {
        return data[read_pos];
    }

    // remove oldest object from queue:
    void pop() { 
        // destroy the object:
        data[read_pos++].~T();

        // keep pointer in bounds.
        read_pos %= capacity;
        --in_use;
    }

    // release the buffer:
~circular_buffer() { operator delete(data); }
};

#endif

В отличие от стандартных контейнеров, это напрямую использует operator new и operator delete. Для реального использования вы, вероятно, хотите использовать класс распределителя, но на данный момент он будет больше отвлекаться, чем внести вклад (IMO, в любом случае).

Ответ 3

  • Когда вы создаете объект с new, вы несете ответственность за вызов delete. Когда вы создаете объект с make_shared, результирующий shared_ptr отвечает за сохранение count и call delete, когда счетчик использования обращается в нуль.
  • Выход из области действия означает отказ от блока. Это когда деструктор вызывается, предполагая, что объект не был выделен с помощью new (т.е. Это объект стека).
  • О том, когда вам нужно явно вызвать деструктор, является выделение объекта размещение new.

Ответ 4

1) Объекты не создаются "через указатели". Существует указатель, который присваивается любому объекту, который вы 'новый'. Предполагая, что это то, что вы имеете в виду, если вы назовете "delete" на указателе, он фактически удалит (и вызовет деструктор) объект, который вызывает разницу указателей. Если вы назначаете указатель на другой объект, произойдет утечка памяти; ничто в С++ не будет собирать ваш мусор для вас.

2) Это два отдельных вопроса. Переменная выходит за пределы области действия, когда стек стека, в котором он был объявлен, вылетает из стека. Обычно это когда вы покидаете блок. Объекты в куче никогда не выходят за рамки, хотя их указатели на стек могут. Ничто в частности не гарантирует, что деструктор объекта в связанном списке будет вызван.

3) Не совсем. Там может быть Deep Magic, которая предложила бы иначе, но обычно вы хотите сопоставить свои "новые" ключевые слова с вашими ключевыми словами "delete" и поместить все в свой деструктор, чтобы убедиться, что он правильно очищает себя. Если вы этого не сделаете, обязательно прокомментируйте деструктор с конкретными инструкциями любому, кто использует класс, о том, как они должны вручную очищать ресурсы объекта.

Ответ 5

Чтобы дать подробный ответ на вопрос 3: да, есть редкие случаи, когда вы можете явно вызвать деструктор, в частности, в качестве сопоставления с новым местом размещения, как отмечает dasblinkenlight.

Чтобы дать конкретный пример этого:

#include <iostream>
#include <new>

struct Foo
{
    Foo(int i_) : i(i_) {}
    int i;
};

int main()
{
    // Allocate a chunk of memory large enough to hold 5 Foo objects.
    int n = 5;
    char *chunk = static_cast<char*>(::operator new(sizeof(Foo) * n));

    // Use placement new to construct Foo instances at the right places in the chunk.
    for(int i=0; i<n; ++i)
    {
        new (chunk + i*sizeof(Foo)) Foo(i);
    }

    // Output the contents of each Foo instance and use an explicit destructor call to destroy it.
    for(int i=0; i<n; ++i)
    {
        Foo *foo = reinterpret_cast<Foo*>(chunk + i*sizeof(Foo));
        std::cout << foo->i << '\n';
        foo->~Foo();
    }

    // Deallocate the original chunk of memory.
    ::operator delete(chunk);

    return 0;
}

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

Ответ 6

  • Указатели. Регулярные указатели не поддерживают RAII. Без явного delete будет мусор. К счастью, С++ имеет автонауки, которые обрабатывают это для вас!

  • Область. Подумайте, когда переменная становится невидимой для вашей программы. Обычно это находится в конце {block}, как вы указываете.

  • Ручное уничтожение. Никогда не пытайтесь. Просто позвольте scope и RAII сделать магию для вас.

Ответ 7

Всякий раз, когда вы используете "новое", то есть присоединяете адрес к указателю или, если хотите, вы требуете места в куче, вам нужно "удалить" его. 1.yes, когда вы что-то удаляете, вызывается деструктор.
2. Когда вызывается деструктор связанного списка, вызывается его деструктор объектов. Но если они указатели, вам нужно удалить их вручную. 3. Когда пространство заявлено "новым".

Ответ 8

Да, деструктор (a.k.a. dtor) вызывается, когда объект выходит из области видимости, если он находится в стеке или когда вы вызываете delete указатель на объект.

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

  • Хорошая реализация связанного списка вызовет dtor всех объектов в списке, когда список уничтожается (потому что вы либо вызвали какой-то метод, чтобы его уничтожить, либо вышли из области действия). Это зависит от реализации.

  • Я сомневаюсь, но я не удивлюсь, если есть какие-то странные обстоятельства.

Ответ 9

Если объект создается не через указатель (например, A a1 = A();), деструктор вызывается, когда объект разрушается, всегда, когда закончена функция, в которой находится объект. Например: < ш >

void func()
{
...
A a1 = A();
...
}//finish


деструктор вызывается, когда код выполняется в строке "finish".

Если объект создается с помощью указателя (например, A * a2 = new A();), деструктор вызывается, когда указатель удаляется (удаляет a2;). Если точка не удалена пользователем явно или перед тем, как удалить новый адрес, произойдет утечка памяти. Это ошибка.

В связанном списке, если мы используем std:: list < > , нам не нужно заботиться об утечке дескриптора или памяти, потому что std:: list < > закончил все это для нас. В связанном списке, написанном нами самим, мы должны написать дескриптор и удалить указатель explictly.Otherwise, это вызовет утечку памяти.

Мы редко вызываем деструктор вручную. Это функция, обеспечивающая систему.

Извините за мой плохой английский!