Разрушение объекта в C++

Когда именно уничтожаются объекты в C++ и что это значит? Нужно ли их уничтожать вручную, так как сборщика мусора нет? Как исключения вступают в игру?

_(Note: This is meant to be an entry to [Stack Overflow's C++ FAQ](https://stackoverflow.com/questions/tagged/c++-faq). If you want to critique the idea of providing an FAQ in this form, then [the posting on meta that started all this](https://meta.stackexchange.com/questions/68647/setting-up-a-faq-for-the-c-tag) would be the place to do that. Answers to that question are monitored in the [C++ chatroom](https://chat.stackoverflow.com/rooms/10/c-lounge), where the FAQ idea started out in the first place, so your answer is very likely to get read by those who came up with the idea.)_

person fredoverflow    schedule 19.06.2011    source источник
comment
Кто голосовал за закрыть эту тему? Я не вижу никакой причины. На самом деле, это, безусловно, хороший FAQ. +1   -  person Nawaz    schedule 19.06.2011
comment
@Nawaz: однако, насколько я знаю, политика SO говорит только о хороших вопросах, а не о хороших записях часто задаваемых вопросов*. ;)   -  person jalf    schedule 19.06.2011
comment
@jalf: FAQ также представляет собой форму вопросов и ответов. Сам FAQ означает часто задаваемые вопросы. К тому же таких тем слишком много, и многие из них хорошие. Спросите их, кому было полезно их прочитать. Спросите меня. :D   -  person Nawaz    schedule 19.06.2011
comment
@Nawaz: Но является ли это хорошим вопросом (что является критерием для SO), если его не задал кто-то, кому действительно нужно было знать ответ? Если его так часто спрашивают, почему @Fred нужно было задавать его самому, чтобы он мог дать ответ? Я просто хочу сказать, что если вы играете по правилам, то это хороший FAQ не имеет значения, важно то, что это хороший вопрос, и я, по крайней мере, сужу об этом на основании того, скорее всего, ОП получит нужный ему ответ (что в данном случае недействительно, потому что ОП знает ответ), и от того, найдут ли его другие с той же проблемой.   -  person jalf    schedule 19.06.2011
comment
Считает ли клика, которая публикует вопросы FAQ и знает ответы, хорошим FAQ, не имеет значения.   -  person jalf    schedule 19.06.2011
comment
Чтобы следовать строгим правилам, разве идея C++-Faq не возникла как репозиторий наиболее часто задаваемых вопросов, а не только хорошие ответы или хорошо объясненный контент? Затем возникает вопрос: этот вопрос задавался так много раз, прежде чем стать часто задаваемым вопросом? Сказав это, содержание превосходно и на высшем уровне. Вопрос: соответствует ли оно идее часто задаваемых вопросов.   -  person Alok Save    schedule 19.06.2011
comment
@jalf: как насчет клики, которая, вопреки тому, что сказали Джоэл и Джефф, думают, что вопрос о том, знает ли спрашивающий ответ или нет, имеет какое-либо отношение к тому, хороший ли это вопрос? По-видимому, очень важно, что думает эта клика, поскольку для закрытия вопроса требуется всего 5 голосов, а затем, если люди, которые проголосовали за вопрос, не вернутся, чтобы проверить, они никогда не обнаружат, что меньшая группа людей выиграла спор. Это хороший вопрос, если (а) люди могут найти его с помощью поиска или (б) людей можно направить на него, когда они задают похожие вопросы, и ответы им помогают.   -  person Steve Jessop    schedule 19.06.2011
comment
Если об этом так часто спрашивают, почему @Fred понадобилось задавать его самому - обычно потому, что отдельные люди, которые не знают об этом, не думают спрашивать, когда уничтожаются объекты, вместо этого они задают какой-то конкретный вопрос об их конкретном code, на который есть ответ, вам нужно понимать время жизни ваших объектов. Таким образом, конкретные вопросы содержат слишком много деталей, которые не имеют отношения к другим людям, задающим вопросы по тому же вопросу. Я не знаю, так ли это здесь, но это для вопросов часто задаваемых вопросов, которые я использовал в прошлом, чтобы направить спрашивающих.   -  person Steve Jessop    schedule 19.06.2011
comment
Хороший сериал, продолжай в том же духе ;)   -  person Lukasz Madon    schedule 19.06.2011
comment
@jalf: нет ничего плохого в том, чтобы отвечать на вопросы самостоятельно. На самом деле это поощряется.   -  person Thomas Bonini    schedule 19.06.2011
comment
Этот вопрос добавлен, чтобы ответить на этот преждевременно закрытый вопрос: stackoverflow.com/q/11712020/14065   -  person Martin York    schedule 29.07.2012
comment
Ирония возникает, когда этот закрывается как дубликат того. Мне нравится твой ответ, кстати. Я надеюсь, что другой спрашивающий увидит это.   -  person chris    schedule 30.07.2012
comment
Бессовестный захват репутации. Нет, правда, хорошая работа. Хороший пример того, что простые вопросы не обязательно должны быть глупыми.   -  person Konrad Rudolph    schedule 30.07.2012
comment
@KonradRudolph: нужно всего 5 правок, чтобы задать вопрос сообществу. Скорее результат отвращения к людям, которые бесполезны для новых пользователей.   -  person Martin York    schedule 30.07.2012
comment
Я действительно ценю это. это не первый раз, поэтому я вижу, как закрываются скромные qns   -  person PermanentGuest    schedule 30.07.2012
comment
stackoverflow.com/q/11712020/14065 снова открыт. Должны ли мы сейчас закрыть это как обман и попросить мод объединить ответ?   -  person sbi    schedule 30.07.2012
comment
@FredOverlow: Если это действительно обман FAQ, то и тот, который является обманом...   -  person sbi    schedule 30.07.2012


Ответы (2)


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

В то время как семантика уничтожения объектов класса определяется деструкторами, уничтожение скалярного объекта всегда не является операцией. В частности, удаление переменной-указателя не уничтожает указатель.

Объекты с ограниченной областью действия

автоматические объекты

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

void some_function()
{
    Foo a;
    Foo b;
    if (some_condition)
    {
        Foo y;
        Foo z;
    }  <--- z and y are destructed here
}  <--- b and a are destructed here

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

Это приводит к одному из самых важных правил C++:

Деструкторы никогда не должны бросать.

нелокальные статические объекты

Статические объекты, определенные в области пространства имен (обычно называемые «глобальными переменными»), и статические элементы данных уничтожаются в порядке, обратном их определению, после выполнения main:

struct X
{
    static Foo x;   // this is only a *declaration*, not a *definition*
};

Foo a;
Foo b;

int main()
{
}  <--- y, x, b and a are destructed here

Foo X::x;           // this is the respective definition
Foo y;

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

Если исключение покидает деструктор статического объекта, вызывается функция std::terminate.

локальные статические объекты

Статические объекты, определенные внутри функций, создаются, когда (и если) поток управления проходит через их определение в первый раз.1 Они уничтожаются в обратном порядке после выполнения main:

Foo& get_some_Foo()
{
    static Foo x;
    return x;
}

Bar& get_some_Bar()
{
    static Bar y;
    return y;
}

int main()
{
    get_some_Bar().do_something();    // note that get_some_Bar is called *first*
    get_some_Foo().do_something();
}  <--- x and y are destructed here   // hence y is destructed *last*

Если исключение покидает деструктор статического объекта, вызывается функция std::terminate.

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

подобъекты базового класса и подобъекты-члены

Когда поток управления покидает тело деструктора объекта, его подобъекты-члены (также известные как его «члены данных») уничтожаются в порядке, обратном их определению. После этого его подобъекты базового класса уничтожаются в порядке, обратном списку спецификаторов базы:

class Foo : Bar, Baz
{
    Quux x;
    Quux y;

public:

    ~Foo()
    {
    }  <--- y and x are destructed here,
};          followed by the Baz and Bar base class subobjects

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

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

элементы массива

Элементы массива уничтожаются в порядке убывания. Если во время создания n-го элемента возникает исключение, элементы с n-1 по 0 уничтожаются до распространения исключения.

временные объекты

Временный объект создается при оценке выражения prvalue типа класса. Наиболее ярким примером выражения prvalue является вызов функции, которая возвращает объект по значению, например T operator+(const T&, const T&). При нормальных обстоятельствах временный объект уничтожается, когда полностью оценивается полное выражение, лексически содержащее значение prvalue:

__________________________ full-expression
              ___________  subexpression
              _______      subexpression
some_function(a + " " + b);
                          ^ both temporary objects are destructed here

Приведенный выше вызов функции some_function(a + " " + b) является полным выражением, поскольку он не является частью более крупного выражения (вместо этого он является частью оператора-выражения). Следовательно, все временные объекты, созданные во время вычисления подвыражений, будут уничтожены точкой с запятой. Таких временных объектов два: первый строится при первом добавлении, а второй строится при втором добавлении. Второй временный объект будет уничтожен раньше первого.

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

Если локальная ссылка инициализируется выражением prvalue, время жизни временного объекта расширяется до области действия локальной ссылки, поэтому вы не получите оборванную ссылку:

{
    const Foo& r = a + " " + b;
                              ^ first temporary (a + " ") is destructed here
    // ...
}  <--- second temporary (a + " " + b) is destructed not until here

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

const int& r = i + j;

Динамические объекты и массивы

В следующем разделе уничтожить X означает «сначала уничтожить X, а затем освободить базовую память». Точно так же создать X означает «сначала выделить достаточно памяти, а затем построить там X».

динамические объекты

Динамический объект, созданный с помощью p = new Foo, уничтожается с помощью delete p. Если вы забудете delete p, у вас есть утечка ресурсов. Вы никогда не должны пытаться выполнить одно из следующих действий, поскольку все они приводят к неопределенному поведению:

  • уничтожить динамический объект через delete[] (обратите внимание на квадратные скобки), free или любым другим способом
  • уничтожить динамический объект несколько раз
  • получить доступ к динамическому объекту после его уничтожения

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

динамические массивы

Динамический массив, созданный с помощью p = new Foo[n], уничтожается с помощью delete[] p (обратите внимание на квадратные скобки). Если вы забудете delete[] p, у вас утечка ресурсов. Вы никогда не должны пытаться выполнить одно из следующих действий, поскольку все они приводят к неопределенному поведению:

  • уничтожить динамический массив через delete, free или любым другим способом
  • уничтожить динамический массив несколько раз
  • получить доступ к динамическому массиву после его уничтожения

Если во время создания n-го элемента возникает исключение, элементы с n-1 по 0 уничтожаются в порядке убывания, основная память освобождается, а исключение распространяется.

(Обычно для динамических массивов следует предпочесть std::vector<Foo>, а не Foo*. Это значительно упрощает написание правильного и надежного кода.)

умные указатели с подсчетом ссылок

Динамический объект, управляемый несколькими объектами std::shared_ptr<Foo>, уничтожается во время уничтожения последнего объекта std::shared_ptr<Foo>, участвовавшего в совместном использовании этого динамического объекта.

(Обычно для общих объектов следует предпочесть std::shared_ptr<Foo>, а не Foo*. Это значительно упрощает написание правильного и надежного кода.)

person fredoverflow    schedule 19.06.2011
comment
нет упоминания о порядке уничтожения статических локальных переменных по сравнению со статическими глобальными переменными - person Nick; 22.06.2011
comment
Предлагаю подробно описать случай, когда у вас есть автоматический объект в непустой функции. - person AerandiR; 20.06.2012
comment
@FredOverflow В отношении динамических массивов обычно следует предпочесть std::vector<Foo>, а не Foo*. - На самом деле, в большинстве случаев std::deque<Foo> лучше, чем std::vector<Foo>, но это другой разговор. - person Mihai Todor; 17.07.2013
comment
@MihaiTodor Я видел, как это довольно много проповедовали, но на практике кажется, что все используют std::vector вместо std::deque. Здесь я говорю только за себя, но мне нравится, когда моя память непрерывна. - person fredoverflow; 17.07.2013
comment
@FredOverflow Надеюсь, пользователи не забудут resize() его соответствующим образом, прежде чем вставлять в него элементы :) - person Mihai Todor; 17.07.2013
comment
@MihaiTodor Не нужно беспокоиться, std::vector::push_back амортизировал постоянное время. - person fredoverflow; 17.07.2013
comment
@FredOverflow Меня беспокоит перераспределение в некоторых сценариях, особенно если это вектор сложных объектов, поскольку вы не получаете никаких предупреждений, когда это происходит. Кроме того, я нашел убедительным совет Херба Саттера: gotw.ca/gotw/054.htm поэтому я не уверен, стоит ли беспокоиться о небольшом снижении производительности из-за несмежной памяти. - person Mihai Todor; 18.07.2013

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

Мы будем использовать этот объект в качестве примера:

class Test
{
    public:
        Test()                           { std::cout << "Created    " << this << "\n";}
        ~Test()                          { std::cout << "Destroyed  " << this << "\n";}
        Test(Test const& rhs)            { std::cout << "Copied     " << this << "\n";}
        Test& operator=(Test const& rhs) { std::cout << "Assigned   " << this << "\n";}
};

В C++ существует три (четыре в C++11) различных типа объекта, и тип объекта определяет продолжительность жизни объекта.

  • Статические объекты продолжительности хранения
  • Объекты продолжительности автоматического хранения
  • Объекты длительности динамического хранения
  • (В С++ 11) Объекты продолжительности хранения потоков

Статические объекты продолжительности хранения

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

Test  global;
int main()
{
    std::cout << "Main\n";
}

> ./a.out
Created    0x10fbb80b0
Main
Destroyed  0x10fbb80b0

Примечание 1. Существует два других типа объекта длительности статического хранения.

статические переменные-члены класса.

Они во всех смыслах и целях аналогичны глобальным переменным с точки зрения продолжительности жизни.

статические переменные внутри функции.

Это лениво созданные статические объекты длительности хранения. Они создаются при первом использовании (в потокобезопасном поместье для C++11). Как и другие статические объекты продолжительности хранения, они уничтожаются, когда приложение завершает работу.

Порядок строительства/разрушения

  • Порядок построения внутри единицы компиляции четко определен и совпадает с декларацией.
  • Порядок построения между единицами компиляции не определен.
  • Порядок разрушения прямо противоположен порядку построения.

Объекты продолжительности автоматического хранения

Это наиболее распространенный тип объектов, который вы должны использовать в 99% случаев.

Это три основных типа автоматических переменных:

  • локальные переменные внутри функции/блока
  • переменные-члены внутри класса/массива.
  • временные переменные.

Локальные переменные

При выходе из функции/блока все переменные, объявленные внутри этой функции/блока, будут уничтожены (в порядке, обратном созданию).

int main()
{
     std::cout << "Main() START\n";
     Test   scope1;
     Test   scope2;
     std::cout << "Main Variables Created\n";


     {
           std::cout << "\nblock 1 Entered\n";
           Test blockScope;
           std::cout << "block 1 about to leave\n";
     } // blockScope is destrpyed here

     {
           std::cout << "\nblock 2 Entered\n";
           Test blockScope;
           std::cout << "block 2 about to leave\n";
     } // blockScope is destrpyed here

     std::cout << "\nMain() END\n";
}// All variables from main destroyed here.

> ./a.out
Main() START
Created    0x7fff6488d938
Created    0x7fff6488d930
Main Variables Created

block 1 Entered
Created    0x7fff6488d928
block 1 about to leave
Destroyed  0x7fff6488d928

block 2 Entered
Created    0x7fff6488d918
block 2 about to leave
Destroyed  0x7fff6488d918

Main() END
Destroyed  0x7fff6488d930
Destroyed  0x7fff6488d938

переменные-члены

Срок жизни переменных-членов привязан к объекту, которому они принадлежат. Когда продолжительность жизни владельца заканчивается, продолжительность жизни всех его членов также заканчивается. Поэтому вам нужно посмотреть на срок службы владельца, который подчиняется тем же правилам.

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

  • Таким образом, для членов класса они создаются в порядке объявления
    и уничтожаются в порядке, обратном объявлению.
  • Таким образом, для членов массива они создаются в порядке 0-->top
    и уничтожаются в обратном порядке top-->0

временные переменные

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

std::string   data("Text.");

std::cout << (data + 1); // Here we create a temporary object.
                         // Which is a std::string with '1' added to "Text."
                         // This object is streamed to the output
                         // Once the statement has finished it is destroyed.
                         // So the temporary no longer exists after the ';'

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

Объекты длительности динамического хранения

Эти объекты имеют динамическую продолжительность жизни и создаются с помощью new и уничтожаются с помощью вызова delete.

int main()
{
    std::cout << "Main()\n";
    Test*  ptr = new Test();
    delete ptr;
    std::cout << "Main Done\n";
}

> ./a.out
Main()
Created    0x1083008e0
Destroyed  0x1083008e0
Main Done

Для разработчиков, использующих языки со сборщиком мусора, это может показаться странным (управление сроком жизни вашего объекта). Но проблема не так серьезна, как кажется. В C++ не принято напрямую использовать динамически размещаемые объекты. У нас есть объекты управления, чтобы контролировать срок их службы.

Ближе всего к большинству других языков, собранных GC, является std::shared_ptr. Это будет отслеживать количество пользователей динамически созданного объекта, и когда все они уйдут, автоматически вызовет delete (я думаю об этом как о лучшей версии обычного объекта Java).

int main()
{
    std::cout << "Main Start\n";
    std::shared_ptr<Test>  smartPtr(new Test());
    std::cout << "Main End\n";
} // smartPtr goes out of scope here.
  // As there are no other copies it will automatically call delete on the object
  // it is holding.

> ./a.out
Main Start
Created    0x1083008e0
Main Ended
Destroyed  0x1083008e0

Объекты продолжительности хранения потоков

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

person Martin York    schedule 29.07.2012