2016-09-27 33 views
2

做这样的事情不好吗? (不与对象指针做操作之前draw()函数内部检查nullptr)我应该经常检查nullptr的成员指针吗?

class SomeClass 
{ 
public: 
    SomeClass(Object& someValidObject) 
    { 
     object = &someValidObject; 
    } 

    void draw() 
    { 
     object->draw(); 
    } 

    Object* object = nullptr; 
} 

或者我应该总是在对象调用操作之前检查nullptr,即使构造是肯定要作出指针指向什么?

void draw() 
{ 
    if(object != nullptr) 
     object->draw? 
} 
+1

“即使构造函数肯定会使指针指向某个东西?”如果我向你传递一个在堆栈上创建的对象,一旦它超出范围就会被销毁?是的,这是**非常糟糕 –

+0

只是使用智能指针并让它做脏活是不是更好? –

+0

@MateenUlhaq,当简单考虑SomeClass :: draw时,智能指针也可能无效。 –

回答

1

我想这是一个味道问题。但是I认为这是一种良好的做法,以便在代码(和代码)中表达期望的可维护性;因此,我宁愿写:

void draw() 
{ 
    if(object == nullptr) { /* throw some exception */ } 

    object->draw() 
} 

编辑

正如我回答的意见建议,通常是不够的,只在C检查指针++。事实上,一个指针可以是非空的但是无效的(这被称为悬挂指针)。在调用draw函数之前object被销毁时会发生这种情况。

+0

或者调整你的答案,解释为什么这是一种错误的做法。 –

4

取决于您试图防范什么。

正如已经指出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

0

即使构造可以肯定的是将会使指针指向的东西

这对新构造情况属实。但是这个保证持续了多久?考虑以下几点:

Object o; 
SomeClass s(o); 
s.object = nullptr 
s.draw(); // oops 

有人必须保证指针不为空。当然,您可以随时在draw进行检查。这样可行。在这种情况下,担保人本身就是draw。那么draw的前提条件是:object指向一个有效的对象,并满足Object::drawobject指向null的前提条件。

或者,您可以声明object != nullptr是一个要求(只需要object指向符合Object::draw的前提条件的有效对象)。在这种情况下,保证人是draw的来电者。那么你不需要检查null。

前者的优点是函数具有更容易被程序员错误侵犯的宽松前置条件。缺点是性能损失(在这种情况下可能是边际的)可能的冗余检查。

后者的优点是在调用者已经保证了object != nullptr的情况下的运行时性能优势。缺点是由程序员错误违反前提条件更容易。这可以通过使用一个断言来缓解,该断言允许在开发时捕获错误并在生产中优化性能。

后一种方法可以通过使object私密性得到显着改善。然后object != nullptr可以被认为是一个类不变量。如果不变性得到保证,则不需要运行时检查,只有类的实现者可能意外地违反了前提条件。使用断言和单元测试可以轻松捕获这种违规行为。


违反任何先决条件的在这两种方法的结果是不确定的行为。前提条件不能用C++表达,但只是文档的一部分。

由于SomeClassobject的使用期限不负责任,因此object必须指向有效对象的前提条件更加棘手。为了使保证更简单,考虑使用智能指针的可能性,或者完全避免间接寻址。

0

这一切都归结为合同的想法。如果你的指针可以合理地为空,你必须检查它。如果指针永远不能为空,不要检查它,你只是在浪费CPU周期。另外,如果指针为空,那么行为应该是什么。

存在使用断言的情况。它只出现在调试代码中,并传递给你的用户tgat契约是指针永远不应该为空。