我在几篇文章中读到,原始指针几乎不应该被使用。相反,它们应始终包装在智能指针中,无论是作用域指针还是共享指针。
但是,我注意到像Qt,wxWidgets和Boost这样的库永远不会返回也不期望智能指针,就好像它们根本没有使用它们一样。相反,它们返回或期望原始指针。这有什么原因吗?当我编写公共 API 时,我应该远离智能指针吗?为什么?
只是想知道为什么当许多重大项目似乎避免使用智能指针时,建议使用它们。
除了许多库是在标准智能指针出现之前编写的之外,最大的原因可能是缺少标准的C++应用程序二进制接口(ABI(。
如果您正在编写仅标头库,则可以将智能指针和标准容器传递到您的心中的内容。它们的源代码在编译时可供您的库使用,因此您仅依赖于其接口的稳定性,而不是其实现的稳定性。
但由于缺少标准 ABI,通常无法跨模块边界安全地传递这些对象。GCC shared_ptr
可能与MSVC shared_ptr
不同,MSVC也可能与英特尔shared_ptr
不同。即使使用相同的编译器,也不能保证这些类在版本之间是二进制兼容的。
最重要的是,如果要分发库的预构建版本,则需要依赖标准 ABI。C 语言没有,但编译器供应商非常了解给定平台的 C 库之间的互操作性 - 存在事实上的标准。
情况对C++来说并不好。各个编译器可以处理其自己的二进制文件之间的互操作,因此您可以选择为每个受支持的编译器(通常是 GCC 和 MSVC(分发一个版本。但有鉴于此,大多数库只导出 C 接口,这意味着原始指针。
但是,非库代码通常应该更喜欢智能指针而不是原始指针。
可能有很多原因。列出其中的几个:
- 智能指针最近才成为标准的一部分。在那之前,他们是其他图书馆的一部分
- 它们的主要用途是避免内存泄漏;许多库没有自己的内存管理;通常他们提供实用程序和 API
- 它们被实现为包装器,因为它们实际上是对象而不是指针。与原始指针相比,它具有额外的时间/空间成本;库的用户可能不希望有这样的开销
编辑:使用智能指针完全是开发人员的选择。这取决于各种因素。
-
在性能关键型系统中,您可能不希望使用智能产生开销的指针
-
需要向后兼容的项目,您可能不想要使用具有 C++11 特定功能的智能指针
编辑2 由于以下段落,在 24 小时内有一连串的反对票。我不明白为什么答案被否决,即使下面只是一个附加建议而不是答案。
但是,C++始终有助于您打开选项。:)例如
template<typename T>
struct Pointer {
#ifdef <Cpp11>
typedef std::unique_ptr<T> type;
#else
typedef T* type;
#endif
};
在您的代码中,将其用作:
Pointer<int>::type p;
对于那些说智能指针和原始指针不同的人,我同意这一点。上面的代码只是一个想法,人们可以编写一个只需与#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
}
智能指针有两个问题(C++11 之前(:
- 非标准,所以每个库都倾向于重新发明自己的库(NIH syndrom 和依赖关系问题(
- 潜在成本
默认的智能指针是免费的,是 unique_ptr
.不幸的是,它需要 C++11 移动语义,这是最近才出现的。所有其他智能指针都有成本(shared_ptr
,intrusive_ptr
(或语义不理想(auto_ptr
(。
随着C++11的临近,带来std::unique_ptr
,人们会忍不住认为它终于结束了......我不太乐观。
只有少数主要编译器实现了 C++11 的大部分内容,并且仅在其最新版本中实现。我们可以预期QT和Boost等主要库愿意在一段时间内保持与C++03的兼容性,这在一定程度上阻止了新的闪亮智能指针的广泛采用。
你不应该远离智能指针,它们有其用途,特别是在你必须传递对象的应用程序中。
库往往只返回一个值或填充一个对象。他们通常没有需要在很多地方使用的对象,所以他们不需要使用智能指针(至少不在他们的界面中,他们可以在内部使用它们(。
我可以以我们一直在开发的一个库为例,经过几个月的开发,我意识到我们只在少数类中使用指针和智能指针(占所有类的 3-5%(。
在大多数地方,通过引用传递变量就足够了,每当我们有一个可能为 null 的对象时,我们就使用智能指针,当我们使用的库强迫我们使用时,我们使用原始指针。
编辑(由于我的声誉,我无法发表评论(:通过引用传递变量是非常灵活的:如果你希望对象是只读的,你可以使用const引用(你仍然可以做一些讨厌的强制转换来编写对象(,但你可以得到最大的保护(与智能指针相同(。但我确实同意只返回对象要好得多。
Qt毫无意义地重新发明了标准库的许多部分,试图成为Java。我相信它现在确实有自己的智能指针,但总的来说,它几乎不是设计的巅峰之作。据我所知,wxWidgets早在编写可用的智能指针之前就已经设计好了。
至于Boost,我完全希望他们在适当的时候使用智能指针。您可能需要更具体。
此外,不要忘记智能指针的存在是为了强制执行所有权。如果 API 没有所有权语义,那么为什么要使用智能指针呢?
还有其他类型的智能指针。您可能需要一个专用的智能指针,用于网络复制之类的东西(检测它是否被访问并将任何修改发送到服务器或类似的东西(,保留更改历史记录,标记它被访问的事实,以便在将数据保存到磁盘时可以进行调查等等。不确定在指针中执行此操作是否是最佳解决方案,但是在库中使用内置的智能指针类型可能会导致人们被锁定在其中并失去灵活性。
除了智能指针之外,人们可以有各种不同的内存管理要求和解决方案。我可能想自己管理内存,我可以为内存池中的东西分配空间,以便提前分配而不是在运行时分配(对游戏有用(。我可能正在使用C++的垃圾回收实现(C++11 使这成为可能,尽管尚不存在(。或者,也许我只是没有做任何足够高级的事情来担心打扰它们,我可以知道我不会忘记未初始化的对象等等。也许我只是对自己在没有指针拐杖的情况下管理记忆的能力充满信心。
与C集成也是另一个问题。
另一个问题是智能指针是STL的一部分。C++被设计为可以在没有 STL 的情况下使用。
好问题。 我不知道你提到的具体文章,但我不时读过类似的东西。 我怀疑这些文章的作者往往对C++式编程有偏见。 如果编写者只在必须的时候才用C++编程,然后尽快回到Java或类似的地方,那么他就不会真正分享C++心态。
有人怀疑一些或大多数相同的作者更喜欢垃圾收集内存管理器。 我没有,但我的想法与他们不同。
智能指针很棒,但它们必须保持引用计数。 保留引用计数在运行时会产生成本 - 通常成本适中,但仍然成本高昂。 通过使用裸指针来节省这些成本并没有错,尤其是在指针由析构函数管理的情况下。
C++的一个优点是它支持嵌入式系统编程。 裸指针的使用是其中的一部分。
更新:评论者正确地观察到C++的新unique_ptr
(自 TR1 起可用(不计算引用。 评论者对"智能指针"的定义也与我想象的有所不同。 他对这个定义可能是对的。
进一步更新:下面的评论线程很有启发性。 所有这些都是推荐阅读的。
这也取决于您在哪个领域工作。我以编写游戏引擎为生,我们避免像瘟疫一样的提升,在游戏中,提升的开销是不可接受的。在我们的核心引擎中,我们最终编写了自己的stl版本(很像ea stl(。
如果我要编写一个表单应用程序,我可能会考虑使用智能指针;但是一旦内存管理成为第二天性,对内存没有粒度控制就会变得很烦人。