为什么C ++库和框架从不使用智能指针?


156

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

但是,我注意到Qt,wxWidgets之类的框架以及Boost之类的库从不返回也不期望有智能指针,就好像它们根本没有使用它们一样。相反,它们返回或期望原始指针。有什么理由吗?编写公共API时,我应该远离智能指针吗?为什么?

只是想知道为什么在许多大型项目似乎都避免使用智能指针的情况下建议使用智能指针。


22
您刚刚命名的所有这些库都是在多年前启动的。智能指针只有在C ++ 11中才真正成为标准。
chrisaycock 2012年

22
例如,在嵌入式/实时系统中,智能指针确实具有开销(引用计数等),这可能很关键。恕我直言-智能指针是为懒惰的程序员。同样,许多API都具有最低的公分母。当我打字时,我感觉到火焰在舔我的脚!
Ed Heal 2012年

93
@EdHeal:之所以会感觉到火焰在舔脚,是因为您在各方面都完全错了。例如,有哪些开销unique_ptr?没有任何。Qt / WxWidgets是针对嵌入式系统还是实时系统?不,它们最多适用于台式机上的Windows / Mac / Unix。智能指针用于希望正确使用它的程序员。
Puppy 2012年

24
确实,手机正在运行Java。
R. Martinho Fernandes

12
智能指针是C ++ 11中真正真正的标准吗?什么???这些东西已经使用了20多年了。
卡兹(Kaz)2012年

Answers:


124

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

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

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

底线是,如果要分发库的预构建版本,则需要依赖的标准ABI。C没有,但是编译器供应商对于给定平台的C库之间的互操作性非常好-实际上存在标准。

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

但是,非库代码通常应优先使用智能指针而不是原始指针。


17
我同意你的看法,即使传递一个std :: string也是一件很痛苦的事情,这充分说明了C ++是“库的绝佳语言”。
Ha11ow12年

8
底线更像是:如果要分发预构建版本,则必须对要支持的每个编译器进行分发。
josefx 2012年

6
@josefx:是的,这很可悲,但确实如此,唯一的选择是COM或原始C接口。我希望C ++社区将开始为此类问题感到担忧。我的意思是,C ++不是2年前的新语言。
机器人混乱2012年

3
我投票失败,因为这是错误的。在大多数情况下,ABI问题都难以解决。虽然几乎不友好,但ABI也并非不可逾越。
小狗

4
@NathanAdams:这类软件无疑是令人印象深刻且有用的。但是它处理了更深层问题的症状:生命周期和所有权的C ++语义介于贫穷和不存在之间。如果语言不允许它们,则不会出现那些堆错误。因此,可以肯定的是,智能指针不是万能药,而是一种尝试来弥补最初使用C ++所造成的一些损失。
乔恩·普迪

40

可能有很多原因。要列出其中一些:

  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
}

14
在3上。有些智能指针具有额外的时间/空间成本,而有些则没有,包括std::auto_ptr很长时间以来一直是标准的一部分(并且请注意,我喜欢将其std::auto_ptr作为创建对象的函数的返回类型,即使它是在其他地方几乎没有用)。在C ++ 11 std::unique_ptr中,与普通指针相比没有额外的开销。
大卫·罗德里格斯(DavidRodríguez)-dribeas 2012年

4
确实...的出现unique_ptr和消失都有很好的对称性auto_ptr,针对C ++ 03的代码应使用后者,而针对C ++ 11的代码可以使用前者。智能指针是不是 shared_ptr,有很多标准,没有标准,其中包括建议的标准,被拒绝的managed_ptr
dribeas大卫-罗德里格斯

2
@iammilind,这些很有趣,但是有趣的是,如果我们最终使用智能指针(很显然很多人会建议这样做),那么最终会创建与主要库不兼容的代码。当然,我们可以根据需要包装/拆开智能指针,但是似乎很麻烦,并且会产生不一致的代码(有时我们处理智能指针,有时则不这样)。
劳伦特

7
关于智能指针具有“额外的时间/空间成本”的说法有些误导。除了unique_ptr会增加运行时成本外,所有其他智能指针都将导致运行时开销,但这unique_ptr是迄今为止最常用的一种。您提供的代码示例也具有误导性,因为unique_ptrT*是完全不同的概念。您将两者都称为事实,type给人的印象是它们可以互换。
void-pointer

12
您不能像这样键入typedef,这些类型在任何方面都不等效。像这样编写typedef会带来麻烦。
Alex B

35

智能指针(C ++ 11之前的版本)存在两个问题:

  • 非标准的,因此每个库都倾向于重塑自己的库(NIH语法和依赖关系问题)
  • 潜在成本

默认的智能指针,因为它是无成本,是unique_ptr。不幸的是,它需要C ++ 11 move语义,这种语义直到最近才出现。所有其他智能指针拥有成本(shared_ptrintrusive_ptr)或有不太理想的语义(auto_ptr)。

随着C ++ 11的到来,带一个std::unique_ptr,人们会以为它终于结束了……我并不那么乐观。

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


12

您不应该远离智能指针,尤其是在必须传递对象的应用程序中,它们会发挥作用。

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

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

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

编辑(由于我的声誉,我无法评论):通过引用传递变量非常灵活:如果您希望对象是只读的,则可以使用const引用(您仍然可以进行一些讨厌的强制转换以编写该对象),但您会获得最大程度的保护(智能指针也是如此)。但是我确实同意只返回对象会更好。


我并不完全同意您的看法,但是我要指出的是,在大多数情况下,有一种流派拒绝使用变量引用。我承认我坚持那所学校。我希望函数不要修改其参数。就我所知,无论如何,C ++的变量引用不会采取任何措施来防止对它们所引用的对象的错误处理,这正是智能指针的目的。
2012年

2
你有这个常量(似乎我可以评论:D)。
机器人混乱2012年

9

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

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

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


19
Qt是在许多功能要使用的平台上充分普及之前编写的。它拥有很长一段时间的智能指针,并使用它们在几乎所有Q *类中进行隐式资源共享。
rubenvb 2012年

6
每个 GUI库都不必要地重新发明轮子。甚至字符串,Qt都有QString,wxWidgets都有wxString,MFC有着可怕的名字CString。UTF-8是否std::string足以胜任99%的GUI任务?
2012年

10
@Inverse QString是在std :: string不在时创建的。
MrFox

检查何时创建qt以及当时可用的智能指针。
Dainius

3

好问题。我不知道您所引用的具体文章,但我不时阅读类似的文章。我的怀疑是,此类文章的作者倾向于对C ++风格的编程怀有偏见。如果编写者仅在必要时使用C ++进行编程,然后尽快返回Java或类似的语言,那么他实际上并不会分享C ++的思维方式。

一个人怀疑某些或大多数相同的作者更喜欢垃圾收集内存管理器。我没有,但是我和他们的想法不同。

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

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

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

进一步更新: 下面的注释线程将点亮。建议阅读所有内容。


2
首先,嵌入式系统编程是所有编程中的绝大部分,并且是无关紧要的。C ++是一种通用语言。其次,shared_ptr保持引用计数。还有许多其他智能指针类型根本不保留引用计数。最后,提到的库针对的是具有大量可用资源的平台。并不是说我是个失败者,而是我要说的是您的帖子充满了错误。
Puppy 2012年

2
@thb-我同意你的观点。DeadMG-请尝试在没有嵌入式系统的情况下生活。是的-有些智能指针没有开销,但有些开销。OP提到了库。例如,Boost包含嵌入式系统使用的部件-但是智能指针可能不适用于某些应用程序。
艾德·赫尔

2
@EdHeal:不能没有嵌入式系统!=为它们编程不是一个很小的,无关紧要的少数派。智能指针适用于需要管理资源生存期的每种情况。
Puppy 2012年

4
shared_ptr没有开销。它仅在不需要线程安全共享所有权语义的情况下才有开销,这正是它所提供的。
R. Martinho Fernandes

1
不,shared_ptr的开销远远超过线程安全共享所有权语义所必需的最小值。特别是,它仅出于存储refcount的目的,分配了一个与要共享的实际对象分开的堆块。intrusive_ptr效率更高,但是(像shared_ptr一样)它还假定指向该对象的每个指针都是intrusive_ptr。使用自定义引用计数共享指针,您可以获得比intrusive_ptr更低的开销,就像我在我的应用程序中所做的那样,然后只要可以保证至少一个智能指针的寿命超过T *值,就可以使用T *。
Qwertie

2

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

人们可以拥有智能指针之外的各种不同的内存管理要求和解决方案。我可能想自己管理内存,我可能正在为内存池中的东西分配空间,因此它是预先分配的,而不是在运行时分配的(对游戏有用)。我可能正在使用C ++的垃圾回收实现(C ++ 11使这成为可能,尽管尚不存在)。或者,也许我只是没有做任何足以使他们烦恼的高级事情,我可以知道我不会忘记未初始化的对象等等。也许我对不用指针就可以管理内存的能力充满信心。

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

另一个问题是智能指针是STL的一部分。C ++设计为无需STL就可以使用。


另一个问题是智能指针是STL的一部分。 ”事实并非如此。
curiousguy

0

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

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


3
没有所谓的“助推器开销”。
curiousguy

4
我从来没有将shared_ptr放慢到任何值得注意的程度。他们加快了生产和调试过程。另外,“提升的开销”到底是什么意思?那是一个很大的毯子​​。
derpface

@curiousguy:这是所有这些标头和宏+模板伏都教的编译开销……
einpoklum 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.