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

lau*_*ent 156 c++ api pointers smart-pointers

我在一些文章中读到,几乎从不使用原始指针.相反,它们应始终包含在智能指针内,无论是作用域还是共享指针.

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

只是想知道为什么许多重大项目似乎避免使用智能指针.

Jon*_*rdy 123

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

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

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

最重要的是,如果要分发库的预构建版本,则需要依赖标准ABI.C没有,但编译器供应商非常擅长给定平台的C库之间的互操作性 - 事实上的标准.

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

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

  • 我同意你的意见,即使传递std :: string也可能很痛苦.这说明很多关于C++作为"图书馆的优秀语言". (17认同)
  • 底线更像是:如果要分发预构建版本,则必须为要支持的每个编译器执行此操作. (8认同)
  • @josefx:是的,这很难过但是真的,唯一的选择是COM或原始C接口.我希望C++ comity能够开始担心这类问题.我的意思是它不像C++是2年前的新语言. (6认同)
  • @NathanAdams:这样的软件无疑是令人印象深刻和有用的.但它处理了更深层次问题的症状:生命和所有权的C++语义介于贫困和不存在之间.如果语言不允许,那些堆错误就不会出现.可以肯定的是,聪明的指针并不是灵丹妙药 - 它们试图弥补一开始使用C++带来的一些损失. (4认同)
  • 我贬低了因为这是错误的.在大多数情况下,ABI问题不仅仅是可管理的.虽然几乎不是用户友好的,但ABI也难以克服. (3认同)

iam*_*ind 40

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

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

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

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

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

Edit2由于下面的段落,在24小时的范围内有一串几个downvotes.我不明白为什么答案被低估了,尽管下面只是一个附加建议而不是答案.
但是,C++总是方便您打开选项.:)例如

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};
Run Code Online (Sandbox Code Playgroud)

在您的代码中使用它作为:

Pointer<int>::type p;
Run Code Online (Sandbox Code Playgroud)

对于那些说智能指针和原始指针不同的人,我同意这一点.上面的代码只是一个想法,人们可以编写一个可以与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
}
Run Code Online (Sandbox Code Playgroud)

并将其用作:

Destroy(p);
Run Code Online (Sandbox Code Playgroud)

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

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));
Run Code Online (Sandbox Code Playgroud)

哪里Assign()是:

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}
Run Code Online (Sandbox Code Playgroud)

  • 在3.*一些*智能指针有额外的时间/空间成本,其他没有,包括`std :: auto_ptr`长期以来一直是标准的一部分(注意,我喜欢`std :: auto_ptr `作为创建对象的函数的返回类型,即使它在其他地方几乎无用).在C++ 11中,`std :: unique_ptr`比普通指针没有额外的成本. (13认同)
  • 你不能像这样输入类型,这些类型在任何方面都不相同.写这样的typedef就是在惹麻烦. (12认同)
  • 智能指针具有"额外时间/空间成本"的说法有些误导; 除了`unique_ptr`之外的所有智能指针都会产生运行时成本,但`unique_ptr`是迄今为止最常用的指针.您提供的代码示例也具有误导性,因为`unique_ptr`和`T*`是完全不同的概念.你将它们都称为"类型"这一事实给人的印象是它们可以相互交换. (7认同)
  • 确切地说......'unique_ptr`的外观和`auto_ptr`的消失有一个很好的对称性,针对C++ 03的代码应该使用后者,而针对C++ 11的代码可以使用前者.智能指针**不是**`shared_ptr`,有许多标准和无标准,包括被拒绝为"managed_ptr"的标准提案 (4认同)
  • @iammilind,这些都是有趣的,但有趣的是,如果我们最终使用智能指针,显然许多人会建议,我们最终会创建与主要库不兼容的代码.当然,我们可以根据需要包装/解包智能指针,但它似乎很麻烦,会产生不一致的代码(有时我们处理智能指针,有时候不会). (2认同)
  • -1:你甚至可以像`Pointer <int> :: type`一样使用它吗?以为你需要指定它是一个类型,而不是静态成员,如下所示:`typename Pointer <int> :: type`.与其他人指出的相比,这是一个次要因素 - 做这样的事情非常糟糕.使用或不使用智能指针,不要试图像这样聪明. (2认同)
  • @iammilind如果我刚刚开始学习智能指针,我会发现代码示例非常具有误导性.之所以可以选择使用原始指针而不是`std :: unique_ptr`,正是因为前者允许您管理未绑定到任何特定范围的资源.你的代码清单可能会让人们相信盲目地用`t*`代替`std :: unique_ptr <T>`是可以接受的. (2认同)

Mat*_* M. 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的兼容性一段时间,这在一定程度上排除了新的闪亮智能指针的广泛采用.


Rob*_*ess 12

你不应该远离智能指针,它们尤其适用于必须传递物体的应用程序.

库往往只返回一个值或填充一个对象.它们通常不具有需要在很多地方使用的对象,因此不需要它们使用智能指针(至少不在它们的界面中,它们可以在内部使用它们).

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

在大多数地方通过引用传递变量就足够了,只要我们有一个可能为null的对象,我们就会使用智能指针,而当我们使用的库强制我们使用时,我们会使用原始指针.

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

  • 你有const(似乎我可以评论:D). (2认同)

Pup*_*ppy 9

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

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

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

  • Qt是在大部分功能在其想要使用的平台上广泛普及之前编写的.它已经有很长一段时间的智能指针,并使用它们在几乎所有Q*类中隐式共享资源. (19认同)
  • 当std :: string不在时,创建了@Inverse QString. (10认同)
  • *每个*GUI库都不必要地重新发明轮子.即使是字符串,Qt也有`QString`,wxWidgets有`wxString`,MFC有可怕名字`CString`.对于99%的GUI任务,UTF-8`std :: string`不够好吗? (6认同)