我只是一个刚起步的程序员,至少比最佳情况下尝试编写更多程序。到目前为止,我一直在阅读Herb Sutter的“Exceptional C++”,并且在异常安全性章节中浏览了三次。但是,除非他提出的示例(堆栈),否则我不确定如何准确地争取异常安全性与速度,以及何时做到这一点完全是愚蠢的。

例如,我当前的家庭作业项目是一个双向链接列表。由于我已经编写了其中的一些程序,因此我想花一些时间来了解一些更深层次的概念,例如ES。

这是我的弹出式功能:

void List::pop_front()
{
    if(!head_)
        throw std::length_error("Pop front:  List is empty.\n");
    else
    {
        ListElem *temp = head_;
        head_          = head_->next;
        head_->prev    = 0;
        delete temp;
        --size_;
    }
}

我对此有些困惑。

1)当列表失败时,我真的应该抛出一个错误吗?我不应该只是不做任何事然后返回,而不是强制列表的用户执行try {] catch(){}语句(这也很慢)。

2)有多个错误类(加上我的老师要求我们在该类中实现的ListException)。这样的事情真的需要自定义错误类吗?是否有关于何时使用特定异常类的一般指南? (例如,范围,长度和边界都听起来一样)

3)我知道在所有引发异常的代码之前,我不应该更改程序状态。这就是为什么我最后减小size_的原因。在这个简单的示例中,这真的必要吗?我知道删除不能抛出。分配给0时head _-> prev是否有可能抛出? (头是第一个节点)

我的push_back函数:
void List::push_back(const T& data)
{
    if(!tail_)
    {
        tail_ = new ListElem(data, 0, 0);
        head_ = tail_;
    }
    else
    {
    tail_->next = new ListElem(data, 0, tail_);
    tail_ = tail_->next;
    }
    ++size_;
}

1)我经常听到在C++程序中任何事情都会失败。测试ListElem的构造函数是否失败(或new期间为tail_)是否可行?

2)是否有必要测试数据的类型(在我将所有模板化之前,当前为一个简单的typedef int T),以确保该类型对于该结构是可行的?

我意识到这些都是过于简单的示例,但是目前我对何时应该实际练习良好的ES以及何时不应该练习它感到困惑。

最佳答案



绝对抛出异常。

如果列表为空,用户必须知道发生了什么,否则调试起来会很麻烦。用户是,而不是,被迫使用try/catch语句;如果异常是意外的(即只能由于程序员错误而发生),则没有理由 try catch 它。当未捕获到异常时,它将陷入std::terminate和,这是非常有用的行为。无论如何,try/catch语句本身并不慢。实际抛出异常和释放堆栈的代价是什么。如果不引发异常,则几乎不花费任何费用。



尽可能具体。使用自己的错误类是执行此操作的最佳方法。使用继承对相关异常进行分组(以便调用者可以更轻松地捕获它们)。



如果head_为null,则取消引用(作为分配给head_->prev的尝试的一部分)是未定义行为。引发异常是未定义行为的可能结果,但是却是一个不太可能的事件(它要求编译器以一种被认为是荒谬的语言来摆脱握住您的手;)),而不是一个我们担心的是,因为未定义的行为是未定义的行为-这意味着您的程序无论如何已经是错误的,并且试图使错误的方式变得更正确没有任何意义。

另外,您已经在明确检查head_是否不为null。因此,假设您没有对线程做任何事情,这没有问题。



有点偏执。 :)



如果new失败,则抛出std::bad_alloc实例。抛出异常正是您想要在此处发生的事情,因此您不需要或不需要做任何事情-只是让它传播即可。将错误重新描述为某种列表异常并没有真正添加有用的信息,并且可能只会使事情变得更模糊。

如果构造函数ListElem失败,则应该通过抛出异常来使失败,并且也应该让它通过约999到1。

这里的关键是,每当引发异常时,就不会进行
清理工作,因为您尚未修改列表以及构造/新建的对象Officially Never Existed(TM),因此没有清理工作。只要确保及其构造函数是异常安全的,就可以了。如果new调用未能分配内存,则甚至不会调用构造函数。

您需要担心的时间是当您在同一位置
中为分配多个分配时。在这种情况下,必须确保如果第二个分配失败,则捕获异常(无论它是什么),清除第一个分配,然后重新抛出。否则,您将泄漏第一个分配。



在编译时检查类型。您实际上无法在运行时对它们做任何事情,也永远不会现实需要。 (如果您不希望进行所有类型检查,那么为什么要使用一种语言来强制您仅在整个地方键入类型名称呢?:))

关于c++ - 异常安全-何时,如何,为什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/4482788/

10-14 17:14
查看更多