空指针和检查指针是否指向有效对象

56

在我以前的一些代码项目中,当我还没有听说过智能指针时,每当我需要检查指针是否仍指向有效对象时,我总是会做类似于这样的事情...

object * meh = new object;
if(meh) 
    meh->member;

或者当我需要安全地删除对象时,可以使用类似下面的方法:

if(meh)
{
    delete meh;
    meh = 0;
}

我现在已经通过“硬教训”了解了在布尔表达式中使用对象和指针的问题。同时,我也了解到了不那么新但相当不错的C++功能——nullptr关键字。但现在我很好奇。

我已经检查并修正了我的大部分代码,例如删除对象时我现在会这样写:

if(meh)
{
    delete meh;
    meh = nullptr;
}

现在我想知道布尔值。当你像这样将一个整数传递到if语句中时,

int meh;
if(meh)

那么它会在没有你编写代码的情况下隐式地检查是否为零。

if(meh == 0) // does the exact same check

现在,C++会对指针做同样的事情吗?如果像这样将char*传递给if语句呢?

char * meh;
if(meh)

那么它会隐式地将其与nullptr进行比较吗?由于我已经这样写这些if语句很长时间了,检查指针在使用之前是否有效已经成为一种习惯。如果这不是功能的实现方式,为什么不是呢?太难实现了吗?通过消除你可能犯错的另一种微小方式,可以解决一些问题。


35
在使用delete时不需要检查指针。删除nullptr是完全安全的。 - n. m.
1
在你的最后一个例子中,你是不是想说 char * meh = nullptr; if(meh)?这个指针没有初始化。 - Jesse Good
4
您使用 new 表达式的结果永远不会为空,而是会使用异常。同时,删除空指针是可以的,它不会产生任何作用。此外,通常最好不要将指针的值重置为 null。最后一次使用它应该是它不为 null 的最后一次,因此访问已经被删除的指针应被视为一个 bug;将其设置为 null 就隐藏了这个问题。 - GManNickG
3
“我总是在使指针无效后将其设置为零,这样我就知道非零的指针是有效的。”这是一个反模式。如果你有两个指向同一对象的指针会发生什么?将其中一个设置为零不会影响另一个指针。 - David Schwartz
1
@FatalCatharsis:那样做是可以的。但是如果其他代码删除了指针的另一个副本,然后对其进行引用,同样的运行时错误将会发生。因此,清零指针既不是必要的也不足够。如果你在不必要的地方使用它,那么你只是效率低下。但是如果你在不足够的地方使用它...砰!(当然,这并不意味着你永远不应该这样做。但它很少是正确的工具,并且在许多地方被错误地使用。这就是它成为反模式的原因。) - David Schwartz
显示剩余5条评论
3个回答

60
在C语言中,任何不为0的值都是真的。因此,你肯定可以使用:
if (ptrToObject) 
    ptrToObject->doSomething();

安全取消引用指针。

C++11有些变化,nullptr_t是一种类型,nullptr是它的一个实例;nullptr_t的表示是特定于实现的。因此,编译器可以按其所需定义nullptr_t。它只需要确保可以强制执行对不同类型的nullptr_t转换的适当限制--其中允许布尔值--并确保可以区分nullptr_t和0。

因此,只要编译器遵循C++11语言规范,nullptr将被正确且隐式地转换为布尔值false,上面的代码片段仍然有效。

如果删除引用对象,则不会发生任何更改。

delete ptrToObject;
assert(ptrToObject);
ptrToObject = nullptr;
assert(!ptrToObject);    

因为我已经写了这么长时间的if语句,所以在使用指针之前检查其是否有效已经成为我的本能反应。我会打出 if (object *) 并调用其成员。 不行。 请维护一个适当的对象图(最好使用唯一/智能指针)。正如指出的那样,无法确定指向非 nullptr 的指针是否指向有效的对象。责任始终在于您来维护生命周期...这就是指针包装器首先存在的原因。
事实上,由于 sharedweak 指针的生命周期是明确定义的,它们具有语法糖,可以让您像使用裸指针一样使用它们,其中有效指针具有值,而所有其他指针都为 nullptr共享指针
#include <iostream>
#include <memory>

void report(std::shared_ptr<int> ptr) 
{
    if (ptr) {
        std::cout << "*ptr=" << *ptr << "\n";
    } else {
        std::cout << "ptr is not a valid pointer.\n";
    }
}

int main()
{
    std::shared_ptr<int> ptr;
    report(ptr);

    ptr = std::make_shared<int>(7);
    report(ptr);
}

#include <iostream>
#include <memory>

void observe(std::weak_ptr<int> weak) 
{
    if (auto observe = weak.lock()) {
        std::cout << "\tobserve() able to lock weak_ptr<>, value=" << *observe << "\n";
    } else {
        std::cout << "\tobserve() unable to lock weak_ptr<>\n";
    }
}

int main()
{
    std::weak_ptr<int> weak;
    std::cout << "weak_ptr<> not yet initialized\n";
    observe(weak);

    {
        auto shared = std::make_shared<int>(42);
        weak = shared;
        std::cout << "weak_ptr<> initialized with shared_ptr.\n";
        observe(weak);
    }

    std::cout << "shared_ptr<> has been destructed due to scope exit.\n";
    observe(weak);
}

现在,C ++会对指针执行相同的操作吗?如果像这样将char *传递给if语句呢?
因此,回答问题:对于裸指针来说,否。对于包装指针,是的。
封装您的指针,朋友们。

1
如果您忘记将指针清零,那么它可能会导致运行时错误。但是,如果隐式地将指针与nullptr进行比较,则无论指针是否为0或寻址某些内容,当对象不存在时,它都会解析为false。 - FatalCatharsis
2
是的。那么你有什么问题? - dcow
3
在C和C++中,空指针与整数零比较相等,非空指针与整数零比较不等--无论指针的位表示如何。 - ephemient
1
他只是解释了我的答案,否则他会发表自己的。 - dcow
1
我认为你对于永远不要使用 if(p) 的警告过于强烈了。例如,这就是使用 std::weak_ptr 的方式 - 将其转换为 std::shared_ptr 并测试它以确定对象是否仍然存在。 - Mark Ransom
显示剩余11条评论

15

无法测试指针是否指向有效对象。如果指针不为空但未指向有效对象,则使用指针会导致未定义的行为。为避免此类错误,你需要注意被指向对象的生存期;而智能指针类可帮助完成这个任务。

如果 meh 是一个裸指针,则 if (meh)if (meh != 0)if (meh != nullptr) 没有任何区别。只要指针不为 null,它们都会执行。

从字面值 0nullptr 存在隐式转换。


好的,有方法可以做到。我见过嵌入self指针并检查self == this,并在销毁时将self清零的代码。可能会浪费时间和空间。 - user207421
@user207421,也许您可以发表一篇带有您的想法(和具体代码)的答案,我觉得您正在描述不同的东西。 - M.M

0
"

在编程中,“将指针设置为零后使其无效,以便知道非零指针是有效的”是一种反模式。如果您有两个指向同一对象的指针会发生什么?将其中一个设置为零并不会更好,也不影响另一个。

"

是的,这样做通常是毫无意义的,但我不确定这是否回答了问题(你可能会因此得到一些负评)。 - golvok

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接