2012-04-10 2 views
79

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

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

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

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

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

+2

Даже ваши конкретные вопросы слишком широки. «Этот указатель позже удален» и «с учетом нового адреса, чтобы указать на», совершенно разные. Найдите больше (на некоторые из них ответили), а затем задайте отдельные вопросы для тех частей, которые вы не смогли найти. –

ответ

49

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

Я думал, что последний из ваших примеров объявил функцию? Это пример «самого досадного разбора». (Другим более тривиальным моментом является то, что я предполагаю, что вы имели в виду «новый Foo()» с капиталом «F».) –

+2

Nice catch. Благодарю. –

+1

Я думаю, что 'Foo myfoo (" foo ")' не самый Vexing Parse, но 'char * foo =" foo "; Foo myfoo (foo); 'is. – Cosine

2
  1. Указатели - Обычные указатели не поддерживают RAII. Без явного delete будет мусор. К счастью, у C++ есть auto pointers, которые обрабатывают это для вас!

  2. Сфера - Подумайте о том, когда переменная становится невидимую к вашей программе. Обычно это в конце {block}, как вы указываете.

  3. Ручное разрушение - Никогда не пытайтесь это делать. Просто позвольте scope и RAII сделать волшебство для вас.

+0

Примечание: auto_ptr устарел, как упоминает ваша ссылка. – tnecniv

+0

'std :: auto_ptr' устарел на C++ 11, да. Если OP фактически имеет C++ 11, он должен использовать 'std :: unique_ptr' для одиночных владельцев или' std :: shared_ptr' для ссылочных учетных записей нескольких владельцев. – chrisaycock

+0

«Ручное разрушение - никогда не пытайтесь это». Я очень часто останавливаю указатели объектов на другой поток, используя системный вызов, который компилятор не понимает. «Опираясь» на области/авто/умные указатели заставили бы мои приложения сбой катастрофически, поскольку объекты были удалены вызывающим потоком, прежде чем они могли бы обрабатываться потребительским потоком. Эта проблема затрагивает ограниченные и refCounted объекты и интерфейсы. Только указатели и явное удаление. –

5
  1. При создании объекта с new, вы ответственны за вызов delete. Когда вы создаете объект с make_shared, результат shared_ptr отвечает за подсчет и вызов delete, когда счетчик использования будет равен нулю.
  2. Выйти из сферы действия означает оставить блок. Это когда вызывается деструктор, предполагая, что объект был не, выделенный new (т. Е. Это объект стека).
  3. О том, когда вам нужно явно вызвать деструктор, является выделение объекта с помощью placement new.
+1

Существует подсчет ссылок (shared_ptr), хотя, очевидно, не для простых указателей. – Pubby

+1

@Pubby: Хорошо, давайте продвинем хорошую практику. Отредактированный ответ. – MSalters

3

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

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

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

1

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

0

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

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

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

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

+1

«Если вы перенесите указатель без первого вызова delete, вы получите утечку памяти, потому что объект все еще существует в памяти». Не обязательно. Его можно было бы удалить с помощью другого указателя. –

0

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

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


деструктор вызывается, когда код execused к строке «закончить».

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

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

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

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

+0

Неверно, что вы не можете вызвать деструктор вручную - вы можете (например, увидеть код в моем ответе). Верно то, что подавляющее большинство времени вы не должны :) –

3

Чтобы дать подробный ответ на вопрос 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; 
} 

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

10

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

Ответ да. @DavidSchwartz дал один пример, но это довольно необычный. Я приведу пример, который находится под капотом того, что много программистов на C++ все время используют: 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's 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, во всяком случае).

Смежные вопросы