undef. behaviour in ctor-exc. handlers

K

Klaus Ahrens

hi all,

acoording to the c++ standard

"15.3.
- -10- Referring to any non-static member or base class of an object in
the handler for a function-try-block of a constructor or destructor for
that object results in undefined behavior."

it is not allowed to refer to l (delete l;) in the following code

#include <iostream>
#include <memory>

using namespace std;

struct L {
L() { cout<<"L() at "<<this<<endl; }
~L() { cout<<"~L() at "<<this<<endl; }
};

class X {
public:
L* l;
X() try : l(new L) {
cout<<"X() at "<<this<<endl;
}
catch (...) { delete l; throw; }

~X(){
cout<<"~X() at "<<this<<endl;
delete l;
}
};

int main(){
try {
X* p = new X;
delete p;
}
catch(...) { std::cout<<"some exception occured"<<std::endl; }
}

i wonder why this has been declared such, because it leads directly
to leaking local ressources. i know herb sutters arguments from 'more
exc. c++' (item 18) where a local pointer could refer to a meanwhile
destoyed subobject (as i understand it). but this seems to be a very
rare situation.

moreover compiled with g++ (3.3.5) the output is

L() at 0x804a018
X() at 0x804a008
~X() at 0x804a008
~L() at 0x804a018

without the leaking L

any idea ?

--
mfg
k ahrens
_____________________________________________
\ phone +49 30 2093 3113 \ :) \
\ fax +49 30 2093 3112 \__________________\
\ mailto:[email protected] \
\ http://www.informatik.hu-berlin.de/~ahrens \
\ ____________________________________________\
 
M

mlimber

Klaus said:
hi all,

acoording to the c++ standard

"15.3.
- -10- Referring to any non-static member or base class of an object in
the handler for a function-try-block of a constructor or destructor for
that object results in undefined behavior."

it is not allowed to refer to l (delete l;) in the following code

#include <iostream>
#include <memory>

using namespace std;

struct L {
L() { cout<<"L() at "<<this<<endl; }
~L() { cout<<"~L() at "<<this<<endl; }
};

class X {
public:
L* l;
X() try : l(new L) {
cout<<"X() at "<<this<<endl;
}
catch (...) { delete l; throw; }

This is not legal C++. If your compiler allows such syntax (as you
imply below), it is non-conformant.
~X(){
cout<<"~X() at "<<this<<endl;
delete l;
}
};

int main(){
try {
X* p = new X;
delete p;
}
catch(...) { std::cout<<"some exception occured"<<std::endl; }
}

i wonder why this has been declared such, because it leads directly
to leaking local ressources. i know herb sutters arguments from 'more
exc. c++' (item 18) where a local pointer could refer to a meanwhile
destoyed subobject (as i understand it). but this seems to be a very
rare situation.

moreover compiled with g++ (3.3.5) the output is

L() at 0x804a018
X() at 0x804a008
~X() at 0x804a008
~L() at 0x804a018

without the leaking L

But your code doesn't throw an exception. Try adding a second member to
X and throwing an exception in the second member's constructor:

class MyException {};
class M { M() { throw MyException(); } };
class L {};
class X
{
L* l_;
M* m_;
public:
X()
try
: l_( new L ),
m_( new M )
{}
catch( const MyException& e )
{
// Automatic rethrow of e here
}
};

Now l_ is leaked.
any idea ?

Perhaps this is the same material from Herb Sutter that you're
referring to, but he says (http://www.gotw.ca/gotw/066.htm):

"[O]nce you get into your constructor try-block's handler, any local
variables in the constructor body are also already out of scope, and
you are guaranteed that no base subobjects or member objects exist any
more, period. You can't even refer to their names. Either the parts of
your object were never constructed, or those that were constructed have
already been destroyed. So you can't be cleaning up anything that
relies on referring to a base or member of the class (and anyway,
that's what the base and member destructors are for, right?)."

In practice, that means you should use a smart pointer such as
std::auto_ptr instead of a raw pointer for all class members to prevent
leaking local resources.

The same link discusses the rationale for why C++ does things that way,
and I'll just commend it for your reading pleasure rather than recap it
here.

Cheers! --M
 
K

Klaus Ahrens

mlimber said:
Klaus said:
hi all,

acoording to the c++ standard

"15.3.
- -10- Referring to any non-static member or base class of an object in
the handler for a function-try-block of a constructor or destructor for
that object results in undefined behavior."

it is not allowed to refer to l (delete l;) in the following code

#include <iostream>
#include <memory>

using namespace std;

struct L {
L() { cout<<"L() at "<<this<<endl; }
~L() { cout<<"~L() at "<<this<<endl; }
};

class X {
public:
L* l;
X() try : l(new L) {
cout<<"X() at "<<this<<endl;
}
catch (...) { delete l; throw; }


This is not legal C++. If your compiler allows such syntax (as you
imply below), it is non-conformant.

~X(){
cout<<"~X() at "<<this<<endl;
delete l;
}
};

int main(){
try {
X* p = new X;
delete p;
}
catch(...) { std::cout<<"some exception occured"<<std::endl; }
}

i wonder why this has been declared such, because it leads directly
to leaking local ressources. i know herb sutters arguments from 'more
exc. c++' (item 18) where a local pointer could refer to a meanwhile
destoyed subobject (as i understand it). but this seems to be a very
rare situation.

moreover compiled with g++ (3.3.5) the output is

L() at 0x804a018
X() at 0x804a008
~X() at 0x804a008
~L() at 0x804a018

without the leaking L


But your code doesn't throw an exception. Try adding a second member to
X and throwing an exception in the second member's constructor:

class MyException {};
class M { M() { throw MyException(); } };
class L {};
class X
{
L* l_;
M* m_;
public:
X()
try
: l_( new L ),
m_( new M )
{}
catch( const MyException& e )
{
// Automatic rethrow of e here
}
};

Now l_ is leaked.

any idea ?


Perhaps this is the same material from Herb Sutter that you're
referring to, but he says (http://www.gotw.ca/gotw/066.htm):

"[O]nce you get into your constructor try-block's handler, any local
variables in the constructor body are also already out of scope, and
you are guaranteed that no base subobjects or member objects exist any
more, period. You can't even refer to their names. Either the parts of
your object were never constructed, or those that were constructed have
already been destroyed. So you can't be cleaning up anything that
relies on referring to a base or member of the class (and anyway,
that's what the base and member destructors are for, right?)."

In practice, that means you should use a smart pointer such as
std::auto_ptr instead of a raw pointer for all class members to prevent
leaking local resources.

The same link discusses the rationale for why C++ does things that way,
and I'll just commend it for your reading pleasure rather than recap it
here.

Cheers! --M
i agree with your arguments with multiple ressources, indeed i forgot to
throw, but even with an exception in my X-ctor g++ produces (obviously
wrong):

L() at 0x804b018
X() at 0x804b008
~L() at 0x804b018
some exception occured

in general c++ guarantees leak-free constructor failures by deleting
memory of half baked objects (arrays) implicitly. why couldn't this be
extended to single-ressource-owning classes at least (as in my class X)???


--
mfg
k ahrens
_____________________________________________
\ phone +49 30 2093 3113 \ :) \
\ fax +49 30 2093 3112 \__________________\
\ mailto:[email protected] \
\ http://www.informatik.hu-berlin.de/~ahrens \
\ ____________________________________________\
 
A

Alf P. Steinbach

* Klaus Ahrens:
hi all,

acoording to the c++ standard

"15.3.
- -10- Referring to any non-static member or base class of an object in
the handler for a function-try-block of a constructor or destructor for
that object results in undefined behavior."

it is not allowed to refer to l (delete l;) in the following code

#include <iostream>
#include <memory>

using namespace std;

struct L {
L() { cout<<"L() at "<<this<<endl; }
~L() { cout<<"~L() at "<<this<<endl; }
};

class X {
public:
L* l;
X() try : l(new L) {
cout<<"X() at "<<this<<endl;
}
catch (...) { delete l; throw; }

~X(){
cout<<"~X() at "<<this<<endl;
delete l;
}
};

int main(){
try {
X* p = new X;
delete p;
}
catch(...) { std::cout<<"some exception occured"<<std::endl; }
}

i wonder why this has been declared such, because it leads directly
to leaking local ressources.

No, the C++ rules don't.

The above program is invalid.

If you'd care to present a valid program and explain _why_ you think it
would leak, we can set you right (or perhaps it would leak, it's not
difficult to arrange for a program to leak memory).
 
M

mlimber

Klaus said:
mlimber said:
Klaus said:
hi all,

acoording to the c++ standard

"15.3.
- -10- Referring to any non-static member or base class of an object in
the handler for a function-try-block of a constructor or destructor for
that object results in undefined behavior."

it is not allowed to refer to l (delete l;) in the following code

#include <iostream>
#include <memory>

using namespace std;

struct L {
L() { cout<<"L() at "<<this<<endl; }
~L() { cout<<"~L() at "<<this<<endl; }
};

class X {
public:
L* l;
X() try : l(new L) {
cout<<"X() at "<<this<<endl;
}
catch (...) { delete l; throw; }


This is not legal C++. If your compiler allows such syntax (as you
imply below), it is non-conformant.

~X(){
cout<<"~X() at "<<this<<endl;
delete l;
}
};

int main(){
try {
X* p = new X;
delete p;
}
catch(...) { std::cout<<"some exception occured"<<std::endl; }
}

i wonder why this has been declared such, because it leads directly
to leaking local ressources. i know herb sutters arguments from 'more
exc. c++' (item 18) where a local pointer could refer to a meanwhile
destoyed subobject (as i understand it). but this seems to be a very
rare situation.

moreover compiled with g++ (3.3.5) the output is

L() at 0x804a018
X() at 0x804a008
~X() at 0x804a008
~L() at 0x804a018

without the leaking L


But your code doesn't throw an exception. Try adding a second member to
X and throwing an exception in the second member's constructor:

class MyException {};
class M { M() { throw MyException(); } };
class L {};
class X
{
L* l_;
M* m_;
public:
X()
try
: l_( new L ),
m_( new M )
{}
catch( const MyException& e )
{
// Automatic rethrow of e here
}
};

Now l_ is leaked.

any idea ?


Perhaps this is the same material from Herb Sutter that you're
referring to, but he says (http://www.gotw.ca/gotw/066.htm):

"[O]nce you get into your constructor try-block's handler, any local
variables in the constructor body are also already out of scope, and
you are guaranteed that no base subobjects or member objects exist any
more, period. You can't even refer to their names. Either the parts of
your object were never constructed, or those that were constructed have
already been destroyed. So you can't be cleaning up anything that
relies on referring to a base or member of the class (and anyway,
that's what the base and member destructors are for, right?)."

In practice, that means you should use a smart pointer such as
std::auto_ptr instead of a raw pointer for all class members to prevent
leaking local resources.

The same link discusses the rationale for why C++ does things that way,
and I'll just commend it for your reading pleasure rather than recap it
here.

Cheers! --M
i agree with your arguments with multiple ressources, indeed i forgot to
throw, but even with an exception in my X-ctor g++ produces (obviously
wrong):

L() at 0x804b018
X() at 0x804b008
~L() at 0x804b018
some exception occured

in general c++ guarantees leak-free constructor failures by deleting
memory of half baked objects (arrays) implicitly. why couldn't this be
extended to single-ressource-owning classes at least (as in my class X)???

All constructed members of a class *are* implicitly destroyed when the
constructor (or any function) throws an exception, and that inclues raw
pointers that happen to be members. However, the standard rules for
pointers is that if the pointer is non-null and it goes out of scope
without being deleted (or somehow passing ownership), the object it
pointed to is leaked. A simpler example:

void Foo()
{
L* rawPtr = new L;
std::auto_ptr<L> autoPtr( new L );
throw MyException();
}

In this code, rawPtr is leaked but autoPtr is not. The two key benefits
of smart pointers are that they free the programmer from having to
manually delete each new-ed object and that they provide exception
safety. Their use is not only good practice in the example with M and L
above; it is essential for a non-leaking program.

Cheers! --M
 
K

Klaus Ahrens

Alf said:
* Klaus Ahrens:



No, the C++ rules don't.

The above program is invalid.

i know this for sure, and i know that auto_ptrs are much better, but my
question is concerned with the rationale behind that rule !
 
A

Alf P. Steinbach

* Klaus Ahrens:
i know this for sure, and i know that auto_ptrs are much better, but my
question is concerned with the rationale behind that rule !

Presumably you mean the rule 15.3/10 that if you refer to a non-static
member variable in a constructor's function try-block catch clause,
you're invoking Undefined Behavior

The reason is very simple: when you enter the catch clause the member
has already been destroyed, it does not exist anymore, or alternatively
it wasn't fully constructed, and did not ever come into existence; see
15.3/11.

Essentially a constructor function try-block is only useful for
converting a non-standard exception originating in the constructor's
initialization list, to a standard exception. The purpose of C++
'catch' is _not_ to do cleanup. The purpose is to fulfill contracts,
such as a constract about only throwing standard exceptions.
 

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,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top