異常安全性問題
- 異常安全代碼
在出現異常的情況下仍然能夠正確運行- 異常中立
將所有的異常都轉發給調用者- 永遠不要在析構函數、重載運算符函數operator delete()或者operator delete[]中拋出異常
在編寫析構函數和內存釋放函數時,要假設存在throw()這樣的異常規范
以template < typename T > class Stack為例
template <typename T>
class Stack
{
public:
Stack();
~Stack();
Stack( const Stack & );
Stack & operator=( const Stack & );
size_t Count() const { return vused_; }
void Push( const T& t );
T & Top()
{
if ( 0 == vused_ )
{
throw "empty stack";
}
return v_[vused_-1];
}
void Pop()
{
if ( 0 == vused_ )
{
throw "pop from empty stack";
}
else
{
-- vused_;
}
}
private:
T *NewCopy( const T *src,
size_t srcsize,
size_t destsize );
T * v_;
size_t vsize_;
size_t vused_;
};
默認構造過程
template <typename T>
Stack<T>::Stack()
: v_( new T[10] ),
vsize_( 10 ),
vused_( 0 )
{
}
析構過程
template <typename T>
Stack<T>::~Stack()
{
delete[] v_;
}
輔助函數
template <typename T>
T *Stack<T>::NewCopy( const T *src, size_t srcsize, size_t destsize )
{
assert( destsize >= srcsize );
T *dest = new T[destsize];
try
{
copy( src, src+srcsize, dest );
}
catch ( ... )
{
delete[] dest;
throw;
}
return dest;
}
拷貝構造函數
template <typename T>
Stack<T>::Stack( const Stack<T> &other )
: v_( NewCopy( other.v_, other.vsize_, other.vsize_ ) ),
vsize_( other.vsize_ ),
vused_( other.vused_ )
{
}
拷貝賦值過程
template <typename T>
Stack<T> &Stack<T>::operator=( const Stack<T> &other )
{
if ( this != &other )
{
T *v_new = NewCopy( other.v_, other.vsize_, other.vsize_ );
delete[] v_;
v_ = v_new;
vsize_ = other.vsize_;
vused_ = other.vused_;
}
return *this;
}
Push()
template <typename T>
void Stack<T>::Push( const T &t )
{
if ( vused_ == vsize_ )
{
size vsize_new = vsize_*2+1;
T *v_new = NewCopy( v_, vsize_, vsize_ );
delete[] v_;
v_ = v_new;
vsize_ = vsize_new;
}
v_[vused_] = t;
++ vused_;
}
- 基本保證:如果在類型T中或者程序中其他的地方拋出了異常,對象不應該造成資源泄漏
- 強保證:如果某個操作由于拋出異常而終止,那么程序的狀態應該保持不變
- 無異常拋出保證:無論什么情況下,函數都不會拋出異常
封裝內存管理工作
template <typename T1, typename T2>
void construct( T1 *p, const T2 &value )
{
new (p) T1(value);
}
template <typename T>
void destroy( T *p )
{
p->~T();
}
template <typename T>
void destroy( T first, T last )
{
while ( first != last )
{
destroy( &*first );
++ first;
}
}
template <typename T>
class StackImpl
{
protected:
StackImpl( size_t size=0 )
: v_( static_cast<T *>( 0==size ? 0 : operator new( sizeof(T)*size ) ) ),
vsize_( size ),
vused_( 0 )
{
}
~StackImpl()
{
destroy( v_, v_+vused_ );
operator delete( v_ );
}
void Swap( StackImpl & other ) throw()
{
swap( v_, other.v_ );
swap( vsize_, other.vsize_ );
swap( vused_, other.vused_ );
}
T *v_;
size_t vsize_;
size_t vused_;
private:
StackImpl( const StackImpl & );
StackImpl & operator=( const StackImpl & );
};
更好的Stack
template <typename T>
class Stack : private StackImpl<T>
{
public:
Stack( size_t size=0 )
: StackImpl<T>( size )
{ }
~Stack();
Stack( const Stack &other )
: StackImpl<T>( other.vused_ )
{
while ( vused_ < other.vused_ )
{
construct( v_+vused_, other.v_[vused_] );
++ vused_;
}
}
Stack & operator=( const Stack & other )
{
Stack temp( other );
Swap( temp );
return *this;
}
size_t Count() const
{
return vused_;
}
void Push( const T & )
{
if ( vused_ == vsize_ )
{
Stack temp( vsize_*2+1 );
while ( temp.Count() < vused_ )
{
temp.Push( v_[temp.Count()] );
}
temp.Push( t );
swap( temp );
}
else
{
construct( v_+vused_, t );
++ vused_;
}
}
T & Top()
{
if ( 0 == vused_ )
{
throw "empty stack";
}
return v_[ vused_-1 ];
}
void Pop()
{
if ( 0 == vused_ )
{
throw "pop from empty stack";
}
else
{
-- vused_;
destroy( v_+vused_ );
}
}
}
優雅的實現
假設在StackImpl中,屬性為public
template <typename T>
class Stack
{
public:
Stack( size_t size=0 )
: impl( size )
{ }
~Stack();
Stack( const Stack & other )
: impl( other.impl_.vused_ )
{
while ( impl_.vused_ < other.impl_.vused_ )
{
construct( impl_.v_+impl_.vused_, other.impl_.v_[impl_.vused_] );
++ impl_.vused_;
}
}
Stack & operator=( const Stack & other )
{
Stack temp( other );
impl_.Swap( other.impl_ );
return *this;
}
size_t Count() const
{
return impl_.vused_;
}
void Push( const T& t )
{
if ( impl_.vused_ == impl_.vsize_ )
{
Stack temp( impl_.vsize_*2+1 );
while ( temp.Count() < impl_.vused_ )
{
temp.Push( impl_.v_[temp.Count()] );
}
temp.Push( t );
impl_.Swap( temp.impl_ );
}
else
{
construct( impl_.v_+impl_.vused_, t );
++ impl_.vused_;
}
}
T &Top()
{
if ( 0 == impl_.vused_ )
{
throw "empty stack";
}
returm impl_.v_[ impl_.vused_-1 ];
}
void Pop()
{
if ( 0 == impl_.vused_ )
{
throw "pop from empty stack";
}
else
{
--impl_.vused_;
destroy( impl_.v_+impl_.vused_ );
}
}
private:
StackImpl< T > impl_;
};
安全的異常
指導原則
- 永遠不要在析構函數、重載的operator delete()或者operator delete[]()等函數中拋出異常:編寫每個析構函數和內存釋放函數時,都要假定在聲明這些函數的時候使用了throw()異常規范
- 通過RAII(“資源獲得也就意味著初始化”)這種慣用法來分離資源的所有權和資源的管理權
- 在每個函數中,將所有可能會拋出異常的代碼放在一起,并進行安全處理,當確認這些代碼所進行的工作都已經成功地完成時,才可以使用不會拋出異常的操作來修改程序的狀態
參考資料:
《Exceptional C++中文版》