我正在实现一个像std :: vector这样的简单向量,我编写了一些函数,而不用担心它给出了什么样的异常安全保证。我对c ++的例外有一点了解,但我没有经历过编写异常安全代码。 这是我的代码:
template <typename T>
class vector {
public:
vector(int _DEFAULT_VECTOR_SIZE = 10) :
size(0), array_(new T[_DEFAULT_VECTOR_SIZE]), capacity(_DEFAULT_VECTOR_SIZE) {}
void push_back(T& elem)
{
if (size == capacity)
resize(2 * size);
array_[size] = elem;
size++;
}
void pop_back()
{
--size;
}
void resize(int size_)
{
if (size_ > capacity)
{
T* temp = new T[size_];
memcpy(temp,array_,size*sizeof(T));
swap(temp, array_);
delete[] array_;
capacity = size_;
}
}
private:
T* array_;
int size;
int capacity;
};
所以我的问题是:我如何修改我的代码(函数),至少提供基本保证,或者为基本或强保证编写异常安全代码的一些技巧? 感谢
答案 0 :(得分:1)
异常安全有两种主要形式:
您面临的主要挑战是处理赋值和复制构造函数。正如评论已经注意到的那样,您不应该使用memcpy
,因为它无法调用复制构造函数。例如,复制std::string
也应该复制字符缓冲区,字符串向量是一个非常正常的类型,你应该支持它。
那么,让我们看看你的vector的拷贝构造函数。它需要复制源向量的每个元素。每个单独的副本都可以抛出。如果其中一个字符串太长而副本抛出std::bad_alloc
怎么办?
现在,异常安全意味着您将程序保持在一个理智的状态,因此没有内存泄漏。您的矢量副本ctor失败,因此dtor将无法运行。谁清理了T* array
呢?这必须在您的副本中完成。
当复制失败时,将不会有新的向量,因此您可以免费获得第二种类型的异常安全性。 (“强烈的异常安全”)。但是让我们看看下面的赋值运算符v2 = v1
。你会覆盖一个旧的矢量。如果您先执行.resize(0)
然后复制所有元素,则可能会在副本中途遇到异常。您的原始矢量内容已消失,新内容不完整。尽管如此,你还没有泄露任何记忆,也没有复制半个元素。
为了使分配安全,有一个简单的技巧:首先将源向量复制到临时向量。如果失败,没问题(见上文)。我们还没有碰到目的地。但是如果赋值成功,我们会交换临时和目标向量的array*
指针size
和capacity
。交换指针和交换int是安全的(不能抛出)。最后,我们让临时向量超出范围,这会破坏不再需要的旧向量元素。
因此,通过对临时对象进行所有危险操作,我们确保任何异常都不会触及原始状态。
您需要检查所有方法以查看是否可能出现这些问题,但模式通常类似。如果元素副本或元素赋值抛出,请不要泄漏array
,请将该异常传播给调用者。