如何跟踪每个函数提供的异常安全保证

4
在编写异常安全的代码时,需要考虑所有被调用函数的异常安全保证(无保证、基本保证、强保证或不抛出异常)。由于编译器没有提供帮助,我认为一个函数命名约定可能会有所帮助。是否存在任何已建立的符号标准,表明函数提供的异常安全保证级别?我想到了匈牙利式的命名约定:
void setFooB(Foo const& s); // B, offers basic guarantee
int computeSomethingS();    // S, offers strong guarantee
int getDataNT() throws();   // NT, offers no-throw
void allBetsAreOffN();      // N, offers no guarantee

编辑:我同意评论中对这种命名规范的不满,因此请允许我详细阐述我建议采用此规范的原因。

假设我重构了一些代码,并在此过程中更改了函数提供的异常安全级别。如果保证从强到基本发生变化(可能是由于速度提高所造成),那么调用重构后函数的每个函数都必须重新考虑其异常安全性。如果保证变化也触发了函数名称的变化,它将允许编译器在至少标记所有使用更改过的函数方面帮助我一点点。尽管这种命名规范存在问题,但这是我建议采用它的原因。这与const非常相似,其中函数的const性的变化会对其他调用函数产生连锁效应,但在那种情况下,编译器给出了非常有效的帮助。

所以我想问的是,人们开发了哪些工作习惯,以确保在代码维护和重构期间实际实现他们预期的异常保证。


4
请不要再推出另一个代码瑕疵管理系统了,我们已经够了吧? - Gene Bushuyev
不要这样做,正如Gene建议的那样。也许你可以把它们放在单独的命名空间中,并用一些模板或默认参数支持它们。最好将它们放在单独的头文件/命名空间中,并且好好记录文档。 - Ajay
1
每件事都应该至少提供基本的保证,这样您就不必担心任何问题。记录下提供更强保障的事物(通过注释或良好、有意义的名称);在我的经验中,这些并不常见。考虑使用throws()noexcept来明确无异常保证(但也要了解throws()的缺点)。请不要为此目的使用可怕的名称装饰。 - Alan Stokes
1
@Kerrek SB:异常保证不是关于停止异常,而是关于在抛出异常时你对一个对象做出的保证。大多数对象应该提供强壮保证(例如STL容器)(它要么工作正常,要么抛出异常,但不更改对象),但有时候你只需要基本保证(对象不会处于无效状态)。很少有方法需要无抛出保证(除了析构函数和swap()),你应该足够聪明,永远不要编写没有任何保证的代码。 - Martin York
或许楼主可以添加一个简短的段落来解释这个做法的原因。 - Kerrek SB
显示剩余8条评论
4个回答

5

我通常会在注释中记录这些内容(使用doxygen),除了无异常保证,如果且仅当确定函数保证不会抛出异常,并且异常安全性很重要时,我通常会使用throw() 异常说明进行标记。

也就是说,我通常更关注代码中未处理异常会导致问题的部分,并在本地处理它们(通过其他方式确保代码具有异常安全性,如RAII,在外部执行工作,然后通过无异常操作合并结果--即无异常交换,这是我唯一主动标记为throw()的函数)。

其他人可能有其他经验,但我发现这对我的日常工作已经足够。


3

我认为你不需要做任何特殊的处理。

我只会记录那些不会抛出异常的函数,因为编程语言的语法允许这样做。

在您的项目中,不应有不能提供任何保证的代码。因此,只剩下强/基本的内容需要记录。对于这两种保证,我认为您不需要明确地提到它们,因为它们与方法本身并不相关,而是涉及整个类(对于这两种保证)。它们提供的保证实际上取决于使用方式。

我希望我能在所有情况下都提供强大的保证(但实际上不是这样),有时候代价太大,有时候根本不值得努力(如果事情会抛出异常,它们将被销毁)。


1

我理解你想要做得好的意愿,但是我对这种命名约定并不确定。

总的来说,我对那些语言本身没有强制执行的命名约定持谨慎态度:它们很容易变成最大的骗子。

如果你真的需要这样的东西,我的建议是获取一个编译器(例如Clang)并添加一组新的属性。请注意,您需要编辑标准库提供的头文件以及所有依赖的第三方头文件,以注释它们,以便从底层开始获得这些保证。

然后,您可以让编译器检查这些注释(这也不是件容易的事...),然后这些注释就变得有用了,因为它们不能欺骗


1
我在考虑添加。
@par Exception Safety

Strong guarantee

在适当的情况下,请查看我的Javadocs。

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