[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.c++

exception handling problem

asm23

9/25/2008 10:52:00 AM

Hi, everyone, I'm learning <<thinking c++>> volume Two, and testing the
code below.

/////////////////////////////////////////////////////////////////////////////
//: C01:Wrapped.cpp
#include <iostream>
#include <cstddef>
using namespace std;


template<class T, int sz = 1> class PWrap {
T* ptr;
public:
class RangeError {}; // Exception class
PWrap() {
ptr = new T[sz];
cout << "PWrap constructor" << endl;
}
~PWrap() {
delete[] ptr;
cout << "PWrap destructor" << endl;
}
T& operator[](int i) throw(RangeError) {
if(i >= 0 && i < sz) return ptr[i];
throw RangeError();
}
};

class Cat {
public:
Cat() { cout << "Cat()" << endl; }
~Cat() { cout << "~Cat()" << endl; }
void g() {}
};

class Dog {
public:
void* operator new[](size_t) {
cout << "Allocating a Dog" << endl;
//throw 47; //*******************************NOTE
return 0;
}
void operator delete[](void* p) {
cout << "Deallocating a Dog" << endl;
::delete[](p);
}
};

class UseResources {
PWrap<Cat, 3> cats;
PWrap<Dog> dog;
public:
UseResources() {
cout << "UseResources()" << endl;
}
~UseResources() {
cout << "~UseResources()" << endl;
}
void f() { cats[1].g(); }
};

int main() {
try {
UseResources ur;
} catch(int) {
cout << "inside handler" << endl;
} catch(...) {
cout << "inside catch(...)" << endl;
}
}
/////////////////////////////////////////////////////////////////////////////////
In the UseResources class, It will create *Three cats* and *One dog*,
When I comment the "throw 47" line in the class Dog implementation.
it has the result output:
=================================
Cat()
Cat()
Cat()
PWrap constructor
Allocating a Dog
PWrap constructor
UseResources()
~UseResources()
Deallocating a Dog
PWrap destructor
~Cat()
~Cat()
~Cat()
PWrap destructor
==================================

But When I uncomment the "throw 47" line, the result will be:

=================================
Cat()
Cat()
Cat()
PWrap constructor
Allocating a Dog
~Cat()
~Cat()
~Cat()
PWrap destructor
inside handler
==================================

It seems that an exception happened when we are create the Dog object.

My Question:
When an exception occurred in the allocate a Dog object,
Why was *~UseResources()* not called?
Why was *~Cat()* called?

How does the "exception mechanism" guarantee that the allocated object
will be cleared.

Someone can give this a brief trick? Is it about call stack unwinding?
Thank you very much.



6 Answers

Paavo Helde

9/25/2008 5:50:00 PM

0

asm23 <asmwarrior@gmail.com> kirjutas:

> Hi, everyone, I'm learning <<thinking c++>> volume Two, and testing
> the code below.
>
> ///////////////////////////////////////////////////////////////////////
> ////// //: C01:Wrapped.cpp
> #include <iostream>
> #include <cstddef>
> using namespace std;
>
>
> template<class T, int sz = 1> class PWrap {
> T* ptr;
> public:
> class RangeError {}; // Exception class
> PWrap() {
> ptr = new T[sz];
> cout << "PWrap constructor" << endl;
> }
> ~PWrap() {
> delete[] ptr;
> cout << "PWrap destructor" << endl;
> }
> T& operator[](int i) throw(RangeError) {
> if(i >= 0 && i < sz) return ptr[i];
> throw RangeError();
> }
> };
>
> class Cat {
> public:
> Cat() { cout << "Cat()" << endl; }
> ~Cat() { cout << "~Cat()" << endl; }
> void g() {}
> };
>
> class Dog {
> public:
> void* operator new[](size_t) {
> cout << "Allocating a Dog" << endl;
> //throw 47; //*******************************NOTE
> return 0;
> }
> void operator delete[](void* p) {
> cout << "Deallocating a Dog" << endl;
> ::delete[](p);
> }
> };
>
> class UseResources {
> PWrap<Cat, 3> cats;
> PWrap<Dog> dog;
> public:
> UseResources() {
> cout << "UseResources()" << endl;
> }
> ~UseResources() {
> cout << "~UseResources()" << endl;
> }
> void f() { cats[1].g(); }
> };
>
> int main() {
> try {
> UseResources ur;
> } catch(int) {
> cout << "inside handler" << endl;
> } catch(...) {
> cout << "inside catch(...)" << endl;
> }
> }
> ///////////////////////////////////////////////////////////////////////
> ////////// In the UseResources class, It will create *Three cats* and
> *One dog*, When I comment the "throw 47" line in the class Dog
> implementation. it has the result output:
> =================================
> Cat()
> Cat()
> Cat()
> PWrap constructor
> Allocating a Dog
> PWrap constructor
> UseResources()
> ~UseResources()
> Deallocating a Dog
> PWrap destructor
> ~Cat()
> ~Cat()
> ~Cat()
> PWrap destructor
> ==================================
>
> But When I uncomment the "throw 47" line, the result will be:
>
> =================================
> Cat()
> Cat()
> Cat()
> PWrap constructor
> Allocating a Dog
> ~Cat()
> ~Cat()
> ~Cat()
> PWrap destructor
> inside handler
> ==================================
>
> It seems that an exception happened when we are create the Dog object.
>
> My Question:
> When an exception occurred in the allocate a Dog object,
> Why was *~UseResources()* not called?
> Why was *~Cat()* called?

In general, everything which has been constructed will be destructed.
Something is considered constructed when its constructor completes. The
Cats and their PWrap constructors were completed, so they were destructed
as well. The Dog, its PWrap and UseResources constructors were not
completed, so their destructors were not called.

Note that because each your class wraps at most only one potentially
failing resource allocation (a good design!), everything gets cleaned up
automagically, no memory leaks etc.

> How does the "exception mechanism" guarantee that the allocated object
> will be cleared.

It's the task for compiler writers, do not worry about this.

>
> Someone can give this a brief trick? Is it about call stack unwinding?
> Thank you very much.

Stack unwinding is about different objects in a stack frame, but here the
issue is about subobjects of a complex object, which is slightly
different.

I would say this is more of the class invariants. The destructor is
entitled to expect the object in a good shape, i.e. class invariant
holding. If the constructor did not complete, there is no guarantee that
the class invariant holds, thus the destructor cannot be called.

hth
Paavo



asm23

9/26/2008 2:38:00 AM

0

Paavo Helde wrote:

> In general, everything which has been constructed will be destructed.
> Something is considered constructed when its constructor completes. The
> Cats and their PWrap constructors were completed, so they were destructed
> as well. The Dog, its PWrap and UseResources constructors were not
> completed, so their destructors were not called.
>
> Note that because each your class wraps at most only one potentially
> failing resource allocation (a good design!), everything gets cleaned up
> automagically, no memory leaks etc.
>
>> How does the "exception mechanism" guarantee that the allocated object
>> will be cleared.
>
> It's the task for compiler writers, do not worry about this.
>
>> Someone can give this a brief trick? Is it about call stack unwinding?
>> Thank you very much.
>
> Stack unwinding is about different objects in a stack frame, but here the
> issue is about subobjects of a complex object, which is slightly
> different.
>
> I would say this is more of the class invariants. The destructor is
> entitled to expect the object in a good shape, i.e. class invariant
> holding. If the constructor did not complete, there is no guarantee that
> the class invariant holds, thus the destructor cannot be called.
>
> hth
> Paavo
>
>

Hi Paavo, Thanks for helping me. The book also suggest that we could use
a wrap class to avoid memory leak in class constructors. But I know,
there are some tricks about the object creating. for example, once an
constructor was executed, the state will be *remembered*, and then if
exception happen after that, there objects in the try block frame will
be recognized and their associated destructor will be called.

I'm not quite understand about the term *class invariants* and *class
invariant holding*. Does this mean the wrap class in my code?

Thanks.

Paavo Helde

9/27/2008 8:19:00 AM

0

asm23 <asmwarrior@gmail.com> kirjutas:

> Paavo Helde wrote:
>
>> In general, everything which has been constructed will be destructed.
>> Something is considered constructed when its constructor completes.
>> The Cats and their PWrap constructors were completed, so they were
>> destructed as well. The Dog, its PWrap and UseResources constructors
>> were not completed, so their destructors were not called.
>>
>> Note that because each your class wraps at most only one potentially
>> failing resource allocation (a good design!), everything gets cleaned
>> up automagically, no memory leaks etc.
>>
>>> How does the "exception mechanism" guarantee that the allocated
>>> object will be cleared.
>>
>> It's the task for compiler writers, do not worry about this.
>>
>>> Someone can give this a brief trick? Is it about call stack
>>> unwinding? Thank you very much.
>>
>> Stack unwinding is about different objects in a stack frame, but here
>> the issue is about subobjects of a complex object, which is slightly
>> different.
>>
>> I would say this is more of the class invariants. The destructor is
>> entitled to expect the object in a good shape, i.e. class invariant
>> holding. If the constructor did not complete, there is no guarantee
>> that the class invariant holds, thus the destructor cannot be called.
>>
>> hth
>> Paavo
>>
>>
>
> Hi Paavo, Thanks for helping me. The book also suggest that we could
> use a wrap class to avoid memory leak in class constructors. But I
> know, there are some tricks about the object creating. for example,
> once an constructor was executed, the state will be *remembered*, and
> then if exception happen after that, there objects in the try block
> frame will be recognized and their associated destructor will be
> called.

IMO, a good design is such which is exception-safe without any try-catch
blocks. C++ has built-in means (automatic call of destructors) to achieve
this. The only thing which has to be followed is that a single
constructor may not allocate more than 1 raw resource. For example, if
there are two malloc calls in the contructor, then the first may succeed
and the second fail; as the constructor does not complete, the destructor
is not executed and the first resource leaks.

To avoid this, the individual mallocs have to packaged in separate
classes, either a home-made wrapper like in your case, or preferrably in
an existing library class, like std::vector.

See also: http://en.wikipedia.org...

>
> I'm not quite understand about the term *class invariants* and *class
> invariant holding*. Does this mean the wrap class in my code?

http://en.wikipedia.org/wiki/Class...

regards
Paavo

asm23

9/27/2008 12:20:00 PM

0

Paavo Helde wrote:
> IMO, a good design is such which is exception-safe without any try-catch
> blocks. C++ has built-in means (automatic call of destructors) to achieve
> this. The only thing which has to be followed is that a single
> constructor may not allocate more than 1 raw resource. For example, if
> there are two malloc calls in the contructor, then the first may succeed
> and the second fail; as the constructor does not complete, the destructor
> is not executed and the first resource leaks.
>
> To avoid this, the individual mallocs have to packaged in separate
> classes, either a home-made wrapper like in your case, or preferrably in
> an existing library class, like std::vector.
>
> See also: http://en.wikipedia.org...
>
>> I'm not quite understand about the term *class invariants* and *class
>> invariant holding*. Does this mean the wrap class in my code?
>
> http://en.wikipedia.org/wiki/Class...
>
> regards
> Paavo
>

Thanks Paavo for the second help.

After carefully reading the RAII article on wikipedia, I gain a lot of
knowledge about RAII. I'm feeling exciting. The exception-safe code is
wonderful.

The main *Idea* I know is "a scoped object is only destroyed if fully
constructed. for example:
foo(){
CMyClass o1;
CMyClass o2;
CMyClass o3;
}
if an exception happened in o2's constructor, since o3 is not
constructed, o3's destructor will never be called in exception handling.


Now, I understand what the term *class invariant* means.
http://www.stanford.edu/~pgbovine/programming-with-rep-inva...

Thanks for helping me, My question is totally solved.

Paavo Helde

9/27/2008 6:59:00 PM

0

asm23 <asmwarrior@gmail.com> kirjutas:
>
> After carefully reading the RAII article on wikipedia, I gain a lot of
> knowledge about RAII. I'm feeling exciting. The exception-safe code is
> wonderful.

Nice to hear! Coincidentally, this is what Bjarne Stroustrup says about
himself in http://www.cilk.com/multicore-blog/bid/6703/C-Invent...
Stroustrup-answers-the-Multicore-Proust-Questionnaire

The contribution for which I most want to be remembered:
The C++ destructor and the programming techniques that rely on it.

asm23

9/28/2008 12:09:00 AM

0

Paavo Helde wrote:
> The contribution for which I most want to be remembered:
> The C++ destructor and the programming techniques that rely on it.
>
Yes, Thanks. I read the page.
I think RAII is a big conception we should understand. Through I start
to learned C++ about 4 years ago, I still lack some "big idea and
conception" on this language. I'm writing code like "class and it's
functions" day by day.

Now I think there is more I should to learn in C++. Thanks for bring me
on the right way.