语言确实很重要,因为某些语言可能有做你想做的事的方法,这取决于它们如何定义和实现对象和对象生命周期等思想。事实上,许多语言都无法或几乎不可能拥有类似于悬空指针或悬空引用的东西。但是这个问题被标记为 C++ 并且有一个 C++ 示例,而 C++ 肯定没有办法在 delete 表达式之后进行这种检查。
为什么不呢?因为 C++ 语言及其典型编译器的核心原则之一是不用为不用的东西付费。在几乎所有的计算机体系结构上,C++ 原始指针都可以以一种非常有效地获取内存的方式实现,但不提供任何方式来保证或检查内存实际上仍然有效并且没有被重用于完全不同的东西。编译器可以自动添加额外的簿记,以便检查 C++ 指针对象是否有效,但这意味着实际程序执行不可避免的额外工作会显着减慢所有程序,即使在部分不是使用这些检查。因此,C++ 可以通过这种方式快速编写代码,但存在要求程序正确使用指针的危险。
另一方面,C++ 确实使实现各种包装器和簿记变得相当容易,这些包装器和簿记确实提供了您想要的检查等额外功能,并且仍然使用与熟悉的原始指针语法非常相似的语法。更好的是,标准库中为您提供了这些包装器的一些最有用的模式。我们可能会重写您的示例代码的几种方法,以及实际尝试使用 *b 的语句:
-
使用std::unique_ptr
std::unique_ptr<int> a = std::make_unique<int>(2);
std::unique_ptr<int> b = a;
a = nullptr;
std::cout << *b;
结果:程序无法编译!
从a 初始化b 是非法的,因为unique_ptr 中的“唯一”意味着编译器将帮助您确保一次只有一个指向所拥有对象的指针。不适用于您询问的情况,但实际上需要动态创建的对象但每个对象只有一个指针是很常见的,这也控制着对象的生命周期。在这种情况下,unique_ptr 的一个好处是它有助于避免意外创建其他指针,这些指针可能会在以后悬空。
关于它的另一个好处是您不需要任何等效的delete。如果unique_ptr 的生命周期结束(例如,到达{block} 的末尾,销毁具有unique_ptr 作为成员的对象,或从容器中删除unique_ptr),它会为你做delete。同样,如果您将unique_ptr 重新分配为指向其他对象,它将delete 之前指向的任何对象。所以a = nullptr; 行是一种强制指针立即清理并忘记其对象的方法,但通常不是必需的。所有这些都有助于避免泄漏、双重删除和悬空指针。
顺便说一句,由于unique_ptr 只是在编译时检查以避免复制和在明确定义的点自动清理,因此使用它的程序通常可以编译成与使用原始程序一样快的可执行文件指针,new 和 delete 直接。只是减少了程序员的工作量和危险。
-
使用std::shared_ptr
std::shared_ptr<int> a = std::make_shared<int>(2);
std::shared_ptr<int> b = a;
a = nullptr;
std::cout << *b;
结果:打印 2。
shared_ptr 为指针添加了簿记功能,以便它始终知道存在多少指向该对象的指针。该对象通常会在没有shared_ptr 指向它的指针存在时才被销毁。
但有时您不希望您的第二个副本真正使对象保持活动状态。当您更改原始指针时,该对象应该消失,就像在您的原始代码中一样。这将我们带到最像原始代码的示例:
-
使用std::shared_ptr 和std::weak_ptr
std::shared_ptr<int> a = std::make_shared<int>(2);
std::weak_ptr<int> b = a;
a = nullptr;
if (std::shared_ptr<int> b_lock = b.lock())
std::cout << *b_lock;
else
std::cout << "b is null\n";
结果:打印“b is null”。
weak_ptr 与shared_ptr 一起使用。当同时使用两者时,指针指向的对象通常会在没有shared_ptr 指向它时立即被销毁,这仍然是正确的。而且,当该对象被销毁时,任何指向它的 weak_ptr 指针都会自动更改为空指针。
需要b.lock() 调用和shared_ptr b_lock,因为您不能像*b 那样直接使用weak_ptr。这是一个安全功能,因为如果您编写代码来检查 b 是否不为空,然后使用 *b,如果您在检查和使用(或其他线程!)之间调用的某个函数碰巧破坏了怎么办?或更改a?相反,我们使用lock() 将weak_ptr 转换回本地shared_ptr,检查该指针是否为空,然后使用shared_ptr。本地的shared_ptr 保证该对象将继续存在足够长的时间以供即将使用它的代码使用,但此后不需要继续存在。
所以你有办法检查指针是否仍然有效。
使用shared_ptr 和weak_ptr 确实需要考虑导致它们指向的对象保持活动或允许它们被清理的条件,但使用new 和delete 的设计也是如此直接。