问题描述
做这样的事情不好吗? (在使用对象指针进行操作之前,不检查draw()函数中的nullptr)
Is it bad to do something like this? (not checking for nullptr inside the draw() function before doing operations with the object pointer)
class SomeClass
{
public:
SomeClass(Object& someValidObject)
{
object = &someValidObject;
}
void draw()
{
object->draw();
}
Object* object = nullptr;
}
或者我应该在调用对象中的操作之前始终检查nullptr,即使
Or should I always check for nullptr before calling operations in the object, even though the constructor is for sure going to make the pointer point to something?
void draw()
{
if(object != nullptr)
object->draw?
}
推荐答案
取决于您要尝试的操作
正如指出的那样,您无法可靠地检查悬空指针,因此如果传递的 Object
在您的 SomeClass
,会发生坏事。
As was pointed out in the comments already, you cannot reliably check for dangling pointers, so if the passed Object
goes out of scope before your SomeClass
, bad things will happen.
因此,您唯一可以可靠检查的是指针是否为 nullptr
,但是正如您已经注意到的那样,当前构造函数使这几乎不可能。但是,只要您的 object
成员变量像现在一样是 public
,理论上用户就可以到达并将其设置为 nullptr
。您可以通过将成员 private
设置为私有,并使用拒绝 nullptr
值的setter(或简单地使用引用)来增加难度,就像构造函数一样)。在这样的设计中, object!= nullptr
可以被认为是类不变的,这是在每次调用成员函数之前和之后都成立的条件(从构造后到破坏之前的时间)。
So the only thing that you can reliably check is whether the pointer is nullptr
, but as you have noticed yourself, currently the constructor makes that next to impossible. However, as long as your object
member variable is public
like it is now, the user could in theory reach in there and set it to nullptr
. You can make that harder by making the member private
and use a setter which rejects nullptr
values (or simply takes a reference, like the constructor). In such a design, object != nullptr
can be considered a class invariant, that is a condition that is true before and after every member function call (from the time after construction until before destruction).
那么我们如何打破类不变式?作为客户,我们可能会违反函数的前提条件,从而使类进入未定义状态。对于像样本一样简单的类,很难做到这一点,因为这些函数实际上没有先决条件。但是让我们假设出于参数考虑,您将添加一个这样的setter:
So how do we break a class invariant? As a client, we could violate a function's preconditions and thus bring the class into an undefined state. With a class as simple as your sample, this is hard to do, as the functions do not really have preconditions. But let's assume for arguments sake, you were to add a setter like this:
// 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
之前死亡,则违反了该类的合同。
Now this is somewhat subtle. The code allows us to pass a nullptr
here, but the documentation explicitly forbids it. If we pass a nullptr
or have the passed object die before our SomeClass
, we violate the contract of that class. But this contract is not enforced in code, it's only in the comments.
在这里要意识到的重要一点是,有些条件无法在代码中检查。我们可以检查 nullptr
,但不能检查悬挂的指针。有时检查是可能的,但是由于高昂的运行时间成本而不希望进行检查(例如,检查是否已对二进制搜索的范围进行了排序)。一旦意识到这一点,很明显,我们作为班级设计师在这里有一些回旋余地。由于我们仍然无法使事物100%防弹,因此我们是否应该进行任何检查?当然,我们可以在任何地方检查 nullptr
,但这意味着要付出运行时的开销来检查基本上是编程错误的东西。
The important thing to realize here is that there are conditions that cannot be checked in code. We can check for nullptr
, but we cannot check for dangling pointers. Sometimes checks would be possible, but are undesirable due to high runtime costs (eg. check that a range is sorted for a binary search). Once we realize this it becomes clear that we have some wiggle room here as a class designer. Since we anyway cannot make things 100% bullet-proof, should we check anything at all? Sure, we can check for nullptr
everywhere, but that means paying the runtime overhead for checking what is basically a programming error.
如果我不想在生产版本中支付这些开销怎么办?如果我在开发过程中犯了一个错误,也许我想让调试器抓住它,但是我不希望我的客户在发行后需要支付支票。
What if I don't want to pay this overhead in production builds? Maybe I would like my debugger to catch it if I make a mistake during development, but I don't want my clients to have to pay for the check later after release.
所以您真正想要的是一个断言:
So what you are really looking for is an assert:
void draw()
{
assert(object);
object->draw();
}
决定是否检查断言中的内容(或根本不检查)或对合同进行适当的运行时检查并非易事。这通常不是哲学问题。如果您想深入了解,约翰·拉科斯(John Lakos)在2014年CppCon上发表了出色的。
The decision whether to check something in an assert (or not at all) or have a proper runtime check that is part of the contract is not an easy one. It is more often than not a philosophical question. John Lakos gave an excellent talk about this at CppCon 2014 if you want to dig deeper.
这篇关于我应该始终检查成员指针是否为nullptr吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!