C++Talk.NET Forum Index C++Talk.NET
C++ language newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Exception safety with object being initialized

 
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ Language (Moderated)
View previous topic :: View next topic  
Author Message
Matthias Hofmann
Guest





PostPosted: Sun Aug 07, 2005 1:34 am    Post subject: Exception safety with object being initialized Reply with quote



Hello!

I haven't dealt much with the issue of exception safety yet, so I am quite a
newbie to this area. I do know what happens when a constructor throws an
exceptions, but I am not sure about initialization. Please consider the
following code:

#include <iostream>
#include <memory>

using std::cout;
using std::endl;

struct Object
{
Object( int i ) {}
};

int f() { throw 0; }

int main()
{
try
{
std::auto_ptr<Object> p( new Object( f() ) );
}
catch ( int i )
{
cout << i << endl;
}

return 0;
}

The initialization of 'Object' will cause an exception to be thrown. I would
expect my compiler to allocate memory for 'Object' after returning from
'f()', so the exception should be thrown before the memory is allocated.

However, I don't know if the compiler is allowed to allocate memory for
'Object' before entering 'f()' and throwing the exception, and in that case,
I wonder if that memory will be released again.

Can anyone please clarify this for me or point me to the relevant section of
the standard?

--
Matthias Hofmann
Anvil-Soft, CEO
http://www.anvil-soft.com - The Creators of Klomanager
http://www.anvil-soft.de - Die Macher des Klomanagers



[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
Maxim Yegorushkin
Guest





PostPosted: Sun Aug 07, 2005 10:45 am    Post subject: Re: Exception safety with object being initialized Reply with quote




Matthias Hofmann wrote:
Quote:
Hello!

I haven't dealt much with the issue of exception safety yet, so I am quite a
newbie to this area. I do know what happens when a constructor throws an
exceptions, but I am not sure about initialization. Please consider the
following code:

#include <iostream
#include
using std::cout;
using std::endl;

struct Object
{
Object( int i ) {}
};

int f() { throw 0; }

int main()
{
try
{
std::auto_ptr }
catch ( int i )
{
cout << i << endl;
}

return 0;
}

The initialization of 'Object' will cause an exception to be thrown. I would
expect my compiler to allocate memory for 'Object' after returning from
'f()', so the exception should be thrown before the memory is allocated.

However, I don't know if the compiler is allowed to allocate memory for
'Object' before entering 'f()' and throwing the exception, and in that case,
I wonder if that memory will be released again.

There are two steps involved with new statement. The first is
allocating memory using operator new (which is not the same thing as
new statement), the second is invoking a constructor. If the latter
fails by throwing an exception, operator delete will be called to free
the allocated memory.

Quote:
Can anyone please clarify this for me or point me to the relevant section of the standard?

You might like taking a look at a C++ FAQ:
http://www.parashift.com/c++-faq-lite/exceptions.html


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Back to top
Matthias Hofmann
Guest





PostPosted: Mon Aug 08, 2005 7:46 am    Post subject: Re: Exception safety with object being initialized Reply with quote



"Maxim Yegorushkin" <maxim.yegorushkin (AT) gmail (DOT) com> schrieb im Newsbeitrag
news:1123405998.634781.166230 (AT) z14g2000cwz (DOT) googlegroups.com...

Quote:
There are two steps involved with new statement. The first is
allocating memory using operator new (which is not the same thing as
new statement), the second is invoking a constructor. If the latter
fails by throwing an exception, operator delete will be called to free
the allocated memory.

Yes, but what if the exception is not thrown by the constructor, but by a
function whose return value is used to initialize the constructor? Let's
take a look at the relevant line of my code:

std::auto_ptr<Object> p( new Object( f() ) );

Is the compiler allowed to allocate memory before f() is entered, and if so,
will the memory be freed if f() throws an exception?

--
Matthias Hofmann
Anvil-Soft, CEO
http://www.anvil-soft.com - The Creators of Klomanager
http://www.anvil-soft.de - Die Macher des Klomanagers



[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Back to top
Rutger van Beusekom
Guest





PostPosted: Mon Aug 08, 2005 7:49 am    Post subject: Re: Exception safety with object being initialized Reply with quote


Matthias Hofmann wrote:
Quote:
Hello!

I haven't dealt much with the issue of exception safety yet, so I am quite a
newbie to this area. I do know what happens when a constructor throws an
exceptions, but I am not sure about initialization. Please consider the
following code:

#include <iostream
#include
using std::cout;
using std::endl;

struct Object
{
Object( int i ) {}
};

int f() { throw 0; }

int main()
{
try
{
std::auto_ptr }
catch ( int i )
{
cout << i << endl;
}

return 0;
}

The initialization of 'Object' will cause an exception to be thrown. I would
expect my compiler to allocate memory for 'Object' after returning from
'f()', so the exception should be thrown before the memory is allocated.

However, I don't know if the compiler is allowed to allocate memory for
'Object' before entering 'f()' and throwing the exception, and in that case,
I wonder if that memory will be released again.

Can anyone please clarify this for me or point me to the relevant section of
the standard?


The following example might clarify things for you. I have also added a
passage from the standard which applies here. As you can see, there is
nothing for you to worry about, but be mindful of other related
problems as demonstrated by the last test.

#include #include <memory>
#include <stdexcept>

#define __AT__ __FILE__ "(" STRINGIZE(__LINE__) "): "
#define STRINGIZE(line) STRINGIZE_(line)
#define STRINGIZE_(line) #line

#define TEST(function)
try{ std::clog << "n[" #function "]" << std::endl; function(); }
catch(const std::exception& e)
{ std::clog << __AT__ #function " failed with an exception:n"
<< e.what() << std::endl; }

int f()
{
throw std::logic_error(__AT__ "f() failed");
return 0;
}

struct B
{
int i_;
B()
: i_(f())
{}
B(int i)
: i_(i)
{}
static void* operator new(size_t size)
{
std::cout << "B::new" << std::endl;
return ::operator new(size);
}
static void operator delete(void* p, size_t)
{
std::cout << "B::delete" << std::endl;
::operator delete(p);
}
};

struct C
{
static void* operator new(size_t size)
{
std::cout << "C::new" << std::endl;
return ::operator new(size);
}
static void operator delete(void* p, size_t)
{
std::cout << "C::delete" << std::endl;
::operator delete(p);
}
};

struct D
{
B* b_;
C* c_;
D(B* b, C* c)
{}
~D()
{
delete b_;
delete c_;
}
static void* operator new(size_t size)
{
std::cout << "D::new" << std::endl;
return ::operator new(size);
}
static void operator delete(void* p, size_t)
{
std::cout << "D::delete" << std::endl;
::operator delete(p);
}
};

template class raii_ptr
{
T* t_;
raii_ptr(const raii_ptr&);
raii_ptr& operator = (const raii_ptr&);
public:
raii_ptr(T* t)
: t_(t)
{
std::cout << "raii_ptr::ctor" << std::endl;
}
~raii_ptr()
{
std::cout << "raii_ptr::dtor" << std::endl;
delete t_;
}
};

void test1()
{
raii_ptr }

void test2()
{
raii_ptr<B> pb(new B(f()));
}

void test3()
{
raii_ptr<D> pb(new D(new B, new C));
}

int main()
{
TEST(test1);
TEST(test2);
TEST(test3);

return 0;
}

/*
output:

[test1]
B::new
B::delete
throw.cpp(116): test1 failed with an exception:
throw.cpp(1Cool: f() failed

[test2]
B::new
B::delete
throw.cpp(117): test2 failed with an exception:
throw.cpp(1Cool: f() failed

[test3]
D::new
C::new <== leakage
B::new
B::delete
D::delete
throw.cpp(118): test3 failed with an exception:
throw.cpp(1Cool: f() failed


ISO/IEC 14882:2003(E)
5.3.4 New
page 82

If any part of the object initialization described above*71) terminates
by
throwing an exception and a suitable deallocation function can be
found, the
deallocation function is called to free the memory in which the object
was
being constructed, after which the exception continues to propagate in
the
context of the new-expression. If no unambiguous matching deallocation
function can be found, propagating the exception does not cause the
object's
memory to be freed. [Note: This is appropriate when the called
allocation
function does not allocate memory; otherwise, it is likely to result in
a
memory leak. ]

*71) This may include evaluating a new-initializer and/or calling a
constructor.
*/

Rutger van Beusekom


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Back to top
Maxim Yegorushkin
Guest





PostPosted: Mon Aug 08, 2005 10:26 am    Post subject: Re: Exception safety with object being initialized Reply with quote


Matthias Hofmann wrote:
Quote:
"Maxim Yegorushkin" <maxim.yegorushkin (AT) gmail (DOT) com> schrieb im Newsbeitrag
news:1123405998.634781.166230 (AT) z14g2000cwz (DOT) googlegroups.com...

There are two steps involved with new statement. The first is
allocating memory using operator new (which is not the same thing as
new statement), the second is invoking a constructor. If the latter
fails by throwing an exception, operator delete will be called to free
the allocated memory.

Yes, but what if the exception is not thrown by the constructor, but by a
function whose return value is used to initialize the constructor? Let's
take a look at the relevant line of my code:

std::auto_ptr<Object> p( new Object( f() ) );

Is the compiler allowed to allocate memory before f() is entered,

It's allowed.

Quote:
and if so, will the memory be freed if f() throws an exception?

f() is an initializer expression for Object. As Rutger van Beusekom
quoted:

If *any part of the object initialization* described above terminates
by
throwing an exception...

So, no leaks occur.


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Back to top
Matthias Hofmann
Guest





PostPosted: Mon Aug 08, 2005 3:12 pm    Post subject: Re: Exception safety with object being initialized Reply with quote

----- Original Message -----
From: "Rutger van Beusekom" <Rutger.van.Beusekom (AT) gmail (DOT) com>
Newsgroups: comp.lang.c++.moderated
To: <Usenet>
Sent: Monday, August 08, 2005 9:49 AM
Subject: Re: Exception safety with object being initialized


Quote:
The following example might clarify things for you. I have also added a
passage from the standard which applies here. As you can see, there is
nothing for you to worry about, but be mindful of other related
problems as demonstrated by the last test.

[skipping example]

Thank you very much for your explanation, it helped me quite a lot. It also
helped me to brush up my knowledge about allocation and deallocation
functions, which is a very confusing matter, especially because the related
information is scattered all over the standard...

There is still one thing that puzzles me. Let's take a look at a class from
your code:

struct B
{
// Other members ommited for clarity.

static void* operator new( size_t size )
{
std::cout << "B::new" << std::endl;
return ::operator new( size );
}

static void operator delete( void* p, size_t )
{
std::cout << "B::delete" << std::endl;
::operator delete( p );
}
};

When deleting an object of type B, B::operator delete( void*, size_t ) will
be called. The standard guarantees that in this case, the second argument
will be the size of the block of memory to be deallocated. However, I found
no such guarantee for the case when B::operator delete( void*, size_t ) is
called by a new-expression - is that an oversight? What value will be passed
for the second argument of B::operator delete( void*, size_t ) if an
exception is thrown while creating a new B?

Matthias Hofmann
Anvil-Soft, CEO
http://www.anvil-soft.com - The Creators of Klomanager
http://www.anvil-soft.de - Die Macher des Klomanagers



[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Back to top
Rutger van Beusekom
Guest





PostPosted: Mon Aug 08, 2005 7:53 pm    Post subject: Re: Exception safety with object being initialized Reply with quote

Quote:
There is still one thing that puzzles me. Let's take a look at a class from
your code:

struct B
{
// Other members ommited for clarity.

static void* operator new( size_t size )
{
std::cout << "B::new" << std::endl;
return ::operator new( size );
}

static void operator delete( void* p, size_t )
{
std::cout << "B::delete" << std::endl;
::operator delete( p );
}
};

When deleting an object of type B, B::operator delete( void*, size_t ) will
be called. The standard guarantees that in this case, the second argument
will be the size of the block of memory to be deallocated. However, I found
no such guarantee for the case when B::operator delete( void*, size_t ) is
called by a new-expression - is that an oversight? What value will be passed
for the second argument of B::operator delete( void*, size_t ) if an
exception is thrown while creating a new B?

Hi Matthias

The previously quoted passage of the standard mentions:
.... and a suitable deallocation function can be found, the deallocation
function is called to free the memory in which the object was being
constructed ...

and at

12.5 Free Store
page 198
item 5

.... the size of the block as its second argument.*105)

*105) If the static type in the delete-expression is different from the
dynamic type and the destructor is not virtual the size might be
incorrect, but that case is already undefined; see 5.3.5


Therefore, when delete is called (either explicitly by hand or
implicitly by new when an exception is thrown) and an appropriate
deallocation function is found, the size of the static type or dynamic
type (in case of a virtual dtor) is passed as the second argument.


Quad erat demonstrandum:

//compile with -DVIRTUAL='' or -DVIRTUAL=virtual


#include #include <iostream>
#include <stdexcept>

struct B
{
int i_;
B(){std::cout << "B::ctor" << std::endl;}
B(int i): i_(i) {throw std::logic_error("B::ctor failed");}
VIRTUAL ~B(){std::cout << "B::dtor" << std::endl;}
virtual void force_vtable(){}
static void* operator new(size_t size)
{
std::cout << "B::operator new(" << size << ")" << std::endl;
return ::operator new(size);
}
static void operator delete(void* p, size_t size)
{
std::cout << "B::operator delete(" << size << ")" << std::endl;
return ::operator delete(p);
}
};

struct C: public B
{
int j_;
C(){std::cout << "C::ctor" << std::endl;}
C(int j): B(j), j_(j) {}
~C(){std::cout << "C::dtor" << std::endl;}
};

#define TEST(expr)
std::cout << "[ " #expr " ]n";
try{ expr; } catch(const std::exception& e)
{std::clog << "exception: " << e.what() << std::endl;}
std::cout << std::endl;

int main()
{
TEST(new B(0));
TEST(new C(0));

TEST(B* pb = new B; delete pb);
TEST(B* pc = new C; delete pc);

return 0;
}


/*
output:

[ new B(0) ]
B::operator new(Cool
B::operator delete(Cool
exception: B::ctor failed

[ new C(0) ]
B::operator new(12)
B::operator delete(12)
exception: B::ctor failed

[ B* pb = new B; delete pb ]
B::operator new(Cool
B::ctor
B::dtor
B::operator delete(8)

[ B* pc = new C; delete pc ]
B::operator new(12)
B::ctor
C::ctor
C::dtor <== missing when compiled with -DVIRTUAL=''
B::dtor
B::operator delete(12) <== when compiled with -DVIRTUAL=virtual
B::operator delete(Cool <== when compiled with -DVIRTUAL=''

*/

Rutger


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Back to top
Matthias Hofmann
Guest





PostPosted: Tue Aug 09, 2005 1:53 pm    Post subject: Re: Exception safety with object being initialized Reply with quote

"Rutger van Beusekom" <Rutger.van.Beusekom (AT) gmail (DOT) com> schrieb im Newsbeitrag
news:1123526187.213471.103860 (AT) o13g2000cwo (DOT) googlegroups.com...

Quote:
... the size of the block as its second argument.*105)

*105) If the static type in the delete-expression is different from the
dynamic type and the destructor is not virtual the size might be
incorrect, but that case is already undefined; see 5.3.5

You should have quoted paragraph 5 in it entirety:

"When a delete-expression is executed, the selected deallocation function
shall be called with the address of the block of storage to be reclaimed as
its first argument and (if the two-parameter style is used) the size of the
block as its second argument."

Note that it says: "When a delete-expression is executed...", it does not
say anything about the case when a new-expression calls a deallocation
function.

Quote:
Therefore, when delete is called (either explicitly by hand or
implicitly by new when an exception is thrown) and an appropriate
deallocation function is found, the size of the static type or dynamic
type (in case of a virtual dtor) is passed as the second argument.

You cannot call delete because it is an operator. There is a delete
expression which calls a deallocation function. A deallocation function is
also called when a new-expression throws an exception. However, the called
deallocation functions are not the necessariliy the same, and the guarantee
about passing the size of the block as its second argument is only given for
the case of the delete-expression.

Quote:
Quad erat demonstrandum:

You code only proves the behaviour of *your* compiler.

There are a couple of things about deallocation functions in the standard
that seem unclear to me. For example, 3.7.3.2 says in paragraph 2 that the
following two dealocation functions are non-placement forms when used as
class members:

void operator delete( void* );
void operator delete( void*, std::size_t )

On the other hand, 5.3.4 says in pragraph 19 that the following placement
forms of an allocation and a deallocation function match:

void* operator new( std::size_t, std::size_t );
void operator delete( void*, std::size_t );

You may notice that the latter deallocation function is used as a placement
deallocation function, although it is defined to be a non-placement
deallocation function in 3.7.3.2/2. Strange, isn't it?

Another thing I find rather curious is that the standard does not define
which deallocation function a delete-expression will call when a class has
several of them. 12.5/4 only says that the compiler will look for a function
called "operator delete" and that the program is ill-formed "if the result
of the lookup is ambigous or inaccessible, or if the lookup selects a
placement deallocation function". But what makes such a lookup ambigous and
when does it select a placement function?

--
Matthias Hofmann
Anvil-Soft, CEO
http://www.anvil-soft.com - The Creators of Klomanager
http://www.anvil-soft.de - Die Macher des Klomanagers



[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]


Back to top
Display posts from previous:   
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ Language (Moderated) All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2006 phpBB Group
SEO toolkit © 2004-2006 webmedic.