我目前正在开发一个使用分配器来管理资源的容器类。我将尝试给出我当前调整容器大小的简短版本。 (真实的不是一维的,但方案是相同的,因为分配的数据是连续的。)
我不清楚的所有内容都标记为 [[[ x ]]]。
示例代码
template<typename T>
class example
// ...
注释:
- size_type === std::allocator::size_type
- 指针 === std::allocator::pointer
- _A === std::allocator 对象
- _begin 是当前容器数据开头的类成员 ( [_begin,_end) )
- size() 返回 (_end - _begin)
- clear() 为 [_begin,_end) 和 _A.deallocate(_begin,size()) 中的所有元素调用 _A.destroy()
- 析构函数调用clear()
调整大小的来源(size_t):
void resize (size_type const & new_size)
{
if (new_size == 0U)
{ // we resize to zero, so we just remove all data
clear();
}
else if (new_size != size())
{ // we don't go to zero and don't remain the same size
size_type const old_size = size();
pointer new_mem(nullptr);
try
{
new_mem = _Allocate(new_size);
}
catch (std::bad_alloc e)
{
// [[[ 1 ]]]
}
size_type counter(0);
for (size_type i=0; i<new_size; ++i)
{
try
{
if (i<size())
_A.construct(new_mem + i, const_cast<const_reference>(*(_begin+i)));
// [[[ 2 ]]]
else
_A.construct(new_mem + i);
++counter;
}
catch (...) // [[[ 3 ]]]
{
// [[[ 4 ]]]
}
}
clear();
_begin = new_mem;
_end = _begin + new_size;
}
}
问题:
[[[ 1 ]]]
如果我没有在这里捕获,我应该调用clear()并在此处重新抛出,还是无论如何调用当前对象的析构函数?
[[[ 2 ]]]
这里使用 const_cast() 或 std::move() 转换为右值引用怎么样? 这会安全地打破异常吗?
如果我移动构造,假设 10 个元素中有 9 个,并且元素 10 在移动构造上抛出一些东西,我会丢失 10 个对象中的 9 个!?
[[[ 3 ]]]
我读到应该避免catch (...)
。尽管如此,我不知道是否还有其他可能。
有没有办法在不知道构造函数是否会向我抛出异常或抛出什么异常的情况下避免使用通用 catch?
[[[ 4 ]]]
我认为正确的步骤是:
- 通过调用 [new_memory, new_memory+counter) 范围内的析构函数来回滚已完成的构造
- 释放 new_mem
- 调用clear()
- 重新抛出
这是正确的吗?
最佳答案
您确实希望避免所有 try
/catch
内容并使用 RAII 来确保正确的资源清理。例如:
void resize (size_type const & new_size)
{
example<T> tmp(_A); // assuming I can construct with an allocator
// If the allocation throws then the exception can propogate without
// affecting the original contents of the container.
tmp._end = tmp._begin = tmp._A.allocate(new_size);
for (size_type i = 0; i < std::min(size(), new_size); ++i)
{
tmp._A.construct(tmp._begin + i, _begin[i]);
++tmp._end; // construction successful, increment _end so this
// object is destroyed if something throws later
}
for (size_type i = size(); i < new_size; ++i)
{
tmp._A.construct(tmp._begin + i);
++tmp._end; // as above
}
// OK, the copy of old objects and construction of new objects succeeded
// now take ownership of the new memory and give our old contents to the
// temporary container to be destroyed at the end of the function.
std::swap(_begin, tmp._begin);
std::swap(_end, tmp._end);
}
注释:
您说“
clear()
对[_begin,_end)
中的所有元素调用_A.destroy()
并且_A.deallocate(_begin,size())
”。为了简单起见,我假设deallocate并不真正关心size()参数,这对于某些分配器来说是正确的。如果这很重要,那么您可能希望example
有“容量”的概念和_capacity
或_end_of_storage
成员。将大小与容量分开将使清理工作更容易编写并且更加健壮。您已经在析构函数(和/或其调用的函数)中编写了正确的清理代码。通过使用临时容器,我可以重用该代码,而不必重复它。
通过使用本地对象,我可以避免所有
try
/catch
block ,并依靠本地对象的自动销毁来清理资源。
关于c++ - 如何安全地处理容器类中的异常/异常?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16577337/