Уничтожение объектов в C ++ - PullRequest
64 голосов
/ 19 июня 2011

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

(Примечание: это должна быть запись в FAQ по C ++ Stack Overflow . Если вы хотите критиковать идею предоставленияFAQ в этой форме, тогда место для этого будет отправлять на мета, который начал все это . Ответы на этот вопрос отслеживаются в C ++ chatroom , где идея FAQво-первых, поэтому ваш ответ, скорее всего, будет прочитан теми, кто придумал эту идею.)

Ответы [ 2 ]

78 голосов
/ 19 июня 2011

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

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

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

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

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

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 's подобъектов, то все его ранее построенные подобъекты будут уничтожены до распространения исключения.Деструктор Foo, с другой стороны, будет не выполняться, поскольку объект Foo никогда не был полностью построен.

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

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

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

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

Временный объект создается, когда вычисляется предварительное выражение типа класса. Наиболее ярким примером выражения 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;

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

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

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

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

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

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

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

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

35 голосов
/ 29 июля 2012

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

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

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 ++ 11) различных типа объектов в C ++ итип объекта определяет продолжительность жизни объектов.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Thread Storage duration объектов

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

...