Deleting a pointer to an incomplete class

K

Kai-Uwe Bux

Does the following have undefined behavior?

struct X;

struct Y {

X * x_ptr;

Y ( void )
: x_ptr ( 0 )
{}

~Y ( void ) {
delete x_ptr;
}

};

struct X {};

int main ( void ) {
Y y;
}


Note that struct X is defined after struct Y is defined but before a
variable of type Y is declared. I feel that it should depend on when/where
the destructor ~Y is instantiated; but the language in the standard is
frightening and I am not sure that, left to my own devices, I would arrive
at the commonly accepted interpretation.

I just note that Comeau gives a warning and g++ accepts the code without
complaint.


Best

Kai-Uwe Bux
 
A

Alf P. Steinbach

* Kai-Uwe Bux:
Does the following have undefined behavior?

struct X;

struct Y {

X * x_ptr;

Y ( void )
: x_ptr ( 0 )
{}

~Y ( void ) {
delete x_ptr;
}

};

struct X {};

int main ( void ) {
Y y;
}


Note that struct X is defined after struct Y is defined but before a
variable of type Y is declared. I feel that it should depend on when/where
the destructor ~Y is instantiated; but the language in the standard is
frightening and I am not sure that, left to my own devices, I would arrive
at the commonly accepted interpretation.

I just note that Comeau gives a warning and g++ accepts the code without
complaint.

You can "delete" a pointer to incomplete type. But IIRC you have
Undefined Behavior if that type then defines a non-trivial destructor.
And that was the problem with Herb Sutter's original GOTW on the PIMPL
idiom, where he employed std::auto_ptr for the implementation object.
 
K

Kai-Uwe Bux

Alf said:
* Kai-Uwe Bux:

You can "delete" a pointer to incomplete type. But IIRC you have
Undefined Behavior if that type then defines a non-trivial destructor.
And that was the problem with Herb Sutter's original GOTW on the PIMPL
idiom, where he employed std::auto_ptr for the implementation object.

Thanks! You are refering to [5.3.4/5], right?

If the object being deleted has incomplete class type at the point of
deletion and the complete class has a non-trivial destructor or a
deallocation function, the behavior is undefined.

Now, the question becomes: which line in the program is the point of
deletion? I would think, in the snippet I posted, it's the line in the
destructor of Y, i.e., the following has UB:

#include <iostream>
#include <ostream>

struct X;

struct Y {

X * x_ptr;

Y ( X * ptr = 0 )
: x_ptr ( ptr )
{}

~Y ( void ) {
delete x_ptr;
}

};

struct X {

~X ( void ) {
std::cout << "crash me\n";
}

};

int main ( void ) {
Y y ( new X );
}

Thus, it makes perfect sense that it does not print anything with g++ :)


However, I was more interested in templates anyway (posted an oversimplified
piece of code). So what about these:


A (typedef)
===========

#include <iostream>
#include <ostream>

template < typename X>
struct Y {

X * x_ptr;

Y ( X * ptr = 0 )
: x_ptr ( ptr )
{}

~Y ( void ) {
delete x_ptr;
}

};

struct X;

typedef Y<X> YX;

struct X {

~X ( void ) {
std::cout << "crash me\n";
}

};

int main ( void ) {
YX yx ( new X );
}



B (inheritance)
===============

#include <iostream>
#include <ostream>

template < typename X>
struct Y {

X * x_ptr;

Y ( X * ptr = 0 )
: x_ptr ( ptr )
{}

~Y ( void ) {
delete x_ptr;
}

};

struct X;

struct YX : public Y<X> {

YX ( X * ptr = 0 )
: Y<X>( ptr )
{}

};

struct X {

~X ( void ) {
std::cout << "crash me\n";
}

};

int main ( void ) {
YX yx ( new X );
}


C (position of function definition)
===================================

#include <iostream>
#include <ostream>

template < typename X>
struct Y {

X * x_ptr;

Y ( X * ptr = 0 )
: x_ptr ( ptr )
{}

~Y ( void ) {
delete x_ptr;
}

};

struct X;

void f ( X * ptr = 0 ) {
Y<X> y ( ptr );
}

struct X {

~X ( void ) {
std::cout << "crash me\n";
}

};

int main ( void ) {
f ( new X );
}



In my experiments with g++, all these programs print "crash me". Where is
the "point of deletion" in these programs when template instantiation
issues enter the picture? Which of the above have UB?


Best

Kai-Uwe Bux
 
A

Alf P. Steinbach

* Kai-Uwe Bux:
Alf said:
You can "delete" a pointer to incomplete type. But IIRC you have
Undefined Behavior if that type then defines a non-trivial destructor.
And that was the problem with Herb Sutter's original GOTW on the PIMPL
idiom, where he employed std::auto_ptr for the implementation object.

Thanks! You are refering to [5.3.4/5], right?

If the object being deleted has incomplete class type at the point of
deletion and the complete class has a non-trivial destructor or a
deallocation function, the behavior is undefined.

If I had looketh in the Holy Standard I guess that would be it, yes.

Now, the question becomes: which line in the program is the point of
deletion? I would think, in the snippet I posted, it's the line in the
destructor of Y, i.e., the following has UB:

#include <iostream>
#include <ostream>

struct X;

struct Y {

X * x_ptr;

Y ( X * ptr = 0 )
: x_ptr ( ptr )
{}

~Y ( void ) {

Uh, C-ism, please don't do that Kai! Hurts me... ;-)

delete x_ptr;
}

};

struct X {

~X ( void ) {
std::cout << "crash me\n";
}

};

int main ( void ) {
Y y ( new X );
}

Thus, it makes perfect sense that it does not print anything with g++ :)
Yes.


However, I was more interested in templates anyway (posted an oversimplified
piece of code). So what about these:


A (typedef)
===========

#include <iostream>
#include <ostream>

template < typename X>
struct Y {

X * x_ptr;

Y ( X * ptr = 0 )
: x_ptr ( ptr )
{}

~Y ( void ) {
delete x_ptr;
}

};

struct X;

typedef Y<X> YX;

struct X {

~X ( void ) {
std::cout << "crash me\n";
}

};

int main ( void ) {
YX yx ( new X );
}

Hm. Hm. I /think/ the template isn't instantiated until 'main' (i.e.,
the typedef doesn't do anything other than introduce a name), where the
definition of X is known, and so it should be OK.

However, there's also the practical matter of compilers that don't
implement two-phase template instantiation.

B (inheritance)
===============

#include <iostream>
#include <ostream>

template < typename X>
struct Y {

X * x_ptr;

Y ( X * ptr = 0 )
: x_ptr ( ptr )
{}

~Y ( void ) {
delete x_ptr;
}

};

struct X;

struct YX : public Y<X> {

I think that right here Y<X> is instantiated, with still incomplete type
X, and thus Undefined Behavior.

YX ( X * ptr = 0 )
: Y<X>( ptr )
{}

};
>
struct X {

~X ( void ) {
std::cout << "crash me\n";
}

};

int main ( void ) {
YX yx ( new X );
}


C (position of function definition)
===================================

#include <iostream>
#include <ostream>

template < typename X>
struct Y {

X * x_ptr;

Y ( X * ptr = 0 )
: x_ptr ( ptr )
{}

~Y ( void ) {
delete x_ptr;
}

};

struct X;

void f ( X * ptr = 0 ) {
Y<X> y ( ptr );
}

Instantiation with incomplete type X, Undefined Behavior.

struct X {

~X ( void ) {
std::cout << "crash me\n";
}

};

int main ( void ) {
f ( new X );
}



In my experiments with g++, all these programs print "crash me".

Hm, B and C "should", as far as I understand it, exhibit UB, which of
course means they could by chance print "crash me", but in practical
terms a compiler-generated empty X destructor should be invoked.

Checking...

I get the same result as your g++-testing by using MSVC 7.1. However,
using MingW g++ 3.4.4 B and C do not print anything (and warnings are
issued for the destructor calls), while A does print "crash me" (and no
warnings). This is consistent with g++ generally being much more
standard-conforming wrt. templates than MSVC.

Where is
the "point of deletion" in these programs when template instantiation
issues enter the picture? Which of the above have UB?

See above. I think I got it right; at least, testing above indicates
that. So, A is OK (A OK ;-)), while B and C are UB.
 
K

Kai-Uwe Bux

Alf said:
* Kai-Uwe Bux: [snip]
struct Y {

X * x_ptr;

Y ( X * ptr = 0 )
: x_ptr ( ptr )
{}

~Y ( void ) {

Uh, C-ism, please don't do that Kai! Hurts me... ;-)

I did not pick that up in C, which I never learned. I like this more verbose
notation because it allows me to quickly identify function definitions as
opposed to function calls (thereby it makes the code just a little bit more
grep-able). Coming from Modula-2, I don't mind verbosity at all :)

(just a nit: It's "Kai-Uwe"; one word, not two, note the hyphen.)

[snip]
Hm. Hm. I /think/ the template isn't instantiated until 'main' (i.e.,
the typedef doesn't do anything other than introduce a name), where the
definition of X is known, and so it should be OK.

However, there's also the practical matter of compilers that don't
implement two-phase template instantiation.



I think that right here Y<X> is instantiated, with still incomplete type
X, and thus Undefined Behavior.



Instantiation with incomplete type X, Undefined Behavior.



Hm, B and C "should", as far as I understand it, exhibit UB, which of
course means they could by chance print "crash me", but in practical
terms a compiler-generated empty X destructor should be invoked.

Checking...

I get the same result as your g++-testing by using MSVC 7.1. However,
using MingW g++ 3.4.4 B and C do not print anything (and warnings are
issued for the destructor calls), while A does print "crash me" (and no
warnings). This is consistent with g++ generally being much more
standard-conforming wrt. templates than MSVC.



See above. I think I got it right; at least, testing above indicates
that. So, A is OK (A OK ;-)), while B and C are UB.


Thanks a lot. I also tried Comeau; and it agrees with your assesment.


Best

Kai-Uwe Bux
 

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,772
Messages
2,569,593
Members
45,111
Latest member
KetoBurn
Top