Effective C++ by Scott Meyers

P

Peter

I right now reading this book.
And he is iterating some points I'm following since 1996, e.g.
exception safety.
But e.g. he is missing one of the major exception safety guidelines,
which is

Allocate only a single resource inside a constructor body (deallocate
in the matching destructor body).
Chain such classes into base-class/member-class relationships.
This way you are able to exploit the code generation features of the
compiler,
since you wont have to deal with partially constructed objects and
cleanup after an exception has been thrown.

I was thinking that this rule is already in broad usage and should be
part of any modern book on C++.
 
J

John Harrison

Peter said:
I right now reading this book.
And he is iterating some points I'm following since 1996, e.g.
exception safety.
But e.g. he is missing one of the major exception safety guidelines,
which is

Allocate only a single resource inside a constructor body (deallocate
in the matching destructor body).
Chain such classes into base-class/member-class relationships.
This way you are able to exploit the code generation features of the
compiler,
since you wont have to deal with partially constructed objects and
cleanup after an exception has been thrown.

I was thinking that this rule is already in broad usage and should be
part of any modern book on C++.

Effective C++ is not that modern a book is it, 10 years old according to
Amazon.

Your criticism is valid I guess, but Effective C++ is a beginners book,
and exception safety is an advanced topic.

It remains an exceptionally good book, taught me more about C++ than any
other single book I've read.

john
 
P

Peter

Peter said:
I was thinking that this rule is already in broad usage and should be
part of any modern book on C++.


and on page 138 he made a mistake in the order of constructor calls.
The correct order is:

bm1::constructor()
bm2::constructor()
Base::Base()
dm1::constructor()
dm2::constructor()
dm3::constructor()
Derived::Derived()

I left out the order of destructor calls in case of an exception.
But this is simple, as it is in reverse order.

This mistake did more damage than the book did good.
 
S

s5n

I right now reading this book.
And he is iterating some points I'm following since 1996, e.g.
exception safety.
But e.g. he is missing one of the major exception safety guidelines,
which is ....
I was thinking that this rule is already in broad usage and should be
part of any modern book on C++.

Your points are valid, but don't forget how old that book is ("modern"
is not valid here, IMO). After writing that book Meyers released
Effective STL and is on the 3rd edition of More Effective C++. Not
*everything* could go into one book. :)

PS: i'm Scott Meyers' biggest fan. :)
 
S

Salt_Peter

and on page 138 he made a mistake in the order of constructor calls.
The correct order is:

bm1::constructor()
bm2::constructor()
Base::Base()
dm1::constructor()
dm2::constructor()
dm3::constructor()
Derived::Derived()

I left out the order of destructor calls in case of an exception.
But this is simple, as it is in reverse order.

This mistake did more damage than the book did good.


It is not in reverse order. In the sequence above Derived() is invoked
first which invokes the other constructors. The only reason
Derived::Derived() is displayed last is because that ctor body is only
executed once the member constructions are completed.
The same applies to Base counterpart. bm1 and bm2 are constructed to
completion before the Base::Base() output is executed. The fact that
bm1 and bm2 constructors were invoked implies that the Base ctor
itself was invoked first.
 
P

Peter

Salt_Peter said:
It is not in reverse order. In the sequence above Derived() is invoked


the destruction is in reverse order of the constructors.
Only these destructors are called, for which the constructor was successful.
This is also true in case of the destruction is caused by an exception
thrown.
Do you claim that it is not so?

first which invokes the other constructors. The only reason
Derived::Derived() is displayed last is because that ctor body is only
executed once the member constructions are completed.
The same applies to Base counterpart. bm1 and bm2 are constructed to
completion before the Base::Base() output is executed. The fact that
bm1 and bm2 constructors were invoked implies that the Base ctor
itself was invoked first.


I'm not following.
Do you claim yet another construction order?
Do be explicit -- could you write down your construction order line by line?
But this does not matter -- I'm quite certain that I'm correct here.
I tested this in countless examples.
 
S

sks

Peter said:
I right now reading this book.
And he is iterating some points I'm following since 1996, e.g.
exception safety.
But e.g. he is missing one of the major exception safety guidelines,
which is

Allocate only a single resource inside a constructor body (deallocate
in the matching destructor body).
Chain such classes into base-class/member-class relationships.
This way you are able to exploit the code generation features of the
compiler,
since you wont have to deal with partially constructed objects and
cleanup after an exception has been thrown.

I was thinking that this rule is already in broad usage and should be
part of any modern book on C++.

I didn't quite understand this but I would like to understand it. What did
you mean by "exploit the code generation features of the
compiler"?

Also, what is wrong with multiple resource allocations inside a constructor
body (as long as the matching destructor gets to deallocate them)?

Thanks.
 
A

Alf P. Steinbach

* Peter:
the destruction is in reverse order of the constructors.
Only these destructors are called, for which the constructor was
successful.
This is also true in case of the destruction is caused by an exception
thrown.
Do you claim that it is not so?

Peter is talking about constructor calls, you're talking about finished
initializations (constructor body executions).

Those are different things.

For example, if Base and Derived are coded like


void say( char const s[] ) { std::cout << s << std::endl; }

int theAnswer( char const s[] ) { say( s ); return 42; }

struct Base
{
Base( int ) { say( "Base constructor" ); }
};

struct Derived
{
Derived(): Base( theAnswer( "Derived init list" ) )
{ say( "Derived constructor" ); }
};

int main() { Derived(); }

But this does not matter -- I'm quite certain that I'm correct here.
I tested this in countless examples.

Try the above.
 
P

Peter

sks said:
I didn't quite understand this but I would like to understand it. What did
you mean by "exploit the code generation features of the
compiler"?


If you would have to perform an initialization of a complex object in C
you would have to perform checks if the initialization of a subobject was
successful.
In case of one initialization of some subobject fails you would have to call
the destructor of the already successful initialized subobjects.
You would have to maintain this order in the constructor and in the
destructor. Lets assume 3 parts A, B and C:
(0 return code stands for success, everything else for failure)

typedef struct
{
A sA;
B sB;
C sC;
} someStructure;

int initSomeStructure(someStructure *_p)
{ if (initA(&_p->sA))
return 1;
if (initB(&_p->sB))
{ destroyA(&_p->sA);
return 1;
}
if (initC(&_p->sC))
{ destroyB(&_p->sB);
destroyA(&_p->sA);
return 1;
}
return 0;
}


void destroySomeStructure(someStructure *_p)
{ destroyC(&_p->sC);
destroyB(&_p->sB);
destroyA(&_p->sA);
}


Usage of this code could look like this:

someStructure *p = malloc(sizeof*p);
if (!p)
{ printf("allocation of somestructure failed\n");
}
else
if (initSomeStructure(p))
{ free(p);
printf("Initialization of somestructure failed\n");

}
else
doSomething(p);

ok so far?

in C++ this would look like this:

struct SomeStructure
{ A m_sA;
B m_sB;
C m_sC;
SomeStructure(void)
{
}
};


Usage of this code would look like this:

SomeStructure *p;
try
{ p = new SomeStructure;
} catch (const std::exception &_r)
{ cout << "Initialization or allocation of somestructure failed with
msg: " << _r.why() << "\n";
}
p->doSomething();


The C++ compiler is generating all this code for you (initializaing the
subobjects and dealing with failure if initialization of some subobject
fails).
I only used a heap-allocated object to show that the compiler generates a
call to delete if the constructor fails.
Notice that you enter the same catch-block independ of whether the
constructor fails or whether new fails.

Also, what is wrong with multiple resource allocations inside a
constructor
body (as long as the matching destructor gets to deallocate them)?


if you allocate multiple resources in a single constructor-body
(
e.g. not as subobjects but e.g. pointers.
Same applies if you wrap multiple C-style resources initializations inside a
single class instead of one class for one C-style resource
):


struct A
{ B *m_pB;
C *m_pC;
D *m_pD;
A(void)
{ m_pB = createB();
try
{ m_pC = createC();
} catch (...)
{ delete m_pB;
throw;
}
try
{ m_pD = createD();
} catch (...)
{ delete m_pC;
delete m_pB;
throw;
}
}
~A(void)
{ delete m_pD;
delete m_pC;
delete m_pB:
}
};

Same applies if your creation functions are C-style error code returning
functions.
You would have to perform cleanup duty yourself.

It is better to use sub-objects:

struct A
{ std::auto_ptr<B> m_sB;
std::auto_ptr<C> m_sC;
std::auto_ptr<D> m_sD;
A(void)
: m_sB(createB()),
m_sC(createC()),
m_sD(createD())
{
}
}


Here the compiler generates the code which destroys the already successfully
constructed subobjects
if construction of some subobject fails.
And you don't need to code the destructor at all.

(
std::auto_ptr is an exception to the rule that you should undo in the
destructor what you did in the constructor.
using std::auto_ptr you undo in the destructor what you do just before
calling the constructor.
This is ok, since the constructor of std::auto_ptr never throws.
)


Using C++ Exception Handling it is finally possible to use constructors for
fallible resource allocation.
Write one class for every type of resource allocation you have to perform.
Then chain such classes into base-class or member-class relationships.
Of course you will have to know (and exploit) the order of construction of
base-objects/member-objects.
 
Y

yinglcs

Effective C++ is not that modern a book is it, 10 years old according to
Amazon.

Your criticism is valid I guess, but Effective C++ is a beginners book,
and exception safety is an advanced topic.

It remains an exceptionally good book, taught me more about C++ than any
other single book I've read.

john

can you suggest a good book for advanced topic in c++? e.g. the
exception safety that you mention?
 
J

John Harrison

can you suggest a good book for advanced topic in c++? e.g. the
exception safety that you mention?

There's a series of books that grew out of the guru of the week column
(http://www.gotw.ca/gotw/), the first is called Exceptional C++ by Herb
Sutter.

Personally I didn't like the style much, but the material covered is good.

john
 
D

Diego Martins

Effective C++ is not that modern a book is it, 10 years old according to
Amazon.

Your criticism is valid I guess, but Effective C++ is a beginners book,
and exception safety is an advanced topic.

It remains an exceptionally good book, taught me more about C++ than any
other single book I've read.

john

I can surely say I only learned C++ properly after I read Scott
Meyers.
I agree completely with john

(and I only learned templates properly after Alexandrescu...)

Diego
 
P

Peter

Really? That seems like a slight overreaction.


I happen to consider the order of construction of a nontrival class
as one of the most important things to know about C++.
A lot of things can be done during construction of an object,
which somebody who does not know about this, may do inside some
init-function.
I consider usage of an init-function to be pre-C++-Exception-Handling-Style.
 
P

Phlip

Peter said:
I consider usage of an init-function to be
pre-C++-Exception-Handling-Style.

A class should be easy to use, both for production clients and test cases.
So a class that has the option to construct with links to other obese
classes should also have the option to construct minimally.

Yes, adept use of fat constructors does demonstrate modern C++
understandings. That's no excuse to make every constructor fat.
 
D

Diego Martins

There's a series of books that grew out of the guru of the week column
(http://www.gotw.ca/gotw/), the first is called Exceptional C++ by Herb
Sutter.

Personally I didn't like the style much, but the material covered is good.

john

these books are good, but their style is too tiresome. I never could
finish this books

Effective C++, More Effective C++, Effective STL and Modern C++ Design
serve well as reference guides because they are well organized

the organization of gotw books is too confusing. I think a clipping or
a reedition may be of great help in order to get the valuable material
of Herb Sutter (exception safety is his best approach, imho)

Diego
 

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

Forum statistics

Threads
473,734
Messages
2,569,441
Members
44,832
Latest member
GlennSmall

Latest Threads

Top