C++ API 中的智能指针

Smart pointers in C++ APIs?

本文关键字:智能 指针 API C++      更新时间:2023-10-16
人们

经常建议不要在现代C++中使用原始指针,除了极少数情况。在C++库 API 中使用智能指针的常见做法是什么?

我想到了以下用例:

  1. 返回新对象的函数。
  2. 返回一个新对象的函数,但它也创建了对该对象的另一个引用。
  3. 仅使用它作为参数接收的对象的函数。
  4. 接管对象所有权的函数。
  5. 一个函数,它将存储对它作为参数接收的对象的引用,但可能存在对同一对象的其他引用(从调用方)。

不幸的是,库 API 设计远远超出了语言本身的规则:突然间你必须关心实现细节,例如 ABI。

与 C 相反,在 C 中,常见的实现可以很容易地交互在一起,C++实现具有非常不同的 ABI(Microsoft VC++ 的 ABI 与 gcc 和 Clang 使用的 Itanium ABI 完全不兼容),并且C++标准库实现也彼此不兼容,因此使用 libstdc++ 编译的库(与 gcc 捆绑在一起)不能被使用另一个主要版本的 libstdc++ 或其他实现(如 libc++(与 Clang 捆绑在一起)的程序使用,如果 界面中将显示std::类。

因此,这实际上取决于您是希望将库作为二进制文件交付,还是假设用户能够使用自己的编译器和所选的标准库实现来编译库。只有在后一种情况下,你才应该公开一个C++接口,因为二进制发行版坚持使用 C 更好(而且与其他语言集成也更容易)。


有了这个,让我们假设你决定使用C++ API:

1) 返回新对象的函数。

如果

对象可以按值返回,则这样做;如果它是多态的,请使用 std::unique_ptr<Object>

2) 返回一个新对象的函数,但它也创建了对该对象的另一个引用。

罕见的情况。我想你的意思是它以某种方式保留了一个参考。在这种情况下,您拥有共享所有权,因此显而易见的选择是 std::shared_ptr<Object> .

3) 仅使用它收到的对象作为参数的函数。

比最初看起来要复杂得多,即使假设没有保留对对象的引用。

  • 通常,通过引用传递(const或不传递)
  • 除非您打算对对象的副本进行操作,在这种情况下,按值传递以从潜在的移动中受益

4)接管对象所有权的函数。

简单所有权:std::unique_ptr<Object>

5) 一个函数,它将存储对它作为参数接收的对象的引用,但可能还有其他对同一对象的引用(从调用方)。

共享所有权:std::shared_ptr<Object>

  1. 在一般情况下,唯一的指针可以完成这项工作。

    std::unique_ptr<Foo> func();
    

    这样您就可以将它们直接分配给调用方站点的auto变量,而不必担心内存管理。

    auto u = func();
    

    如果您需要共享所有权,您可以在呼叫者站点上转换智能指针:

    std::shared_ptr<Foo> s{func()};
    

    但是,如果函数的返回类型不是多态的,并且该类型移动速度很快,则您可能更喜欢按值返回

    Foo func();
    
  2. 共享指针。

    std::shared_ptr<Foo> func();
    
  3. 只需使用对对象的引用或常量引用,在调用方站点"取消引用"原始指针或智能指针。

    void func(Foo& obj);
    void func(const Foo& obj);
    

    如果参数是可选的,则可以使用原始指针,以便可以轻松地将nullptr传递给它们。

    void func(Foo *obj);
    void func(const Foo *obj);
    
  4. 独特的指针。

    void func(std::unique_ptr<Foo> obj);
    
  5. 对共享指针的常量引用。

    void func(const std::shared_ptr<Foo>& obj);
    

    const 引用(对共享指针)只是一种优化,用于防止在调用函数和返回函数时调整引用计数。

这是一个非常古老的问题,但也是一个重要的问题。

答案中缺少的一个思想流派是,如果可以帮助它,则不应从API返回原始指针或智能指针。

已经讨论了原始指针问题。 但是智能指针也可能被滥用,尤其是在具有复杂内部 API 的大型项目中。

解决方法是创建一个 RAII/包装器对象来保存(从未公开的)智能指针。