美文网首页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