Good morning.
The two last posts: "David Harmon 28.11.2006 10:05 " and "BobR
29.11.2006 05:17 " is really very interesting to me. I will consider
them for nearest time.
And if it is interesting to anybody, i can explain my doubts more:
David Harmon wrote:
"If the constructor throws, the object is not completely constructed."
It is true.
"The destructor has no way to know that."
But it is not true. Programmer, of course, can not control a
construction sequence, but the compiler can know, what the "imagined"
part of constructor failed. Let's consider the next class:
struct A
{
int* a; //need array of int[1024]
int b;
double c;
A()throw(exceprion&); //must allocate memory with new[]
~A()throw(); //must free memory
};
This class has only members of base C++ types. They could not throw
exception while creating own memory or data
. They have own
predefined destructors (~int etc), which completele restore system to
state befor the members created and compiler can know about their
destructors and _can calls them if the members were created_.
Lets write simple destructor of the class A:
~A::A()throw(){delete[] a; a=0;}
The destructor of the class A has the one serious trouble - it can not
calls "delete[]" of member "a" befor operator new will return a correct
memory pointer. The data, stored in "a", is indefinite befor new will
return.
If compiler calls the dtor of the class A befor new will return a
correct memory pointer, the program will crash and make at least
"general protection fail".
Selecting way of creation ctor of the class A, we can do like this
A::A()throw(exceprion&):a(0){/*some code*/}
The trick ":a(0)" makes dtor callings safe nearly independent from ctor
code in {}.
It is enough to guess, that compiler _can not_ call dtor of A, if list
of initializers (ctor part one) failed, and _can_ call dtor of A if all
members have been complete created successfully.
There is another important argument to the upper described compiler
behavior: all the class A members of predefined types has own ctor/dtor
pairs, i.e. all the class A members are true classes with full
incapsulated behaviour (they no need external control).
Compiler can manage creating/destroying of the members by "calling"
their trivial ctor/dtor. And executed dtor of any member of the class A
completely restore the piece of system state, which has been changed by
creating the member.
And the ctor "code in {}" of the class A must be managed by compiler in
the same manner. In order to restore all canges, have been created or
partial (not complete) created by ctor "code in {}" execution, compiler
_must_ call its dtor pair, else programmer will have to call dtor by
hand.
I do not see any sence to call destructor code from constructor myself,
for it is can be easy done by compiler.
To do it compiler can create ctor as two "imagined" part: "member_ctor"
and "user_code_ctor". The "member_ctor" code is hidden to programmer,
the correct ctor of any member of the class A is controlled by
programmer with the help of list of initislizers. The goal of the
"member_ctor" - make calls dtor of its class safe. The goal of
"user_code_ctor" is control of its class memory. And dtor parted the
same.
Using the pattern we will have something like this:
{
A obj;
//A::compiled_ctor
// A::member_ctor(); if(exception)goto member_ex
// A::user_code_ctor(); if(exception)goto user_ex
....
//A::compiled_dtor
//user_ex:
// ~A::user_code_dtor();
//member_ex:
// ~A::member_dtor();
}
It is evidently, compiler must _not_ call "user_code_dtor()" if
"user_code_ctor()" is even not started and must call "user_code_ctor()"
if any part of "user_code_dtor()" executed in order to restore system
state.
In my first example any member of the class A has trivial ctor/dtor and
can not throw any exception. Let's consider second example:
struct B
{
A a;
char* b; //need array of char[256]
double c;
B()throw(exceprion&); //must allocate memory with new[]
~B()throw(); //must free memory
};
The class A already can throw exception, but the pattern of its
ctor/dtor creating allows to compiler to use class A as true class
(with its own hidden ctor/dtor behaviour), calling
"A::compiled_ctor"/"A::compiled_dtor" in
"B::member_ctor()"/"B::member_dtor()". The pattern of ctor/dtor allows
to compiler restore all changes maked by partial or complete created
the member A of any class B.
One can see: any class has either members of only predefined types with
their trivial ctor/dtor and can use the ctor/dtor pattern (similar to
the class A), or members like the class B (similar to the class B). For
both cases the ctor/dtor pattern can be used, so destructor code can be
always called by compiler.
I can understand, that my explanaition is quite week and can not be
trusted "in general case".
In order to expand the explanaition to "general case" a man must be an
expert of C++ real programming and know many practical cases, to find
exceptions from my explanation. I do not see any exceptions
Can you object me?