做这样的事情不好吗? (在使用对象指针进行操作之前,不检查draw()函数中的nullptr)
class SomeClass
{
public:
SomeClass(Object& someValidObject)
{
object = &someValidObject;
}
void draw()
{
object->draw();
}
Object* object = nullptr;
}
或者我应该在调用对象中的操作之前检查nullptr,即使构造函数肯定会使指针指向某个东西吗?
void draw()
{
if(object != nullptr)
object->draw?
}
答案 0 :(得分:4)
取决于您要保护的内容。
正如已经指出的那样in the comments,你无法可靠地检查悬空指针,所以如果传递的Object
超出你SomeClass
之前的范围,就会发生不好的事情。
所以你唯一能够可靠检查的是指针是否为nullptr
,但正如你已经注意到的那样,目前构造函数使得它几乎不可能。但是,只要您的object
成员变量现在是public
,用户就可以在那里到达并将其设置为nullptr
。您可以通过创建成员private
并使用拒绝nullptr
值的setter(或简单地使用引用,如构造函数)来更难。在这样的设计中,object != nullptr
可以被认为是类不变,这是在每个成员函数调用之前和之后(从构造之后到破坏之前)为真的条件。 / p>
那么我们如何打破一个类不变量呢?作为客户端,我们可能会违反函数的前提条件,从而使类处于未定义状态。使用像示例一样简单的类,这很难做到,因为函数实际上没有前提条件。但是,为了论证,我们假设您要添加这样的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
,但我们无法检查悬空指针。有时检查是可能的,但由于高运行时间成本(例如,检查范围是否为二进制搜索排序)是不可取的。一旦我们意识到这一点,很明显我们作为班级设计师在这里有一些摆动空间。既然我们无法做到100%防弹,我们应该检查一下吗?当然,我们可以在任何地方检查nullptr
,但这意味着支付运行时开销来检查基本上是编程错误。
如果我不想在生产版本中支付这笔费用,该怎么办?如果我在开发过程中犯了错误,也许我希望我的调试器能够抓住它,但我不希望我的客户在发布后不得不支付支票。
所以你真正想要的是一个断言:
void draw()
{
assert(object);
object->draw();
}
决定是否在断言中检查某些内容(或根本不检查)或进行适当的运行时检查是合同的一部分并非易事。它往往不是一个哲学问题。如果你想深入挖掘,约翰拉科斯给了一个很好的talk about this at CppCon 2014。
答案 1 :(得分:1)
我想这是一个品味问题。但是我认为为了可维护性而在代码中表达期望(以及代码)是一种好的做法;因此我宁愿写:
void draw()
{
if(object == nullptr) { /* throw some exception */ }
object->draw()
}
修改强>
正如我的回答的评论中所建议的那样,通常仅仅在C ++中检查 null 指针是不够的。实际上,指针可以是非null但是无效(这称为悬空指针)。在调用object
函数之前销毁draw
时会发生这种情况。
答案 2 :(得分:0)
即使构造函数肯定会使指针指向
对于新构建的实例来说,这是真的。但这保证持续多久?请考虑以下事项:
Object o;
SomeClass s(o);
s.object = nullptr
s.draw(); // oops
某人必须保证指针不为空。当然,您可以随时查看draw
。这样可行。在这种情况下,保证者本身是draw
。然后draw
的先决条件是:object
指向有效对象,并满足Object::draw
或object
点为空的前提条件。
或者,您可以声明object != nullptr
是必需的(只需要object
指向满足Object::draw
前提条件的有效对象)。在这种情况下,保证者是draw
的来电者。然后你不需要检查空值。
前者的优点是函数具有更宽松的前提条件,这些前提条件更难以被程序员错误所侵犯。缺点是可能冗余检查的性能损失(在这种情况下可能是微不足道的)。
后者的优点是在调用者已经保证object != nullptr
的情况下运行时性能优势。缺点是程序员错误违反前提条件更容易。这可以通过使用一个断言来减轻,该断言允许在开发中捕获错误并在生产中实现最佳性能。
通过将object
设为私有,可以显着改善后一种选择。然后object != nullptr
可以被视为类不变量。如果保证不变性,则不需要运行时检查,只有类的实现者可能会意外违反前提条件。使用断言和单元测试可以很容易地捕获到这种违规行为。
违反任何一种方法中的任何先决条件的结果是未定义的行为。前提条件不能用C ++表示,只是文档的一部分。
object
必须指向有效对象的前提条件更加难以保证,因为SomeClass
不对object
的生命周期负责。为了使保证更简单,请考虑使用智能指针的可能性,或完全避免间接。
答案 3 :(得分:0)
这一切都归结为合同的概念。如果你的指针可以合理地为空,你必须检查它。如果指针永远不应为null,请不要检查它,只是浪费cpu周期。此外,如果指针为空,行为应该是什么。
有使用断言的情况。它只出现在调试代码中并传达给你的用户tgat合同是指针永远不应为null。