如何在良好的c++ api中正确使用shared_ptr
How to properly use shared_ptr in good C++ APIs
我目前正在努力找出如何在c++ api中正确使用c++ 11的shared_ptr
功能。我需要它的主要领域是在容器类(例如场景图中的节点,可能包含子节点列表和对父节点的引用等)。创建节点的副本不是一个选择,使用引用或指针是非常痛苦的,因为没有人真正知道谁负责销毁节点(当有人销毁一个节点时,该节点仍被其他节点引用,程序将崩溃)。
所以我认为在这里使用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;
}
这个API强制用户使用shared_ptr
来创建子节点和父节点之间的实际连接。但是在其他方法中,我想要一个更简单的API,这就是为什么getParent()
方法返回对父类而不是shared_ptr
的引用。
我的第一个问题是:这是shared_ptr
的正确用法吗?还是有改进的空间?
我的第二个问题是:我如何正确地对空指针做出反应?因为getParent
方法返回一个引用,用户可能认为它永远不会返回NULL。但这是错误的,因为当有人将包含空指针的共享指针传递给构造函数时,它将返回NULL。实际上我不想要空指针。必须始终设置父节点。我该如何正确处理?通过手动检查在构造函数中的共享指针和抛出异常时,它包含NULL?还是有更好的办法?也许是某种不可空的共享指针?
按照您所描述的目的使用共享指针是合理的,并且在c++ 11库中越来越普遍。
注意事项:
-
在API上,将
的引用。shared_ptr
作为参数强制调用者构造shared_ptr
。这绝对是一个很好的举动,有所有权的转移点。在函数只使用shared_ptr
的情况下,可以接受对对象或shared_ptr
-
您正在使用
shared_ptr<Parent>
来保留对父对象的回引用,同时在另一个方向上使用一个。这将创建一个保留周期,导致对象永远不会被删除。通常,从上到下引用时使用shared_ptr
,向上引用时使用weak_ptr
。特别要注意委托/回调/观察者对象——这些对象几乎总是需要一个weak_ptr
给被调用者。如果lambdas是异步执行的,您还需要注意它们。一个常见的模式是捕获一个weak_ptr
。 -
通过引用而不是值传递共享指针是带有赞成和反对参数的风格点。显然,当通过引用传递时,您并没有传递所有权(例如,增加对象的引用计数)。另一方面,你也没有承担开销。以这种方式引用对象是有危险的。在更实际的层面上,使用c++ 11编译器和标准库,按值传递应该导致移动而不是复制构造,并且无论如何都几乎是免费的。然而,通过引用传递使调试变得相当容易,因为您不会重复进入
shared_ptr
的构造函数。 -
用
std::make_shared
而不是new()
和shared_ptr
的构造器构建shared_ptr
shared_ptr<Parent> parent = std::make_shared<Parent>();
在现代编译器和库中,这可以节省对
new()
的调用。 -
shared_ptr
和weak_ptr
都可以包含NULL
-就像任何其他指针一样。在解引用之前,您应该始终养成检查的习惯,并且可能也可以自由地使用assert()
。对于构造函数的情况,您总是可以接受NULL
指针,而不是在使用点抛出。 -
您可以考虑使用
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;
-
知道在头文件中您可以在没有看到
Type
的完整声明的情况下转发声明shared_ptr<Type>
也是有用的。这可以节省大量的头文件
使用共享指针的方式是正确的,但有两点需要注意。
-
你的父树和子树必须与其他对象共享指针的生命周期。如果您的父/子树将是指针的唯一用户,请使用
unique_ptr
。如果另一个对象控制指针的生命周期,而你只想引用指针,你可能最好使用weak_ptr
,除非生命周期保证超过你的Parent - Child树,原始指针可能是合适的。请记住,使用shared_ptr
,您可以获得循环参考,因此它不是灵丹妙药。 -
至于如何控制NULL指针:好吧,这一切都归结为你的API隐含的契约。如果不允许用户提供空指针,您只需要记录这一事实。最好的方法是包含一个指针不为空的
assert
。这将使您的应用程序在调试模式下崩溃(如果指针为空),但不会在发布二进制文件上导致运行时损失。但是,如果由于某种原因,空指针是允许输入的,那么您需要在空指针的情况下提供正确的错误处理。
子女不拥有父母。相反,情况正好相反。如果子节点需要能够获得它们的父节点,那么使用非拥有的指针或引用。使用共享的(如果可以的话最好是唯一的)父指针指向子指针。
- CLANG 编译器 说:变量"PTR"可能未初始化
- 在以唯一ptr为值的C++映射中,动态内存何时会被销毁
- 将 ptr 传递给 ptr 到 A 作为参数传递给 A 的函数是不好的做法吗?
- 为共享 ptr 向量实现复制 c'tor?
- 字符和整数中 **(ptr+1) 的值差异
- C++:在不中断共享的情况下通过引用传递共享 PTR?
- 如何将派生类从基 ptr 分配给 nlohmann::json
- 引用 std::shared:ptr 以避免引用计数
- 为什么我不能在不进行任何转换的情况下将浮点数放入任何类型的 ptr 中?
- 在调用函数时,ptr** 和 ptr*& 之间是否有区别,或者首选C++?
- 另一种类型的智能ptr,比如具有弱refs的unique_ptr
- 尝试打印出 *ptr++ 的值,以了解它是如何工作的
- 如何控制共享 ptr 引用计数?
- dopen():不以 root 身份运行时"failed to map segment from shared object"
- C++中的指针否定 (!ptr == NULL)
- 从const ptr*转换为ptr*时出现问题
- 无法使用 libtool 将 -shared 参数传递给 g++
- boost::shared_ptr和std::shared-ptr的同居
- 我可以用std::shared_ptr而不是boost::shared-ptr构建boost库吗
- shared-ptr-C++shared_ptr与unique_ptr用于资源管理