我应该总是检查nullptr成员指针吗?
Should I always check member pointers for nullptr?
这样做不好吗?(在对对象指针进行操作之前不检查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?
}
这取决于你想要保护什么
正如在评论中已经指出的那样,您无法可靠地检查悬空指针,因此如果传递的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
,但这意味着要支付运行时开销来检查基本上是编程错误。
如果我不想在生产构建中支付这些开销怎么办?如果我在开发过程中犯了错误,也许我希望我的调试器能够捕获它,但我不希望我的客户在发布后支付检查费用。
所以你真正要找的是一个assert:
void draw()
{
assert(object);
object->draw();
}
决定是在断言中检查某些内容(或根本不检查),还是在契约中进行适当的运行时检查,这不是一个容易的决定。这往往是一个哲学问题。如果你想深入了解的话,John Lakos在CppCon 2014上做了一个很好的演讲。
我想这是个人喜好的问题。但是我认为为了可维护性,在代码内部(和作为代码)表达期望是一个很好的实践;因此,我宁愿这样写:
void draw()
{
if(object == nullptr) { /* throw some exception */ }
object->draw()
}
编辑
正如我在回答的评论中建议的那样,在c++中只检查null指针通常是不够的。实际上,指针可以是非空但无效(这被称为悬空指针)。当object
在draw
函数被调用之前被销毁时,就会发生这种情况。
,尽管构造函数肯定会让指针指向
对于新构造的实例来说是正确的。但是这个保证能持续多久呢?考虑:
Object o;
SomeClass s(o);
s.object = nullptr
s.draw(); // oops
某人必须保证指针不为空。当然,您可以在draw
中进行检查。这工作。在这种情况下,担保人是draw
本身。则draw
的前提条件为:object
指向有效对象,且满足Object::draw
或object
指向null的前提条件。
或者,您可以声明object != nullptr
是一个要求(只需要求object
指向一个满足Object::draw
先决条件的有效对象)。在这种情况下,保证者是draw
的调用者。那么你不需要检查空
前者的优点是函数有更宽松的前提条件,程序员的错误更难违反。缺点是可能存在冗余检查的性能损失(在这种情况下可能是微不足道的)。
后者的优点是在调用者已经保证了object != nullptr
的情况下的运行时性能优势。缺点是程序员的错误更容易违反前提条件。这可以通过使用一个断言来减轻,该断言允许在开发时捕获错误并在生产中获得最佳性能。
后一种选择可以通过将object
设置为私有来显著改进。那么object != nullptr
可以被认为是一个类不变量。如果不变性得到保证,则不需要运行时检查,并且只有类的实现者可能意外地违反前提条件。使用断言和单元测试可以很容易地发现这种冲突。
在任何一种方法中违反任何前提条件的结果都是未定义行为。前提条件不能在c++中表示,但它们只是文档的一部分。
object
必须指向一个有效对象的前提条件很难保证,因为SomeClass
不负责object
的生命周期。为了使保证更简单,考虑使用智能指针的可能性,或者完全避免间接。
这一切都归结为合同的概念。如果你的指针可以合理地为空,你必须检查它。如果指针不应该为空,不要检查它,你只是在浪费cpu周期。此外,如果指针为空,该如何处理呢?
有一个使用断言的情况。它只出现在调试代码中,并向您的用户传达合约是指针永远不应该为空。
- 如果基类包含双指针成员,则派生类的构造函数
- C++正确的指针成员初始化
- 是否可以使用智能指针成员设置具有另一个结构的结构?
- 为什么 operator() 处的指针成员不起作用?
- 更改队列指针成员的值需要在 C++ 中出现奇怪的错误
- 如何从另一个嵌套类中调用某个封闭类的嵌套类的函数指针成员的值?
- 结构对象的指针成员在传递给函数时被修改
- 参数的混合值,当我调用指针成员函数时
- 如何正确使用结构的共享指针成员?
- C++:私有类指针成员返回未定义的值
- 在函数中传递带有指针成员的结构是浅拷贝或深拷贝在 C 中
- 包含指针成员的嵌套结构
- 在 c++ 中,为什么 -> 被称为二进制中缀指针成员访问运算符?
- C++ 类指针成员行为奇怪(错误)
- 常量结构的指针成员
- 在类C++指针成员中
- 指针成员未在复制构造函数中初始化
- C++移动拥有指针成员的构造函数
- C++ 包含唯一指针成员变量的类的赋值运算符
- 如何使用 QPoint 指针成员对类进行排队和取消排队