如何在优秀的C++ API中正确使用shared_ptr

kay*_*ahr 2 c++ c++11

我目前正在尝试找出如何shared_ptr在C++ API中正确使用C++ 11 的功能.我需要它的主要区域是容器类(例如场景图中的节点,例如可能包含子节点列表和父节点的引用以及类似的东西).创建节点的副本不是一个选项,使用引用或指针是痛苦的,因为没有人真正知道谁负责破坏节点(当有人破坏仍被其他节点引用的节点时,程序将崩溃).

所以我认为shared_ptr在这里使用可能是一个好主意.让我们看一下下面的简化示例(它演示了一个必须连接到父节点的子节点):

#include <memory>
#include <iostream>

using namespace std;

class Parent {};

class Child {
    private:
        shared_ptr<Parent> parent;
    public:
        Child(const shared_ptr<Parent>& parent) : parent(parent) {}
        Parent& getParent() { return *parent.get(); }
};

int main() {
    // Create parent
    shared_ptr<Parent> parent(new Parent());

    // Create child for the parent
    Child child(parent);

    // Some other code may need to get the parent from the child again like this:
    Parent& p = child.getParent();

    ...        

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

此API强制用户使用a shared_ptr来创建子级和父级之间的实际连接.但在其他方法中,我想要一个更简单的API,这就是为什么该getParent()方法返回对父项的引用而不是shared_ptr.

我的第一个问题是:这是正确用法shared_ptr吗?还是有改进的余地?

我的第二个问题是:我如何对空指针做出正确的反应?因为该getParent方法返回一个引用,用户可能认为它永远不会返回NULL.但这是错误的,因为当有人传递包含指向构造函数的空指针的共享指针时,它将返回NULL.其实我不想要空指针.必须始终设置父级.我该如何妥善处理?通过手动检查构造函数中的共享指针并在包含NULL时抛出异常?或者,还有更好的方法?也许是某种不可空的共享指针?

mar*_*rko 8

在您描述的目的中使用共享指针是合理的,并且在C++ 11库中越来越常见.

需要注意的几点:

  • 在API上,将a shared_ptr作为参数强制调用者构造a shared_ptr.这绝对是一个很好的举动,其中有一个指针的所有权转移.在函数仅使用a的情况下,shared_ptr可以接受对象或对象的引用shared_ptr

  • 您正在使用shared_ptr<Parent>对父对象的后向引用,而在另一个方向上使用一个.这将创建一个保留周期,从而导致永远不会被删除的对象.通常,使用shared_ptr从上到下weak_ptr引用时和引用时引用.特别注意委托/回调/观察者对象 - 这些对象几乎总是想要一个weak_ptr被调用者.如果lambdas异步执行,你还需要注意它们.一种常见的模式是捕获a weak_ptr.

  • 通过引用而不是值传递共享指针是带有参数支持和反对的风格点.显然,当通过引用传递时,您没有通过所有权(例如,增加对象的引用计数).另一方面,你也没有花费开销.以这种方式引用对象存在危险.在更实际的层面上,使用C++ 11编译器和标准库,传递值应该导致移动而不是复制构造,并且无论如何几乎都是免费的.但是,通过引用传递使得调试变得相当容易,因为您不会重复进入shared_ptr构造函数.

  • 构造你shared_ptrstd::make_shared而不是new()shared_ptr构造函数

    shared_ptr<Parent> parent = std::make_shared<Parent>();

    使用现代编译器和库,可以节省呼叫new().

  • 双方shared_ptrweak_ptr可以包含NULL-就像任何其他指针可以.你应该总是养成在解除引用之前检查的习惯,也可能是assert()自由地进行检查.对于构造函数的情况,您始终可以接受NULL指针,而是抛弃使用点.

  • 您可以考虑使用a typedef作为共享指针类型.有时使用的一种风格如下:

    typedef std::weak_ptr<Parent> Parent_P;

    typedef std::shared_ptr<Parent> Parent_WkP;

    typedef std::weak_ptr<Child> Child_P;

    typedef std::shared_ptr<Child> Child_WkP;

  • 知道在头文件中你可以在shared_ptr<Type>没有看到完整声明的情况下转发声明也很有用Type.这可以节省很多头臃肿

  • 你能解释为什么`Child_WkP`是非弱指针吗?它首先看起来好像`WkP`应该是一个命名约定来记录它是一个弱指针. (4认同)
  • 不同意typedef,它只是让你更难阅读恕我直言. (4认同)