美文网首页Exceptional C++
【Exceptional C++(6)】异常处理的安全性

【Exceptional C++(6)】异常处理的安全性

作者: downdemo | 来源:发表于2018-01-26 16:48 被阅读14次

问题

  • 实现如下异常-中立的container
    • Stack对象必须保持一致性
    • 即使有内部操作抛出异常,Stack对象也必须是可析构的
    • T的异常必须能传给调用者
template <class T>
class Stack {
public:
    Stack();
    ~Stack();
    Stack(const Stack&);
    Stack operator=(const Stack&);
    unsigned Count(); // 返回T在栈中的数目
    void Push(const T&);
    T pop(); // 如果为空返回缺省构造出来的T
private:
    T* v_;
    unsigned vsize_; // v_区域大小
    unsigned vused_; // v_区域中实际使用的T数目
};

说明

  • 异常中立:代码引发异常时,异常能保持原样传递到外层调用代码
  • 异常安全在Effective C++条款29提到过。当异常抛出时,有异常安全性的函数会不泄露任何资源,不允许数据破坏。异常安全函数必须提供三个保证之一才具有异常安全性
    • 基本承诺:如果异常抛出,程序内任何事物仍保持有效状态,任何对象或数据结构都不被破坏,所有对象都满足前后一致
    • 强烈保证:如果异常抛出,程序状态不改变,如果函数成功就是完全成功,否则回到调用函数之前的状态
    • 不抛掷保证:承诺绝不抛出异常,它们总能完成它们原先承诺的功能,作用于内置类型上的操作都提供nothrow保证

解答

// 默认构造
template<class T>
Stack<T>::Stack()
  : v_(new T[10]),
    vsize_(10),
    vused_(0)
{
    // 若程序到达这里说明构造过程没问题
}
// 拷贝构造
template<class T>
Stack<T>::Stack(const Stack<T>& other)
  : v_(0),
    vsize_(other.vsize_),
    vused_(other.vused_)
{
    v_ = NewCopy(other.v_, other.vsize_, other.vsize_);
    // 若程序到达这里说明拷贝构造过程没问题
}
// 拷贝赋值
template<class 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_;
        // 这里不能抛出异常,因为T的析构函数不能抛出异常
        // ::operator delete[]被声明成throw()
        v_ = v_new;
        vsize_ = other.vsize_;
        vused_ = other.vused_;
    }
    return *this; // 很安全,没有拷贝问题
}
// 析构
template<class T>
Stack<T>::~Stack()
{
    delete[] v_; // 同上,这里也不能抛出异常
}
// 计数
template<class T>
unsigned Stack<T>::Count()
{
    return vused_; // 只是一个内建类型,不会有问题
}
// push
template<class T>
void Stack<T>::Push(const T& t)
{
    if (vused_ = vsize_) // 可以随需要而增长
    {
        unsigned vsize_new = (vsize + 1) * 2; // 增长因子
        T* v_new = NewCopy(v_, vsize_, vsize_new);
        // 若程序到达这里,说明内存分配和拷贝过程都没问题
        delete[] v_; // 同上,这里也不能抛出异常
        v_ = v_new;
        vsize_ = vsize_new;
    }
    v_[vused_] = t; // 如果这里抛出异常,增加操作不会执行
    ++vused_; // 状态也不会改变
}
// pop
template<class T>
T Stack<T>::Pop()
{
    T result;
    if (vused_ > 0)
    {
        result = v_[vused_-1]; // 如果这里抛出异常,减操作不会执行
        --vused_;
    }
    return result;
}
// pop强迫使用者编写非异常安全代码
// 这首先就产生一个副作用,从栈中pop一个元素
// 解决办法是把函数重构成void Stack<T>::Pop(T& result)
// 这样可以在栈状态改变前得知结果的拷贝是否成功
template<class T>
void Stack<T>::Pop(T& result)
{
    if (vused_ > 0)
    {
        result = v_[vused_ - 1];
        --vused_;
    }
}
// 辅助函数
// 当把T从缓冲区拷贝到更大的缓冲区时
// 辅助函数分配新缓冲区并拷贝元素
// 如果这里异常,辅助函数释放所有临时资源
// 并把异常传递出去,保证不发生内存泄漏
template<class T>
T* NewCopy(const T* src, unsigned srcsize, unsigned destsize)
{
    destsize = max(srcsize, destsize); // 基本的参数检查
    T* dest = new T[destsize];
    // 如果程序到达这里说明内存分配和构造函数没问题
    try
    {
        copy(src, src + srcsize, dest);
    }
    catch(...)
    {
        delete[] dest;
        throw;
    }
    // 如果程序到达这里说明拷贝操作也没问题
    return dest;
}

相关文章

网友评论

    本文标题:【Exceptional C++(6)】异常处理的安全性

    本文链接:https://www.haomeiwen.com/subject/lajwaxtx.html