假设我有一个管理资源的类。每个方法都有一个必须满足的前提:被管理资源必须处于有效状态(与unique_ptr
及其operator*
和operator->
相同),并且对象是否位于移出状态,则不满足该前提条件。对于我要访问移动对象的情况(无论出于何种原因意外或故意),都会引发以下问题:
答案 0 :(得分:0)
在C ++中,通常最好使用assert
头中的<cassert>
来确保满足某些条件,并且在发行版本中的开销为零。我认为在这里扔球并不是一个特别好的主意,但是尽管如此,它还是要权衡取舍。抛出使编写异常安全代码变得更加困难,并且使条件检查成为必需。我会说assert
,并记录下来必须满足这些先决条件。例如,std::vector::operator[]
在许多实现中都执行此操作,以检查调试模式下的出站访问。
我只想澄清一下,“移出”对象通常应具有有效状态。用户无法对其状态或值做任何假设,但他们应该能够重新分配或重用该对象。
答案 1 :(得分:0)
在每个方法开始时检查(断言)前提是否满足,这是一个好的设计吗?
如果可以对其进行静态声明,则可以。在运行期间考虑具有这样的代码的呼叫者
for (int i=0; i<100000;++i) {
foo.bar();
}
在条件不变的情况下,每次迭代都检查前提条件会浪费资源。
如果是的话,如果不是的话,解决这个问题的好设计到底是如何呢?也许抛出异常或什么? (我了解这是一个基于意见的问题)
这取决于您希望对呼叫者有多好。一种有效的选择是清楚地说明先决条件,如果呼叫者违反了这些先决条件,那么前提条件就是它们自己。比较一下std::vector::operator[]
:您想传递越界索引吗?没问题(从操作员的角度来看),未定义的行为就是您所得到的(从您的角度来看确实是个问题)。您不能保证在所有情况下都有明确的行为,尤其是在呼叫者不在乎您的前提条件的情况下。在某些情况下,您可能会引发异常,但这取决于细节。
答案 2 :(得分:0)
在每个方法开始时检查(断言)前提是否满足,这是一个好的设计吗?
就像编程中的大多数事情一样,这是一个折衷。什么才是好事取决于您是否更珍惜获得的收益。
您在交易中损失的是性能:不执行检查至少要快,并且通常比执行检查要快。这种效率损失是否显着取决于使用情况。在CPU限制算法的热路径内:可能是。在处理网络通信的代码中:可能不是。要找出差异是否显着,您需要进行测量。
如果不是这样,解决这个问题的好的设计到底有多好?
有几种方法可以在C ++中传达错误:
最简单的形式是中止程序。优点是不会出现未知的副作用,例如由于不确定的行为而导致的安全漏洞。当程序无法恢复时,这是一个不错的选择……但是作为类的实现者,您如何知道类的用户是否可以恢复?
标准assert
宏属于终止程序的类别,但是通常仅在测试中启用检查,而在发布模式下禁用检查,这可能会使程序容易受到攻击。
使用异常是一种方便的方法,可以让客户端代码选择是要从客户端代码中恢复(try
-catch
)还是让程序安全地终止。违反条件之前/之后通常是例外,在这种情况下,适当的例外是合适的。
使用传统的C样式错误代码。这将迫使客户端编写自己的错误处理代码,以避免潜在的用户负担。
每种方式各有优点和缺点。您应该根据需要选择。
答案 3 :(得分:0)
我还将使用类似断言的解决方案来检查该代码中的前提条件。但是,我将使用一些特定于编译器的魔术来优化可能的/不太可能的情况,而不是使用裸露的assert
,就像在Microsoft's GSL library中所做的那样。
您有GSL_LIKELY
,GSL_UNLIKELY
和GSL_ASSUME
宏以及Expects
,Ensures
构造。它们优于普通的assert
,因为基于它们,编译器可以针对可能/不太可能的分支执行更好的代码优化和配置文件汇编。
此外,非常酷的事情是,您可以调整GSL的配置,然后决定在失败的情况下如何处理:抛出,终止或什么都不做。这样,您可以轻松测试不同的行为,而无需修改整个代码库。您只需要更改一些编译定义即可。
还有另一件事:使用它们更为简洁。我认为Expects(m_data != nullptr)
比assert(m_data != nullptr)
更具可读性。