160

我在几篇文章中读到几乎不应该使用原始指针。相反,它们应该始终包装在智能指针中,无论是作用域指针还是共享指针。

然而,我注意到像 Qt、wxWidgets 这样的框架和像 Boost 这样的库永远不会返回也不期望智能指针,就好像它们根本没有使用它们一样。相反,它们返回或期望原始指针。有什么理由吗?在编写公共 API 时是否应该远离智能指针,为什么?

只是想知道为什么在许多主要项目似乎避免使用智能指针时推荐使用智能指针。

4

8 回答 8

124

除了许多库是在标准智能指针出现之前编写的之外,最大的原因可能是缺乏标准的 C++ 应用程序二进制接口 (ABI)。

如果您正在编写仅包含标头的库,则可以将智能指针和标准容器传递给您的核心内容。它们的源代码在编译时可供您的库使用,因此您仅依赖于它们接口的稳定性,而不是它们的实现。

但由于缺乏标准 ABI,您通常无法安全地跨模块边界传递这些对象。GCCshared_ptr可能与 MSVC 不同shared_ptr,后者也可能与 Intel 不同shared_ptr。即使使用相同的编译器,也不能保证这些类在版本之间是二进制兼容的。

底线是,如果您想分发库的预构建版本,您需要一个可以依赖的标准 ABI。C 没有,但编译器供应商非常擅长针对给定平台的 C 库之间的互操作性——存在事实上的标准。

这种情况对于 C++ 来说不是那么好。各个编译器可以处理它们自己的二进制文件之间的互操作,因此您可以选择为每个受支持的编译器分发一个版本,通常是 GCC 和 MSVC。但鉴于此,大多数库只导出一个 C 接口——这意味着原始指针。

然而,非库代码通常更喜欢智能指针而不是原始代码。

于 2012-04-26T22:28:32.333 回答
39

可能有很多原因。列出其中的几个:

  1. 智能指针最近才成为标准的一部分。在那之前,它们是其他图书馆的一部分
  2. 它们的主要用途是避免内存泄漏;许多库没有自己的内存管理;通常,它们提供实用程序和 API
  3. 它们被实现为包装器,因为它们实际上是对象而不是指针。与原始指针相比,它具有额外的时间/空间成本;图书馆的用户可能不希望有这样的开销

编辑:使用智能指针完全是开发人员的选择。这取决于各种因素。

  1. 在性能关键系统中,您可能不想使用会产生开销的智能指针

  2. 需要向后兼容的项目,您可能不想使用具有 C++11 特定功能的智能指针

Edit2由于以下段落,在 24 小时内有一连串的反对票。我不明白为什么答案被否决,即使下面只是一个附加建议而不是答案。
但是,C++ 总是帮助您打开选项。:) 例如

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};

并在您的代码中将其用作:

Pointer<int>::type p;

对于那些说智能指针和原始指针不同的人,我同意这一点。上面的代码只是一个想法,人们可以编写一个可以与 a 互换的代码#define,这不是强制的;

例如,T*必须显式删除,但智能指针不需要。我们可以有一个模板Destroy()来处理它。

template<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

并将其用作:

Destroy(p);

同样,对于原始指针,我们可以直接复制它,对于智能指针,我们可以使用特殊操作。

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));

哪里Assign()是:

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}
于 2012-04-26T13:37:54.163 回答
35

智能指针(C++ 11 之前)有两个问题:

  • 非标准,因此每个库都倾向于重新发明自己的(NIH 综合症和依赖问题)
  • 潜在成本

默认的智能指针,因为它是免费的,是unique_ptr. 不幸的是,它需要最近才出现的 C++11 移动语义。所有其他智能指针都有一个成本(shared_ptr, intrusive_ptr)或具有不理想的语义(auto_ptr)。

随着 C++11 指日可待,带来一个std::unique_ptr,人们会忍不住认为它终于结束了......我并不那么乐观。

只有少数主要编译器实现了 C++11 的大部分内容,并且仅在其最新版本中实现。我们可以期待 QT 和 Boost 等主要库愿意在一段时间内保持与 C++03 的兼容性,这在某种程度上阻碍了新的闪亮智能指针的广泛采用。

于 2012-04-26T13:50:42.643 回答
13

您不应该远离智能指针,它们尤其适用于您必须传递对象的应用程序。

库往往要么只返回一个值,要么填充一个对象。它们通常没有需要在很多地方使用的对象,因此它们不需要使用智能指针(至少不在它们的接口中,它们可能在内部使用它们)。

我可以举一个我们一直在开发的库为例,经过几个月的开发,我意识到我们只在几个类中使用了指针和智能指针(占所有类的 3-5%)。

在大多数情况下,通过引用传递变量就足够了,只要我们有一个可以为空的对象,我们就使用智能指针,而当我们使用的库迫使我们这样做时,我们就使用原始指针。

编辑(由于我的声誉,我无法发表评论):通过引用传递变量非常灵活:如果你希望对象是只读的,你可以使用 const 引用(你仍然可以做一些讨厌的转换来编写对象) 但您可以获得最大的保护(与智能指针相同)。但我确实同意只返回对象要好得多。

于 2012-04-26T13:59:05.810 回答
8

Qt 毫无意义地重新发明了标准库的许多部分,试图成为 Java。我相信它现在确实有自己的智能指针,但总的来说,它还算不上设计的巅峰之作。据我所知,wxWidgets 早在编写可用的智能指针之前就已经设计好了。

至于 Boost,我完全希望他们在适当的地方使用智能指针。您可能必须更具体。

此外,不要忘记智能指针的存在是为了强制所有权。如果 API 没有所有权语义,那么为什么要使用智能指针呢?

于 2012-04-26T13:41:53.853 回答
3

好问题。我不知道你提到的具体文章,但我不时读到类似的东西。我怀疑此类文章的作者倾向于对 C++ 风格的编程抱有偏见。如果作者只在必要时才使用 C++ 编程,然后尽快返回 Java 或类似的语言,那么他并没有真正分享 C++ 的思维方式。

人们怀疑某些或大多数相同的作者更喜欢垃圾收集内存管理器。我不知道,但我的想法与他们不同。

智能指针很棒,但它们必须保持引用计数。保持引用计数会在运行时承担成本——通常是适度的成本,但仍然是成本。通过使用裸指针来节省这些成本并没有错,尤其是在指针由析构函数管理的情况下。

C++ 的一大优点是它支持嵌入式系统编程。裸指针的使用是其中的一部分。

更新: 评论者正确地观察到 C++ 的新unique_ptr(自 TR1 起可用)不计算引用。评论者对“智能指针”的定义也与我想象的不同。他的定义可能是对的。

进一步更新: 下面的评论线程很有启发性。全部推荐阅读。

于 2012-04-26T13:39:47.347 回答
3

还有其他类型的智能指针。您可能需要一个专门的智能指针,用于网络复制(检测它是否被访问并将任何修改发送到服务器或类似的东西),保留更改历史记录,标记它被访问的事实,以便可以在何时进行调查您将数据保存到磁盘等。不确定在指针中这样做是否是最好的解决方案,但在库中使用内置的智能指针类型可能会导致人们被锁定并失去灵活性。

除了智能指针之外,人们还可以有各种不同的内存管理需求和解决方案。我可能想自己管理内存,我可以为内存池中的东西分配空间,所以它是提前分配的,而不是在运行时分配的(对游戏很有用)。我可能正在使用 C++ 的垃圾收集实现(C++11 使这成为可能,尽管尚不存在)。或者也许我只是没有做任何足够先进的事情来担心打扰他们,我知道我不会忘记未初始化的对象等等。也许我只是对自己在没有指针拐杖的情况下管理内存的能力有信心。

与 C 的集成也是另一个问题。

另一个问题是智能指针是 STL 的一部分。C++ 被设计为在没有 STL 的情况下也可以使用。

于 2013-01-06T06:51:58.647 回答
0

这也取决于你在哪个领域工作。我以编写游戏引擎为生,我们避免像瘟疫一样的提升,在游戏中提升的开销是不可接受的。在我们的核心引擎中,我们最终编写了自己的 stl 版本(很像 ea stl)。

如果我要编写表单应用程序,我可能会考虑使用智能指针;但是一旦内存管理成为第二天性,没有对内存进行精细控制就会变得非常烦人。

于 2013-01-08T02:06:43.040 回答