不确定何时在C++中使用异常

7

在使用C++编写科学软件多年后,我仍然无法适应异常处理,也不知道何时应该使用它们。我知道在控制程序流方面使用它们是大忌,但除此之外...考虑以下示例(摘自表示图像掩模并允许用户将区域添加为多边形的类):

class ImageMask
{
public:
    ImageMask() {}
    ImageMask(const Size2DI &imgSize);

    void addPolygon(const PolygonI &polygon);

protected:
    Size2DI imgSize_;
    std::vector<PolygonI> polygons_;
};

该类的默认构造函数会创建一个无用的实例,图像大小未定义。我不希望用户能够向这样的对象添加多边形。但我不确定该如何处理这种情况。当大小未定义并且调用addPolygon()时,我应该:
  1. 默默返回
  2. 断言(imgSize_.valid)以检测使用该类的代码中的违规,并在发布之前修复它们
  3. 抛出异常?
大多数情况下,我选择1)或2)(取决于我的心情),因为对于这样一个简单的场景来说,异常似乎是昂贵、混乱而过度杀伤力的。请给一些见解?

4
阻止默认构造函数?防止创建无效的“ImageMask”实例。 - hmjd
4
如果程序中存在需要修复的漏洞,你应该抛出异常;即使异常很耗费资源也无所谓。不要默默返回,因为这会隐藏错误;也不要使用“assert”,因为这些在发布模式编译中会被编译掉。此外,由于"imgSize_.valid"是实现细节,所以类的客户端不需要知道它是什么。 - Seth Carnegie
3
@MadScientist:C++11中没有STL容器需要你的类型是DefaultConstructible的。 - Andrzej
1
当你说异常处理是“代价高、混乱且过度”的时候,你确定它们比你提出的其他错误处理机制更加昂贵和混乱吗? - Kristopher Johnson
1
@Andrzej “发布模式”通常指的是高度优化的组合,同时减少(或没有)调试信息,并禁用断言。 - Seth Carnegie
显示剩余11条评论
4个回答

6
一般规则是当无法执行所需操作时抛出异常。因此,在您的情况下,当调用addPolygon并且大小未定义或不一致时,抛出异常是有意义的。
默默返回几乎总是错误的做法。assert不是一个好的错误处理技术(它更像是一种设计/文档技术)。
然而,在您的情况下,重新设计接口以使错误条件不可能或不太可能发生可能会更好。例如,像这样的东西:
class ImageMask
{
public:
    // Constructor requires collection of polygons and size.
    // Neither can be changed after construction.
    ImageMask(std::vector<PolygonI>& polygons, size_t size);
}

or like this

class ImageMask
{
public:
    class Builder
    {
    public:
        Builder();
        void addPolygon();
    };

    ImageMask(const Builder& builder);
}

// used like this
ImageMask::Builder builder;
builder.addPolygon(polyA);
builder.addPolygon(polyB);
ImageMask mask(builder);

2

我会尽量避免出现可能创建无用数据的情况。如果您需要一个不为空的多边形,那么不要创建空的多边形,这样可以减少很多麻烦,因为编译器将强制执行不创建空的多边形。

我从不使用静默返回,因为它们会隐藏错误,这使得查找错误比必要的要复杂得多。

当我检测到程序处于一种只有通过软件中的bug才能达到的状态时,我使用asserts。在您的示例中,如果您在接受Size2DI的构造函数中检查了此大小不为空,则断言存储的大小不为空,有助于检测错误。Asserts 不应该具有副作用,并且必须可以删除它们而不更改软件的行为。我发现它们非常实用,可以帮助我找到自己的错误并记录对象/函数等的当前状态。

如果运行时错误很可能直接由函数调用者处理,则我会使用传统的返回值。如果很可能必须在调用堆栈上的多个函数调用之间传递此错误状态,则我更喜欢异常。如有疑虑,我会提供两个函数。

敬礼
Torsten


1
对我而言,1 不是一个选项。是否选择(并记录)默认构建图像掩模,然后添加多边形作为您的组件的有效或无效用法,取决于程序/库的设计。这是一个重要的设计决策。我建议阅读马修·威尔逊(Matthew Wilson)的this article
请注意,您还有更多选项:
  • 发明自己的 assert,始终调用 std::terminate 并进行其他日志记录
  • 禁用默认构造函数(如其他人已经指出的那样)- 这是我最喜欢的方式

1
  1. "默默返回" - 这是真正的“大忌”。程序应该知道出了什么问题。
  2. "断言" - 第二个规则是,只有在无法恢复正常程序流程时才使用断言。
  3. "抛出异常" - 是的,这是正确和良好的技术。只要注意异常安全性即可。关于异常安全编码,GotW上有很多文章。

不要害怕异常。它们不会咬人。 :) 如果你足够掌握这项技术,你将成为一名强大的程序员。 ;)


有趣的是,在Java中异常如此容易和正常地使用,而在C++中它们却被如此误解。 - marcinj
不用惊讶。Java会自动管理内存;而C++开发者可以(幸运或不幸地)通过自己的大脑来管理内存。 ;) - Alex Medveshchek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接