Saturday 11 November 2017

exception - Object destruction in C++

itemprop="text">

When exactly are objects destroyed in
C++, and what does that mean? Do I have to destroy them manually, since there is no
Garbage Collector? How do exceptions come into
play?




(Note:
This is meant to be an entry to href="https://stackoverflow.com/questions/tagged/c++-faq"> C++ FAQ. If you
want to critique the idea of providing an FAQ in this form, then href="https://meta.stackexchange.com/questions/68647/setting-up-a-faq-for-the-c-tag">the
posting on meta that started all this would be the place to do that. Answers
to that question are monitored in the href="http://chat.stackoverflow.com/rooms/10/c-lounge">C++ chatroom, 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.)


class="post-text" itemprop="text">
class="normal">Answer



In the
following text, I will distinguish between scoped objects, whose
time of destruction is statically determined by their enclosing scope (functions,
blocks, classes, expressions), and dynamic objects, whose exact
time of destruction is generally not known until
runtime.




While the destruction
semantics of class objects are determined by destructors, the destruction of a scalar
object is always a no-op. Specifically, destructing a pointer variable does
not destroy the
pointee.





automatic
objects



Automatic objects (commonly referred to
as "local variables") are destructed, in reverse order of their definition, when control
flow leaves the scope of their
definition:



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



If an
exception is thrown during the execution of a function, all previously constructed
automatic objects are destructed before the exception is propagated to the caller. This
process is called stack unwinding. During stack unwinding, no
further exceptions may leave the destructors of the aforementioned previously
constructed automatic objects. Otherwise, the function
std::terminate is
called.



This leads to one of the most important
guidelines in C++:





Destructors should never
throw.




non-local
static objects




Static objects
defined at namespace scope (commonly referred to as "global variables") and static data
members are destructed, in reverse order of their definition, after the execution of
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;


Note that the
relative order of construction (and destruction) of static objects defined in different
translation units is undefined.




If an
exception leaves the destructor of a static object, the function
std::terminate is
called.



local static
objects



Static objects defined inside functions
are constructed when (and if) control flow passes through their definition for the first
time.1
They are destructed in reverse order after the
execution of
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*


If an exception
leaves the destructor of a static object, the function
std::terminate is
called.



1: This is an extremely
simplified model. The initialization details of static objects are actually much more
complicated.




base class
subobjects and member subobjects



When control
flow leaves the destructor body of an object, its member subobjects (also known as its
"data members") are destructed in reverse order of their definition. After that, its
base class subobjects are destructed in reverse order of the
base-specifier-list:



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


If an
exception is thrown during the construction of one of
Foo's subobjects, then all its previously constructed
subobjects will be destructed before the exception is propagated. The
Foo destructor, on the other hand, will
not be executed, since the Foo object was
never fully constructed.



Note that the
destructor body is not responsible for destructing the data members themselves. You only
need to write a destructor if a data member is a handle to a resource that needs to be
released when the object is destructed (such as a file, a socket, a database connection,
a mutex, or heap memory).




array
elements



Array elements are destructed in
descending order. If an exception is thrown during the construction
of the n-th element, the elements n-1 to 0 are destructed before the exception is
propagated.



temporary
objects



A temporary object is constructed when
a prvalue expression of class type is evaluated. The most prominent example of a prvalue
expression is the call of a function that returns an object by value, such as
T operator+(const T&, const T&). Under normal
circumstances, the temporary object is destructed when the full-expression that
lexically contains the prvalue is completely
evaluated:



__________________________
full-expression

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


The above
function call some_function(a + " " + b) is a full-expression
because it is not part of a larger expression (instead, it is part of an
expression-statement). Hence, all temporary objects that are constructed during the
evaluation of the subexpressions will be destructed at the semicolon. There are two such
temporary objects: the first is constructed during the first addition, and the second is
constructed during the second addition. The second temporary object will be destructed
before the first.



If an exception is thrown
during the second addition, the first temporary object will be destructed properly
before propagating the exception.




If
a local reference is initialized with a prvalue expression, the lifetime of the
temporary object is extended to the scope of the local reference, so you won't get a
dangling reference:



{

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


If a
prvalue expression of non-class type is evaluated, the result is a
value, not a temporary object. However, a temporary object
will be constructed if the prvalue is used to initialize a
reference:




const
int& r = i +
j;




In
the following section, destroy X means "first destruct X and then
release the underlying memory".
Similarly, create X means
"first allocate enough memory and then construct X
there".



dynamic
objects




A dynamic object created via
p = new Foo is destroyed via delete p.
If you forget to delete p, you have a resource leak. You should
never attempt to do one of the following, since they all lead to undefined
behavior:




  • destroy a
    dynamic object via delete[] (note the square brackets),
    free or any other means

  • destroy
    a dynamic object multiple times

  • access a dynamic object
    after it has been
    destroyed



If an exception
is thrown during the construction of a dynamic object, the
underlying memory is released before the exception is
propagated.

(The destructor will not be
executed prior to memory release, because the object was never fully
constructed.)



dynamic
arrays



A dynamic array created via
p = new Foo[n] is destroyed via delete[]
p
(note the square brackets). If you forget to delete[]
p
, you have a resource leak. You should never attempt to do one of the
following, since they all lead to undefined
behavior:




  • destroy a
    dynamic array via delete, free or any
    other means

  • destroy a dynamic array multiple
    times

  • access a dynamic array after it has been
    destroyed




If
an exception is thrown during the construction of the n-th element,
the elements n-1 to 0 are destructed in descending order, the underlying memory is
released, and the exception is propagated.



(You
should generally prefer std::vector over
Foo* for dynamic arrays. It makes writing correct and robust
code much easier.)



reference-counting smart
pointers



A dynamic object managed by several
std::shared_ptr objects is destroyed during the
destruction of the last std::shared_ptr object
involved in sharing that dynamic
object.




(You should generally prefer
std::shared_ptr over Foo*
for shared objects. It makes writing correct and robust code much
easier.)


No comments:

Post a Comment

php - file_get_contents shows unexpected output while reading a file

I want to output an inline jpg image as a base64 encoded string, however when I do this : $contents = file_get_contents($filename); print &q...