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 

Improving Effective C++'s Item 42

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





PostPosted: Sat Sep 10, 2005 1:39 pm    Post subject: Improving Effective C++'s Item 42 Reply with quote



Hi!

I'm reading "Effective C++" 2nd Edition, by Scott Meyers - fantastic
book BTW - and I'd like to propose my improvement of Stack class.

The idea is to create a template class that avoids code bloat. It is
achieved by using a generic stack that operates on pointers and doeas
all the job, and also templatized classes that ensure type safety of all
the operations. But there was one restriction: the stack has to hold
pointers to objects. My solution tries to get around this problem. Much
of the comments refer to the version presented in the book, but I think
that they are useful nonetheless.

Maybe it's not the most beautiful code in the world, but I think it
works. I've tested this code: all ctors/c-ctors/dtors were called
correctly and there were no memory leaks.

Comments are welcome!

Stefan Chrobot

/*

Stack

Implementation of a stack which tries to avoid code bloat.
This time Stack can hold any type and it does it in STL fashion, i.e.
holding copies of objects. Every StackNode holds a pointer to a memory,
where resides it's corresponding object. GenericStack and it's
specializations take care of allocation and deallocation of this memory.

*/


// Generic stack declaration
//
// There are three new member functions in GenericStack. objectSize()
// is to return the number of bytes, that each object of held type
// T needs. destroyObject()'s role is to explicitly call the destructor
// for type T. The functionality of both depends on the derived class'
// type, which is unknown to GenericStack, so both were made pure
// virtual. The last function - purge() - takes role of the destructor
// and frees all the memory reserved by the stack. The reasoning behind
// this is that the destructor must call destuctors for each held
// object, but it doesn't know it's type. We can't use destroyObject()
// in destructor, because all calls in constructors and destructors are
// early bound, because in constructor the derived part is not yet
// constructed and in the destructor it's already destroyed. One could
// move the code to destructor of specialized stacks, but then we would
// cause code bloat, which we are trying to avoid. We call purge() from
// destructors of specialized stacks. Apart from that and argument and
// return type of push (explained later), everything stays the same.
class GenericStack
{
protected:
GenericStack();
~GenericStack();
void* push();
void* pop();
bool empty() const;
virtual size_t objectSize() const = 0;
virtual void destroyObject(void*) const = 0;
void purge();
private:
GenericStack(const GenericStack&); // disallowed
GenericStack& operator=(const GenericStack&); // disallowed
struct StackNode
{
void* data;
StackNode* next;
StackNode(void* newData, StackNode* nextNode)
: data(newData), next(nextNode) { }
};
StackNode* top;
};

// Generic stack implementation

// Same as before
GenericStack::GenericStack() : top(0) { }

// All that push() knows and can do is to create a new element
// in the list, connect it to the list, and allocate amount of
// memory, which is specified by derived class. push() returns pointer
// to memory, where new object should be constructed. This pointer is
// then used when calling placement new.
void* GenericStack::push()
{
// create sufficient space
void* newObjectPlace = new char[objectSize()];
// create new list node
top = new StackNode(newObjectPlace, top);
// return pointer to memory
return newObjectPlace;
}

// The role of the pop() is to return a copy of object that sits on the
// top of the stack. Because pop() doesn't know how to construct object
// of unknown type, it just disconnects the top node of the list and
// lets specialized pop() do the rest of the job.
void* GenericStack::pop()
{
StackNode* topOfStack = top; // retrieve top of the stack
top = top->next; // "move" the top of the stack
void* data = topOfStack->data; // remember object held on top
delete topOfStack; // remove top list node
return data; // return address of top element
}

// Same as before
bool GenericStack::empty() const
{
return top == 0;
}

// Do nothing, all handled by purge()
GenericStack::~GenericStack()
{
}

// When removing elements of generic stack, destructor for every object
// has to be called explicitly, because all of them were created using
// placement new, so simply deleting them causes errors.
void GenericStack::purge()
{
while(top)
{
// get last list node
StackNode* toDie = top;
// "move" top of the list
top = top->next;
// call destructor for object, virtual call works OK
destroyObject(toDie->data);
// free object's memory
delete[] static_cast<char*>(toDie->data);
// free list's node memory
delete toDie;
}
}

// Template stack
template<typename T>
class Stack : private GenericStack
{
public:
~Stack()
{
GenericStack::purge(); // Remove all objects
}
// New version of push, accepts argument by const reference,
// like STL containers do.
void push(const T& object)
{
// create new list node, reserve required memory for object,
// retrieve the pointer to where the object should reside
void* newObjectSpace = GenericStack::push();
// create object in specified location of memory using copy
// constructor, making a copy of push()'s argument
new (newObjectSpace) T(object);
}
// pop() has to return a copy of the object
// we're holding on stack
T pop()
{
// remove top list node without removing object's memory
// and therefore the object itself, get pointer to it
T* toDie = static_cast<T*>(GenericStack::pop());
// construct return value from object in memory
T popped = *toDie;
// destroy object
destroyObject(toDie);
// free object's memory, need reinterpret_cast unfortunately
delete[] reinterpret_cast<char*>(toDie);
// return popped object
return popped;
}
// same as before
bool empty() const { return GenericStack::empty(); }
private:
// easy to implement using sizeof()
size_t objectSize() const { return sizeof(T); }
// destroyObject calls destructor explicitly
void destroyObject(void* object) const
{
static_cast<T*>(object)->~T();
}
};

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

Back to top
Andreas Magnusson
Guest





PostPosted: Sun Sep 11, 2005 3:13 pm    Post subject: Re: Improving Effective C++'s Item 42 Reply with quote



Hi, I've also read Meyer's "Effective C++" (don't remember which
edition though) and it's very good.

Quote:
and I'd like to propose my improvement of Stack class

Whoa, I'm a bit sceptical about statements like this...and I've found I
have a reason to be:

You have a possible problem with your GenericStack::push().
Depending on what flavour of new you use you may lose the whole stack.
If you use nothrow new, it may return 0, thus the whole stack is leaked
(or lost into the void, whatever you like). If you use throwing new,
you may be safe considering that the ctor of StackNode cannot throw.
However if it could you'd be in the same mess. The (not so) funny thing
is that GenericStack::push() will seem to work on some compilers and
not on others depending on whether nothrow or throwing new is the
default.

Which brings us to Stack<T>::push(), what happens if the ctor of T
throws? Then you have a StackNode without a valid data-member. Which
basically means that your current top() is invalid (to say the least).
You don't want to alter the stack until you have safely created the
object!

A similar problem is in Stack<T>::pop(). It's also not exception safe.
What if T's ctor throws? Then you will leak memory! You'll want to make
sure that either your object is destroyed even if an exception occurs,
or that nothing happens. As you have it now you have removed the object
from the stack but not deleted it (if an exception occurs).

There's more to stacking cats than first meets the eye!

Keep up the good work and good luck!

/Andreas


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


Back to top
Joel Eidsath
Guest





PostPosted: Sun Sep 11, 2005 9:09 pm    Post subject: Re: Improving Effective C++'s Item 42 Reply with quote



Although Meyer's Stack implmentation was appropriate for making his
points: "Differentiate between inheritance and templates" and "Use
private inheritance judiciously," you would probably want to use the
STL stack in real code.


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

Back to top
Andreas Magnusson
Guest





PostPosted: Sun Sep 11, 2005 9:11 pm    Post subject: Re: Improving Effective C++'s Item 42 Reply with quote

Oops, I forgot to say (a bit tired when I wrote my previous post) as I
recall it Meyer also explains why having pop() return the top element a
bad idea if you want to play it exception-safe. Anyway, here's why:
Eventhough you may be able to extract the element and free the data in
the stack in an exception-safe manner, you may still not be fully
exception-safe. Consider this:

CtorCanThrow v = s.pop();

or even

AssignmentCanThrow v;
v = s.pop();

What may happen is that the element just popped from the stack may get
lost as the ctor/assignment operator of v throws an exception. So you
popped an element but it's gone.

This is why the STL stack implementation (as all stack implementations
that are exception-safe) has three functions that covers the stack
"concept" (push/pop): push(), pop() and top(). Both push() and pop()
return void and top() returns the top-element without popping it.

A great book to read to become more exception-aware is Herb Sutter's
"Exceptional C++". For me it was definately an eye-opener as I believe
it will be for you.

Sorry if I come out a bit harsh, my aim is to educate not to discourage
you!

Cheers,
Andreas


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

Back to top
Stefan Chrobot
Guest





PostPosted: Sun Sep 11, 2005 9:12 pm    Post subject: Re: Improving Effective C++'s Item 42 Reply with quote

Andreas Magnusson napisał(a):
Quote:
Hi, I've also read Meyer's "Effective C++" (don't remember which
edition though) and it's very good.

Mr Meyers keeps on improving it, there's new edition - 3rd - which came
out in May. Wort checking out.

Quote:
Depending on what flavour of new you use you may lose the whole stack.
cut


Up till now I thought the Standard states (though I never read sentence
that would say so) that default new throws, always (though you can still
make it return 0 on failure). Is it true or false? Then again, if it's a
matter of backward compatibility: do I have to worry about it or are
there compilers that support templates but do not support exceptions?

Quote:
It's also not exception safe.
cut


Thank you for checking my code! At the beginning I thought that making
code exception safe would be easy, but then I started passing those
pointers... Well, this is how you gain experience, which I'm still
lacking. I'll try to fix the code and I'll post it then.

Stefan Chrobot

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


Back to top
Stefan Chrobot
Guest





PostPosted: Mon Sep 12, 2005 9:25 am    Post subject: Re: Improving Effective C++'s Item 42 Reply with quote

Andreas Magnusson napisa=B3(a):
Quote:
A great book to read to become more exception-aware is Herb Sutter's
"Exceptional C++". For me it was definately an eye-opener as I believ=
e
it will be for you.
It's already on my to-be-read list, but thanks for pointing that out.


Quote:
Sorry if I come out a bit harsh, my aim is to educate not to discoura=
ge
you!
Constructive criticism is really a good thing.


To answer Joel Eidsath:
Quote:
Although Meyer's Stack implmentation was appropriate for making his
points: "Differentiate between inheritance and templates" and "Use
private inheritance judiciously," you would probably want to use the
STL stack in real code.
Agreed, but then - quoting Scott Meyers - "For this exercise, you'll

ignore the classes in the standard library (...), because you crave the=

experience of writing the code yourself. Reuse is a wonderful thing, bu=
t
when your goal is a deep understanding of how something works, there's
nothing quite like diving in and getting your hands dirty." :-)

And follow-up with myself:
Quote:
I'll try to fix the code and I'll post it then.
Which I believe will not happen so quick.


Stefan Chrobot

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


Back to top
Andreas Magnusson
Guest





PostPosted: Mon Sep 12, 2005 9:27 am    Post subject: Re: Improving Effective C++'s Item 42 Reply with quote

Quote:
Up till now I thought the Standard states (though I never read sentence
that would say so) that default new throws...


Yes, a standards compliant compiler should default to throwing new, but
for library code (as a stack certainly is), I wouldn't rely on:
a) that the client's compiler is standards compliant
b) that the client hasn't changed the default behaviour through
compiler switches

What I mean is that for library code it's not good to rely on that the
client will use throwing new in order to function correctly. Instead
you should declare what you want explicitly.
For example MSDN recommends that you use nothrow when writing third
party libs, since you cannot be sure what the user will chose and using
nothrow is the only way to have a deterministic behaviour (across
different projects).

That said, I would agree with the other poster that using the STL stack
is preferable to writing your own. But writing your own is good for
learning!

Cheers,
Andreas


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


Back to top
Maciej Sobczak
Guest





PostPosted: Mon Sep 12, 2005 9:33 am    Post subject: Re: Improving Effective C++'s Item 42 Reply with quote

Stefan Chrobot wrote:

Quote:
Up till now I thought the Standard states (though I never read sentence
that would say so) that default new throws, always (though you can still
make it return 0 on failure). Is it true or false?

If you don't replace the allocation function (and you can replace it
even globally), then the one that is used by default should either
return a pointer to successfully allocated memory or throw bad_alloc.

In the standard, it is the 18.4.1.1 that formalizes this, with 3.7.3.1
and 5.3.4 giving complete context.

Quote:
Then again, if it's a
matter of backward compatibility: do I have to worry about it or are
there compilers that support templates but do not support exceptions?

Don't worry about all broken compilers in the world. :)


--
Maciej Sobczak : http://www.msobczak.com/
Programming : http://www.msobczak.com/prog/

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


Back to top
Ulrich Eckhardt
Guest





PostPosted: Mon Sep 12, 2005 2:52 pm    Post subject: Re: Improving Effective C++'s Item 42 Reply with quote

Stefan Chrobot wrote:
Quote:
The idea is to create a template class that avoids code bloat.

And, did you succeed? Seriously, how did you measure the bloat and what are
the numbers?


Quote:
virtual size_t objectSize() const = 0;

Size of the contained objects...

Quote:
void* GenericStack::push()
{
// create sufficient space
void* newObjectPlace = new char[objectSize()];
// create new list node
top = new StackNode(newObjectPlace, top);
// return pointer to memory
return newObjectPlace;
}

OK, if I count right, this is the only place the size of the object needs to
be known. Therefore, I'd pass the size from the derived class, because
passing a single parameter whos value is a compile-time constant is
probably faster than jumping from 'this' to the vtable to a function that
returns the parameter. Alternatively, you could store it as constant
because it doesn't change throughout the lifetime of the stack.

Another thing (which has nothing to do with your initial intent of
combatting code bloat) is that you use two allocations. Others already
pointed out that it is not exception safe, but it is also not efficient. I
seem to remember that the time cost of returning an object on the stack and
using free store (of course, for a particular compiler/platform) is the
same when the payload is around 400 bytes. IOW, the time it takes to
allocate your list node is much larger than the time it takes to construct
the node itself.
Since the allocation of the stack value is always connected with the
allocation of the stack node, you should connect these two. Doing so
requires allocating storage for your node and the data payload. Just to
make it more interesting, you have to care for proper alignment, putting
both into a struct will probably help.

Lastly, why not use std::vector<> as backing store? Even if you only store a
vector of pointers it would make several things easier and probably not
cost too much performance. If you use it to store raw data, remember that
objects must be memmoveable then like e.g. PODs.


Quote:
// free object's memory, need reinterpret_cast unfortunately
delete[] reinterpret_cast<char*>(toDie);

No you don't need reinterpret_cast, just a combination of static_casts that
convert from T* to void* to char*.

Uli


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


Back to top
Stefan Chrobot
Guest





PostPosted: Tue Sep 13, 2005 12:21 am    Post subject: Re: Improving Effective C++'s Item 42 Reply with quote

Ulrich Eckhardt napisa=B3(a):
Quote:
The idea is to create a template class that avoids code bloat.
And, did you succeed? Seriously, how did you measure the bloat and wh=
at are
the numbers?

I am neither able to measure the result nor evaluate it as a failure or=

success even if I had needed results. But I'll consider a success
getting the code of this exercise to work with all major and minor
feautures.

Quote:
virtual size_t objectSize() const = 0;
Size of the contained objects...

OK, if I count right, this is the only place the size of the object n=
eeds to
be known. Therefore, I'd pass the size from the derived class, becaus=
e
passing a single parameter whos value is a compile-time constant is
probably faster than jumping from 'this' to the vtable to a function =
that
returns the parameter. Alternatively, you could store it as constant
because it doesn't change throughout the lifetime of the stack.

You're absolutely right. I forgot that I can pass it as an argument for=

GenericStack's constructor.

Quote:
Lastly, why not use std::vector<> as backing store? Even if you only =
store a
vector of pointers it would make several things easier and probably n=
ot
cost too much performance. If you use it to store raw data, remember =
that
objects must be memmoveable then like e.g. PODs.

Do you mean storing raw data in a vector? Wouldn't I need size of the
objects at compile-time?

Could you please explain what memmovable objects are? When object is no=
t
memmovable?

Stefan Chrobot

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


Back to top
Ulrich Eckhardt
Guest





PostPosted: Tue Sep 13, 2005 9:10 am    Post subject: Re: Improving Effective C++'s Item 42 Reply with quote

Stefan Chrobot wrote:
Quote:
Ulrich Eckhardt napisa=B3(a):
Lastly, why not use std::vector<> as backing store? Even if you only
store a vector of pointers it would make several things easier and
probably not cost too much performance. If you use it to store raw
data, remember that objects must be memmoveable then like e.g. PODs.

Do you mean storing raw data in a vector? Wouldn't I need size of the
objects at compile-time?

No, as you are just storing unsigned char, just as your code already did.

Quote:
Could you please explain what memmovable objects are? When object is
not memmovable?

In general, movability is not given when an object directly or indirectly
contains a pointer to itself. Finding out if an object is moveable is
another things though, type_traits can help, explicit specialization of
helper classes can help, too.

Uli


[ 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.