~ destructor doesn't destroy object?

M

muler

Consider:

#include <iostream>

class My_class {
public:
void print() const { std::cout << "alive and well!\n"; }
};

int main(int argc, char* argv[])
{
My_class mc;
My_class& rmc = mc;
// ..
mc.~My_class();

// mc is destroyed above. right? how come the below prints "alive
and well!"
mc.print();

return 0;
}
 
I

Ian Collins

muler said:
Consider:

#include <iostream>

class My_class {
public:
void print() const { std::cout << "alive and well!\n"; }
};

int main(int argc, char* argv[])
{
My_class mc;
My_class& rmc = mc;
// ..
mc.~My_class();

// mc is destroyed above. right? how come the below prints "alive
and well!"

Define destroyed.

All you have done is call the destructor. My_class doesn't have a
destructor, so ~My_class() is a compiler generated empty function.
 
J

James Kanze

Consider:
#include <iostream>
class My_class {
public:
void print() const { std::cout << "alive and well!\n"; }
};
int main(int argc, char* argv[])
{
My_class mc;
My_class& rmc = mc;
// ..
mc.~My_class();
// mc is destroyed above. right? how come the below prints "alive
and well!"
mc.print();
return 0;
}

Because accessing an object after it has been destroyed is
undefined behavior, so anything can happen. In this case, the
member function doesn't actually access the object's data, and
since it isn't virtual, it can be called without accessing the
object either, so it works, just as calling a free function
would (but you can't count on this---the compiler could arrange
things so that it didn't work). And of course, in this case,
you also have a trivial destructor, so it doesn't do anything
anyway---even if the function print accessed member data, it
would likely work.
 
F

Fred Zwarts

muler said:
Consider:

#include <iostream>

class My_class {
public:
void print() const { std::cout << "alive and well!\n"; }
};

int main(int argc, char* argv[])
{
My_class mc;
My_class& rmc = mc;
// ..
mc.~My_class();

// mc is destroyed above. right? how come the below prints "alive
and well!"
mc.print();

return 0;
}

You should not use an object when it has been destructed.
The C++ language does not describe what should happen
if you do such things.
If you do thing you should not do, anything can happen.
Don't be surprised, nothing is unexpected then.
 
J

Johannes Schaub (litb)

muler said:
Consider:

#include <iostream>

class My_class {
public:
void print() const { std::cout << "alive and well!\n"; }
};

int main(int argc, char* argv[])
{
My_class mc;
My_class& rmc = mc;
// ..
mc.~My_class();

// mc is destroyed above. right? how come the below prints "alive
and well!"
mc.print();

return 0;
}

First, your object is still alive, and your destructor call seems to be a
no-op, except for introducing a sequence point. The Standard says that for
the case when an object has a trivial destructor, the lifetime ends when
storage is released or reused (by placement new, for instance, or by calling
delete).

If however your class is a non-POD, beware of 12.7.1, which says:

"For an object of non-POD class type (clause 9), before the constructor
begins execution and after the destructor finishes execution, referring to
any nonstatic member or base class of the object results in undefined
behavior.".

Your class is a POD, so your code seems to be compliant. This stuff is about
C++03 - in C++0x many things changed, so be sure you read about the changes
first.
 
M

Michael Bülow Jensen

muler said:
Consider:

#include <iostream>

class My_class {
public:
void print() const { std::cout << "alive and well!\n"; }
};

int main(int argc, char* argv[])
{
My_class mc;
My_class& rmc = mc;
// ..
mc.~My_class();

// mc is destroyed above. right? how come the below prints "alive
and well!"
mc.print();

return 0;
}

mc.~My_class(); does NOT destroy the mc-object.

The destructor is called WHEN the object is being destroyed, to handle any
special cleanup you might need.

i.e. deallocate memory allocated in the constructor:

#include <iostream>

class MyClass{
private:
int *m_pData;
public:
MyClass(){
m_pData = new int[256];
}
~MyClass(){
if(m_pData)
delete [] m_pData;
}
void print() const {
std::cout << "alive and well!" << std::endl;
}
}

int main(int argc, char* argv[]){
MyClass mc;

mc.print();

return 0;
}

Without the destructor the 256 int's would not be deallocated (==
memory-leak).

By making an explicit call to the destructor you run the "cleanup" code, BUT
the object is not destroyed. It will exist until it goes out of scope:

int main(int argc, char* argv[]){
MyClass mc;

mc.~MyClass(); // this runs cleanup-code, but does NOT destroy the
actual object (the 256 int's are being deallocated)

mc.print(); // the object still exists, and this is a valid call to
"print"

return 0;
}
//NOW the object goes out of scope and gets destroyed

This code should actually cause a runtime error. When the main-function ends
the mc-object will go out of scope. When that happens a call to the
destructor will be made AGAIN and trying to deallocate the 256 int's again
will fail.
 
M

Michael Bülow Jensen

Johannes Schaub (litb) said:
muler said:
Consider:

#include <iostream>

class My_class {
public:
void print() const { std::cout << "alive and well!\n"; }
};

int main(int argc, char* argv[])
{
My_class mc;
My_class& rmc = mc;
// ..
mc.~My_class();

// mc is destroyed above. right? how come the below prints "alive
and well!"
mc.print();

return 0;
}

First, your object is still alive, and your destructor call seems to be a
no-op, except for introducing a sequence point. The Standard says that for
the case when an object has a trivial destructor, the lifetime ends when
storage is released or reused (by placement new, for instance, or by
calling
delete).

If however your class is a non-POD, beware of 12.7.1, which says:

"For an object of non-POD class type (clause 9), before the constructor
begins execution and after the destructor finishes execution, referring to
any nonstatic member or base class of the object results in undefined
behavior.".

Your class is a POD, so your code seems to be compliant. This stuff is
about
C++03 - in C++0x many things changed, so be sure you read about the
changes
first.

C++0x ?!

Hmm... Seems alot has happened while I was taking a break from C++...

Any documents on C++0x you can recommend?
 
J

Johannes Schaub (litb)

Michael said:
muler said:
Consider:

#include <iostream>

class My_class {
public:
void print() const { std::cout << "alive and well!\n"; }
};

int main(int argc, char* argv[])
{
My_class mc;
My_class& rmc = mc;
// ..
mc.~My_class();

// mc is destroyed above. right? how come the below prints "alive
and well!"
mc.print();

return 0;
}

mc.~My_class(); does NOT destroy the mc-object.

The destructor is called WHEN the object is being destroyed, to handle any
special cleanup you might need.

i.e. deallocate memory allocated in the constructor:

#include <iostream>

class MyClass{
private:
int *m_pData;
public:
MyClass(){
m_pData = new int[256];
}
~MyClass(){
if(m_pData)
delete [] m_pData;
}
void print() const {
std::cout << "alive and well!" << std::endl;
}
}

int main(int argc, char* argv[]){
MyClass mc;

mc.print();

return 0;
}

Without the destructor the 256 int's would not be deallocated (==
memory-leak).

By making an explicit call to the destructor you run the "cleanup" code,
BUT the object is not destroyed. It will exist until it goes out of scope:

int main(int argc, char* argv[]){
MyClass mc;

mc.~MyClass(); // this runs cleanup-code, but does NOT destroy the
actual object (the 256 int's are being deallocated)

mc.print(); // the object still exists, and this is a valid call to
"print"

return 0;
}

The object is not destroyed, but it is not alive anymore. Lifetime is a
property of an object, and an object can be alive/not alive. Calling any
non-static member function for an object that's out of its lifetime and
whose type is a non-POD results in undefined behavior (there is an exception
for calls that happen during the destructor or constructor is being called -
but outside these frames no such exception is made).

The Standard says in 3.8/1: "The lifetime of an object of type T ends when
.... if T is a class type with a non-trivial destructor (12.4), the
destructor call starts". Notice that any class with a non-trivial destructor
is not a POD. And then it says in 3.8/6 "Similarly, before the lifetime of
an object has started but after the storage which the object will occupy has
been allocated or, after the lifetime of an object has ended and before the
storage which the object occupied is reused or released, any lvalue which
refers to the original object may be used but only in limited ways. [...] ;
if the original object will be or was of a non-POD class type, the program
has undefined behavior if: the lvalue is used to access a non-static data
member or call a non-static member function of the object [...]"

Of course, the wording is misleading here, i think, because the object *is*
still of non-POD class type. It still exists albeit it's not alive
anymore/yet. But it unambiguously applies to "mc.print();" above: MyClass is
a non-POD, and the call happens outside the lifetime of the object.

In this sense, the destructor is literally a "special member function", and
carries a deeper meaning than just being called automatically when an object
is destroyed: It affects lifetime - for instance an implementation may tweak
the v-table of an object, among other things, and is granted to do that by
the Standard.
 
J

Johannes Schaub (litb)

Juha said:
I think you are confusing object destruction with object deletion
(iow. freeing the memory taken by an object).

If you call the destructor explicitly, the object *is* destroyed, even
though the memory it's taking has not been freed yet. You can try
calling its member functions, but it will be undefined behavior, so
anything can happen (including, among other things, the member function
working just fine).

I'm not sure whether the Standard is *that* explicit about this, and i'm not
even sure whether it's correct. Calling the destructor definitely ends
lifetime in most cases. I would be glad if you pointed out to me the
relevant Standard parts, though.

It's not clear to me that doing so will actually *destroy* the object. The
object still exists, and it's destroyed by the implementation - you can't
seem to do this manually. Take for example 12.7[class.cdtor]/1:"For an
object of non-POD class type (clause 9), before the constructor begins
execution and after the destructor finishes execution, referring to any
nonstatic member or base class of the object results in undefined
behavior.". This sentence would not make sense if the object doesn't exist
anymore at those points. Also, the same for "The lifetime of an object is a
runtime property of the object. The lifetime of an object of type T begins
when..." - same issue: For starting lifetime, you already have to have an
object.

This means: Lifetime != object existence. These are different: An alive
object is up and ready for use. And an existing object is a region of
storage with properties ascribed (including a type), and not necessarily
ready for use.
 
J

James Kanze

I'm not sure whether the Standard is *that* explicit about
this, and i'm not even sure whether it's correct.

It is and it is.
Calling the destructor definitely ends lifetime in most cases.
I would be glad if you pointed out to me the relevant Standard
parts, though.

I don't have my copy of the standard on line here, so this is
just from memory, but there is a whole subsection of section 2
(I think) which treats object lifetime. In that section, it
states quite clearly that object lifetime ends when the
destructor is called, and delimits exactly what you can legally
do with the raw memory between the end of object lifetime and
the moment it is dallocated.
It's not clear to me that doing so will actually *destroy* the
object.

I suppose that you could argue about the wording---I don't think
the standard uses the word "destroy". But the lifetime of the
object definitely ends, and I think Juta's wording corresponds
to a natural use, even if he hasn't quoted the standard
directly.
The object still exists, and it's destroyed by the
implementation - you can't seem to do this manually. Take for
example 12.7[class.cdtor]/1:"For an object of non-POD class
type (clause 9), before the constructor begins execution and
after the destructor finishes execution, referring to any
nonstatic member or base class of the object results in
undefined behavior.". This sentence would not make sense if
the object doesn't exist anymore at those points. Also, the
same for "The lifetime of an object is a runtime property of
the object. The lifetime of an object of type T begins
when..." - same issue: For starting lifetime, you already have
to have an object.
This means: Lifetime != object existence.

Let's not play semantic games. The standard doesn't speak of
"existence" either. It does define lifetime, and specifically
say what you're allowed to do in the interval between the moment
the lifetime ends and the underlying memory is deallocated.
 
J

Johannes Schaub (litb)

James said:
I don't have my copy of the standard on line here, so this is
just from memory, but there is a whole subsection of section 2
(I think) which treats object lifetime. In that section, it
states quite clearly that object lifetime ends when the
destructor is called, and delimits exactly what you can legally
do with the raw memory between the end of object lifetime and
the moment it is dallocated.
Yes, but my point is that lifetime ending will not destroy an object. For
instance, if you are in a class type object, and the destructor starts
execution, you can still dereference "this", and the resulting lvalue must
(because that's the definition of an lvalue) refer to an object or function.
It is not raw storage, but an object outside its lifetime.
I suppose that you could argue about the wording---I don't think
the standard uses the word "destroy". But the lifetime of the
object definitely ends, and I think Juta's wording corresponds
to a natural use, even if he hasn't quoted the standard
directly.
It uses the word "destroy" and "create". At 1.8/1 it says "An object is
created by a definition (3.1), by a new-expression (5.3.4) or by the
implementation (12.2) when needed.". For destroy, it says that in the
individual sections. At 3.6.3/1 for instance: "Destructors (12.4) for
initialized objects of static storage duration (declared at block scope or
at namespace scope) are called as a result of returning from main and as a
result of calling exit (18.3). These objects are destroyed in the reverse
order of the completion of their constructor or of the completion of their
dynamic initialization.".
The object still exists, and it's destroyed by the
implementation - you can't seem to do this manually. Take for
example 12.7[class.cdtor]/1:"For an object of non-POD class
type (clause 9), before the constructor begins execution and
after the destructor finishes execution, referring to any
nonstatic member or base class of the object results in
undefined behavior.". This sentence would not make sense if
the object doesn't exist anymore at those points. Also, the
same for "The lifetime of an object is a runtime property of
the object. The lifetime of an object of type T begins
when..." - same issue: For starting lifetime, you already have
to have an object.
This means: Lifetime != object existence.

Let's not play semantic games. The standard doesn't speak of
"existence" either. It does define lifetime, and specifically
say what you're allowed to do in the interval between the moment
the lifetime ends and the underlying memory is deallocated.

My point is that after lifetime ended, it seems the object is not destoryed
yet.

I'm not actually sure about this, so i say it in vague terms. Because the
Standard does not seem to say where the object is created in the following
code (one could say it is a case of "... or by the implementation when
needed", but that clearly refers to temporary objects, and not to the
following:

int *p = (int*)malloc(sizeof *p);
*p = 0;

For starting lifetime, you have to have an object first, whose lifetime
property you modify. But in the above, the storage created by malloc has no
type, but an object has properties, including a type. And the Standard says
"The properties of an object are determined when the object is created."

So, if "starting lifetime" means "creating an object", and "ending lifetime"
means "destroying an object", i think this issue could be solved by tweaking
the wording of those paragraphs that are affected. In particular, it seems
to me it's necessary to make "lifetime" not a property of an object, but of
a region of storage: If the region of storage is alive, this region gets a
type and the other properties of an object.

But this would be a problem for those cases where a region of storage may
host multiple objects: For "string a[1];", the array is alive as soon as
storage is obtained, but its first element is not yet alive here.

So as you see i'm not sure about this issue. That's why I asked Juha about
some paragraphs of the Standard to show me the matters.
 
J

James Kanze

Yes, but my point is that lifetime ending will not destroy an
object.

That's not how the standard uses it (although admittedly, the
standard doesn't seem to be too consistent in this regard). The
word "destroy" doesn't occur at all in §3.8, which only speaks
of "object lifetime", but in other contexts, like §3.6.3,
"destroy" is used in opposition to "initialization", and in the
library, the effects clause of the destructor is generally
"Destroys an object of class ...".

There is one point to consider, however. The original poster's
class was a POD, with a trivial destructor, and for objects with
non-class types *or* with trivial destructors, the lifetime of
the object doesn't end until "the storage which the object
occupies is reused or releases." So in fact, yes, his code is
well defined and correct. The text of §5.2.4:
"Pseudo-destructor call" is very unclear; it says that "The only
effect is the evaluation of the postfixexpression before the dot
or arrow." Presumably, it is the () which follows which "calls"
the destructor", but §5.2.2 doesn't say anything about this
case. §12.4, on the other hand, says "Once a destructor is
invoked for an object, the object no longer exists; the behavior
is undefined if the destructor is invoked for an object whose
lifetime has ended." With no distinction with regards to
whether the destructor is trivial or not (in contradiction with
§3.8?). According to §3.8, his code is legal, because a
trivial destructor doesn't end the lifetime of the object.
According to §12.4, it is illegal, because once the destructor
is invoked, the object no longer exists---at the very least, the
call of the destructor (even though it is trivial) when the
object goes out of scope will result in undefined behavior. For
various reasons, I'm pretty sure that the intent is for his code
to have undefined behavior, but in so far as his class has a
trivial destructor, I think the standard is ambiguous.
For instance, if you are in a class type object, and the
destructor starts execution, you can still dereference "this",
and the resulting lvalue must (because that's the definition
of an lvalue) refer to an object or function. It is not raw
storage, but an object outside its lifetime.

That's something I've often wondered about myself. From all
appearences, during the execution of a constructor or
destructor, the object does have some special state, which is
neither "existing" nor "raw memory". In a constructor or a
destructor, for example, typeid returns the type of the class
being constructed or destructed, virtual access and dynamic_cast
work, the constructor or destructor can access members, and a
derived class constructor or destructor can convert its this
pointer to a pointer to base, at least implicitly (and
presumably explicitly, although I've had some problems with this
in the past). All of which is in direct contradiction with
§3.8/5. (Again, however, none of this is relevant to the
original example, since it only concerns non-POD's.)
It uses the word "destroy" and "create". At 1.8/1 it says "An
object is created by a definition (3.1), by a new-expression
(5.3.4) or by the implementation (12.2) when needed.". For
destroy, it says that in the individual sections. At 3.6.3/1
for instance: "Destructors (12.4) for initialized objects of
static storage duration (declared at block scope or at
namespace scope) are called as a result of returning from main
and as a result of calling exit (18.3). These objects are
destroyed in the reverse order of the completion of their
constructor or of the completion of their dynamic
initialization.".

Exactly. Destruction is the opposite of
initialization---calling the destructor.
My point is that after lifetime ended, it seems the object is
not destoryed yet.

It's in the process of being destroyed; it's fully destroyed
once the destructor has finished. Provided the destructor is
trivial, or maybe it's provided the object has POD type.
I'm not actually sure about this, so i say it in vague terms.
Because the Standard does not seem to say where the object is
created in the following code (one could say it is a case of
"... or by the implementation when needed", but that clearly
refers to temporary objects, and not to the following:
int *p = (int*)malloc(sizeof *p);
*p = 0;
For starting lifetime, you have to have an object first, whose
lifetime property you modify. But in the above, the storage
created by malloc has no type, but an object has properties,
including a type. And the Standard says "The properties of an
object are determined when the object is created."
So, if "starting lifetime" means "creating an object", and
"ending lifetime" means "destroying an object", i think this
issue could be solved by tweaking the wording of those
paragraphs that are affected. In particular, it seems to me
it's necessary to make "lifetime" not a property of an object,
but of a region of storage: If the region of storage is alive,
this region gets a type and the other properties of an object.

In this particular case (int is a POD), the lifetime of the
object begins when storage with proper alignment is obtained.
IMHO, this still leaves a lot of problems open, consider:

void* p = malloc( std::max(sizeof(double), sizeof(int) );
int* pi = (int*)p;
double* pd = (double*)p;

Do we have two objects, a double and an int? Both occupying the
same storage? (Storage with the proper alignment has been
obtained.) I'd have a tendancy to say that the lifetime of a
POD only starts when it has been used as a given type. And ends
when the space is used as some other type. This is partially
covered by the text "the storage which the object occupies is
reused or released." But what does "reused" really signify
here? *pi and *pd are lvalue expressions. What does §3.10/15
really mean here? Things like:
*pi = 42;
// ...
*pd = 3.14159;
are (presumably) legal, but:
*pi = 42;
printf("%f", *pd);
isn't. Does access in §3.10/15 only mean reading? And what
does this mean with regards to aliasing: I seem to recall a
similar case where g++ failed, e.g.:

void f(int* pi, double* pd)
{
printf("%d", *pi);
*pd = 3.1415;
}

f( pi, pd ); // with pi and pd as above...
But this would be a problem for those cases where a region of
storage may host multiple objects: For "string a[1];", the
array is alive as soon as storage is obtained, but its first
element is not yet alive here.
So as you see i'm not sure about this issue. That's why I
asked Juha about some paragraphs of the Standard to show me
the matters.

The issue is complex, and IMHO there are some problems with the
wording in the standard. I think the intent is clear (but that
may be simply because I've worked under this assumption for so
many years). But I think Juha was just using the word "destroy"
in a more or less everyday sense, and was really only concerned
about the fact that the object was used "after" returning from
the destructor.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top