Re: Exception Safety
On May 24, 3:27 am, jawc...@gmail.com wrote:
{ Some lines are too long. Please fit your text into 70 columns or so;
at most 79 columns. -mod }
Can any one help me as to what I am thick on?
Would appreciate if cced too on jawc...@gmail.com
It helps to write out how the compiler writer would implement this
behaviour, using pseudo C++ code. It can't be real C++ code, because
we need to detecting things like if the programmer actually
implemented class specific new/delete. Plus, there are a number of
optimization that can be done depending on just what T is that are
unavailable to the C++ app writer.
Leaving out the "new_handler" machinery, this is my conceptual non-
optimized implementation for the new[] and delete [] expressions, that
works for any T:
template<class T> void array_delete(T* p);//forward declaration
//new T[n];
template<class T>
T* array_new(std::size_t n){
// use global operator new[] or class operator new[]?
// expression is impossible to make work in standard C++
// but a compiler writer can find this out somehow
bool const classarrnew=has_operator_arraynew<T>::value;
// make room for the count of sucessfully constructed objects
std::size_t const memsize=sizeof(std::size_t)+sizeof(T)*n;
//if this throws, we are OK since there is nothing to undo
//cast to char (byte) so we can do some math on him
char* mem= reinterpret_cast<char*>( classarrnew?
(T::operator new[](memsize)) :
( ::operator new[](memsize)));
//make the leading byte look like a size_t
std::size_t& initcount=*reinterpret_cast<std::size_t*>(mem));
//(for simplcity, assume size_t aligment)
//adjust pointer up to the memory for the actual T's
T* ret=reinterpret_cast<T*>(mem+sizeof(std::size_t));
//now what we have is raw memory with this layout
// initcount | T[0] | | T[n-1]
//0 1 2 3 4 5 6 .... .............memsize-1 memsize
//now initialize
initcount=0;
//keeps a running total of successfully created objects
try{
for(;initcount!=n;++initcount){
new (ret+initcount) T;//placement new, just calls constructor
}
}catch(...){ //oops a constructor threw
array_delete(ret);
throw;//rethrow
}
return ret;//return pointer to first T
}
//delete[] p;
template <class T>
void array_delete(T* p){
if(!p) return;//required by standard
//p points to first T object.
//use global operator delete[] or class operator delete[]?
bool const classarrdel=has_operator_arraydelete<T>::value ;
//cast to char (byte) so we can do some math on him
char* mem=reinterpret_cast<char*>(p);
//go to the start
void * adjmem=(mem-sizeof(std::size_t));
//make the leading byte look like a size_t
std::size_t& initcount=*reinterpret_cast<std::size_t*>(adjmem);
//now we have the total number of *fully constructed* objects
//need to delete in reverse order (according to standard)
for(;initcount!=0;--initcount){
(p+(initcount-1))->~T();//destruct T (no-op for fundamental type)
}
//now get rid of memory
classarrdel? T::operator delete[](adjmem):
(::operator delete[](adjmem));
}
Walking through this, it become very apparent why the (good) C++
authors stress a number of points:
1. operator delete and Destructors should never fail (Sutter)
2. if you define operator new for a class, you should always define
operator delete (Meyers)
3. it is preferable just to use std::vector instead of new[]-- there
is a size() function, dont have to init everything at once, you can
make it use different heap manager without implementing operator new[]
(impossible for fundamental types in any case), it is just as
exception safe, you dont have to match up with delete[], and so
forth. (Stroustrup)
(historical note: new [] preceded std::vector by 10 years at least)
4. you should always call operator delete[] when you use operator new
[] (language rule)
This is just one way a compiler could implement this. Many compilers
would make an optimization for the case of T with a trivial destructor
-- no initcount is needed, since there is nothing to destroy. Also,
another optimization could be made if T is a fundamental type, which
is very often the case. Here neither the initcount nor the try/catch
block nor the for loop is needed --you could just return
whatever ::operator new(size_t) gives you. And I'm sure the the
placement of the initcount varies widely across implementations, if
there is an initcount at all.
But whether a particular compiler actually does these or something
else, who knows?
More info on this subject is in Lippmann's "Inside the C++ Object
Model." Also you may want to look at std::unitialized_fill and
std::unitialized_fill_n -- these functions implement a very similar
concept.
Lance
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]