取决于您试图防范什么。
正如已经指出in the comments,你不能可靠地检查悬挂指针,所以如果通过Object
超出你的SomeClass
之前的范围,就会发生坏事。
所以你唯一能够可靠检查的是指针是否是nullptr
,但正如你已经注意到自己,目前的构造函数使得它几乎不可能。但是,只要您的object
成员变量为public
就像现在这样,用户理论上可以到达并将其设置为nullptr
。您可以通过使成员private
更难以使用,并使用拒绝nullptr
值的setter(或仅仅需要引用,如构造函数)。在这样的设计中,object != nullptr
可以被认为是一个类不变量,这是在每个成员函数调用之前和之后(从构建之后到销毁之前)都是真的条件。
那么我们如何破坏一个类的不变性?作为客户端,我们可能会违反函数的前提条件,从而导致类未定义状态。随着你的样本一样简单,这很难做到,因为这些功能并没有真正的先决条件。但让我们假设为了争辩,你要添加一个像这样的setter:
// Precondition: obj must point to a valid Object.
// That object must be kept alive for the lifetime of the class,
// otherwise the behavior is undefined.
void setObject(Object* obj)
{
object = obj;
}
现在,这是有点微妙。代码允许我们在这里传递nullptr
,但文档明确禁止它。如果我们通过nullptr
或者让我们的SomeClass
之前的物品死亡,我们违反了该级别的合同。但是这个合同没有在代码中强制执行,只是在评论中。
这里要实现的重要一点是,有些条件无法在代码中检查。我们可以检查nullptr
,但我们无法检查悬挂指针。有时候检查是可能的,但由于高运行成本而不受欢迎(例如检查一个范围是否为二分搜索进行排序)。一旦我们意识到这一点,就很清楚,我们在这里有一些摆动空间作为班级设计师。既然我们无法让事情百分百防弹,我们是否应该检查一切?当然,我们可以在任何地方检查nullptr
,但这意味着要花费运行时开销来检查基本上是编程错误的东西。
如果我不想在生产版本中支付此开销,该怎么办?如果我在开发过程中犯了一个错误,也许我会希望我的调试器能够捕获它,但是我不希望我的客户在发布之后稍后再付款。
那么,你真正需要的是一个断言:
void draw()
{
assert(object);
object->draw();
}
是否在断言检查一番(或者根本没有)或者有一个适当的运行时检查属于合同的一部分,是决定不是一件容易的事。这往往不是一个哲学问题。如果你想深入挖掘,约翰拉科斯给出了优秀的talk about this at CppCon 2014。
“即使构造函数肯定会使指针指向某个东西?”如果我向你传递一个在堆栈上创建的对象,一旦它超出范围就会被销毁?是的,这是**非常糟糕 –
只是使用智能指针并让它做脏活是不是更好? –
@MateenUlhaq,当简单考虑SomeClass :: draw时,智能指针也可能无效。 –