【问题标题】:Just how bad are bare pointers?裸指针有多糟糕?
【发布时间】:2012-05-14 19:21:28
【问题描述】:

指向在 C++ 中以其他方式分配的东西的指针是否相当安全?

到目前为止,我一直在使用 STL 容器(在一种情况下是一个数组,但这是另一个问题)来满足我所有的动态内存需求,因此我不需要显式使用 new 关键字.我也一直在愉快地使用普通的 ol'int *foo 类型指针来引用事物。现在我正在阅读有关智能指针的文章(我在 Java 上切过牙,所以我以前从来不用担心这个),传统观点似乎是“裸指针不好,不要使用它们。”

那么我遇到了多大的麻烦?我可以安全地继续使用裸指针,只要它们指向的东西有其他破坏条件吗?这是我可以逃避但将来应该避免的事情吗?还是我应该匆忙修复它是一场灾难?

【问题讨论】:

  • 您应该阅读异常安全、RAII 并查看 C++11 中的两个新智能指针(并且已经在 Boost 中使用了一段时间):std::shared_ptr<T>std::unique_ptr<T> .还有很多其他类似的问题,比如:stackoverflow.com/questions/6675651/…。尤其是异常安全,以及在抛出异常和展开堆栈时如何防止相关问题(如内存泄漏)是值得注意的。

标签: c++ pointers smart-pointers


【解决方案1】:

裸指针本身是安全的,不正确使用它们是危险的(你很容易被带走)。智能指针非常漂亮,但有些 (shared_ptr) 涉及引用计数,这会导致性能损失。您应该尝试在适用的情况下使用智能指针,但 AFAIK 使用指针并不被认为是一个可怕的错误。

在引用 STL 容器的成员时应该小心,因为它们的地址可能会在重定位期间发生变化,从而给您带来奇怪的错误。

【讨论】:

  • unique_ptrscoped_ptr 没有性能开销,对于避免在遇到异常时发生内存泄漏是必要的。
  • 回复:你答案中的最后一句话 - Daaaang。这听起来像是我几乎肯定会遇到并且从未自己想出来的事情。
  • @ObliteraxScourgeofNations:将指针指向基于节点的容器是安全的:arraylistmapset。当您添加(或保留空间)元素时,其他容器可能会使所有指针无效。
  • @MooingDuck,当您删除项目时也会发生这种情况。在向量中,除非您删除最后一项,否则从您刚刚删除的项开始的所有项都将“向下”移动一个档次。
  • @AlexisWilke:规范不是这么说的。当您删除一个项目时,所有指针都将失效。这意味着实现可以分配一个全新的数组并移动所有项目,而不是“将它们向下移动一个档次”。
【解决方案2】:

说“裸指针不好;不要使用它们”是完全准确的,并附有一个小附录:“指向你必须清理的东西”。

如果你有一个对象并且销毁它是其他人的责任,那么原始指针绝对没问题。但是,当您负责通过任何清理功能销毁对象时,请始终使用智能指针。此外,对于您不清理的对象,请注意在什么情况下它们会被其他系统函数本地人、vector 调整大小等清理。

所有权规则:

  • 无所有权:T*,并注意何时无法再使用它
  • 共享所有权:shared_ptr<T>,必要时使用自定义删除器
  • 唯一所有权:unique_ptr<T, Del>,必要时自定义删除器

始终遵循这些规则,您将永远不会遇到任何内存泄漏、双重释放、错误的指针访问或任何类似的与内存相关的错误。

【讨论】:

【解决方案3】:

在编写代码的人和维护代码的人不会犯任何错误的完美世界中,原始指针永远是惊人的。

不幸的是,事实并非如此。首先,裸指针容易出错,指向一些内存,这些内存可以在指针不知道的情况下失效,指针可以别名并且它们指向的内容发生变化。

我们实际上需要智能指针来弥补我们的“愚蠢”。至少有些东西必须是“聪明的”:)。

除非你正在做一些非常底层的事情,否则没有必要使用原始指针,因为它们“不那么聪明”。话虽如此,如果您非常小心,并且在您编写代码后使用您的代码的人非常小心(通常情况并非如此),那么请继续使用原始指针,但除此之外,请使用 smart指针,因为它们只产生很少或没有开销。

unique_ptr<> 在您移动它之前没有任何开销,在这种情况下,它将一个 NULL 写入内存。在现代编译器上,这经常被优化掉。

shared_ptr<> 计算引用并且会产生大量开销,尤其是在多线程应用程序中使用时,但这可以解决,因此并不是什么大问题。

总而言之,没有必要紧急修复原始指针,但我认为不鼓励使用它们。

【讨论】:

  • 我不同意“完美”的世界场景,原始指针不遵守 RAII 并且本质上不是异常安全的。在一个完美的世界里,人们会使用它们
  • 不赞成,因为裸指针非常适合指向您不拥有的资源。 (好吧,你可以使用boost::optional<T&>,但你为什么要使用呢?)
  • 有时还指向你不拥有的东西可以带来一些超级有趣的时刻;)
【解决方案4】:

裸指针被认为是不好的,因为使用它们很容易陷入困境。智能指针会自动为您处理一些问题,这使得它们不太容易出错。

当您对所有代码拥有绝对控制权(即您是项目中唯一的编码员)时,只要您遵循基本的内存分配法律和惯例(“分配内存的人除非另有说明,否则将其删除。”)。但是,当您与其他人一起编写代码时(即,有超过 1 名编码员的项目),这就为错误和误解打开了大门。

智能指针负责谁拥有一个对象(因此谁应该释放它)。他们还可以跟踪使用该对象的最后一个代码何时不再需要它,因此可以在共享分配的数据时安全地释放它。

引用计数的智能指针还为从堆中分配内存的类的那些数据成员生成安全的默认复制构造函数和安全的默认复制赋值运算符。他们可以安全地将他们的引用计数智能指针设置为他们正在复制的原始对象,并且当原始对象或克隆超出范围/被删除时,他们通过智能指针管理和共享的分配内存将留给仍然指向它的其他对象。这不适用于裸指针。如果使用裸指针,则必须编写复制赋值运算符和复制构造函数来克隆分配内存的对象,以防止包含/拥有的分配数据的数据损坏。

【讨论】:

  • 引用计数的智能指针将产生安全的默认复制构造函数,但在 99% 的情况下,它不会做你想要做的事情,并且除了双精度外,它的行为类似于原始指针删除。
【解决方案5】:

当你 newdelete 不用担心太多时,裸指针是不好的。这种行为可能会导致您出现非常奇怪的错误。我的建议是,当您必须使用在堆上分配的几个指针和对象时,学习使用内存泄漏检查器 Valgrind

然后是一些简单的规则,当你用new [] 实例化数组时,你总是必须用delete [] 删除它们,反之,当你用new 实例化单个对象时,总是调用delete

切记避免将new-deletemalloc-freenew[]-delete[] 混用,因为这些函数并非旨在相互配合使用,例如never 这样做:

int *a = (int*)malloc(10*sizeof(int));
delete a;

但是这个

int *a = new int[10];
delete[] a;

正如蒂博尔所说,指针的使用本身并不坏,但一如既往,“能力越大责任越大”:P

【讨论】:

  • 永远不要用new[] 实例化任何东西,永远不要自己调用销毁函数。
  • 为什么要投反对票?我同意使用智能指针,但这些只是对“裸指针”用户有用的提示。
  • 我没有投反对票,但智能指针对于异常安全代码是必需的。如果newdelete 之间的任何函数抛出异常,您的程序将永远不会调用delete 并会泄漏内存。
猜你喜欢
  • 2010-12-30
  • 2010-11-24
  • 2011-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-01
相关资源
最近更新 更多