我应该使用 std::shared 指针来传递指针吗?
Should I use std::shared pointer to pass a pointer?
假设我有一个由 std::unique_ptr
管理的对象。我代码的其他部分需要访问这个对象。传递指针的正确解决方案是什么?我应该只通过 std::unique_ptr::get
传递普通指针还是应该使用并传递 std::shared_ptr
而不是 std::unique_ptr
?
我对 std::unique_ptr
有一些偏好,因为该指针的所有者实际上负责清理。如果我使用共享指针,即使实际上应该销毁对象,也有可能由于共享指针而使对象保持活动状态。
编辑:不幸的是,我忘了提到指针不仅是函数调用的参数,而且还将存储在其他对象中以构建对象的网络结构。我不喜欢共享指针,因为这样就不清楚谁真正拥有该对象了。
通常,您只需将引用或普通指针传递给希望观察对象的代码的其他部分。
通过引用:
void func(const Foo& foo);
std::unique_ptr<Foo> ptr;
// allocate ptr...
if(ptr)
func(*ptr);
通过原始指针传递:
void func(const Foo* foo);
std::unique_ptr<Foo> ptr;
// allocate ptr...
func(ptr.get());
选择将取决于传递空指针的需要。
您有责任通过设计确保观察者在 unique_ptr
被销毁后不使用指针或引用。如果您不能保证,那么您必须使用 shared_ptr
而不是 unique_ptr
。观察者可以持有weak_ptr
表示他们没有所有权。
编辑:即使观察者希望保留指针或引用也是可以的,但这确实让确保在 unique_ptr
被销毁后它不会被使用变得更加困难。
如果托管对象的所有权未被转移(并且因为它是一个 unique_ptr
,所有权不能共享),那么将被调用函数中的逻辑与所有权概念分开更为正确。我们通过引用调用来做到这一点。
这是一种令人费解的说法:
给定:
std::unique_ptr<Thing> thing_ptr;
更改事物:
// declaration
void doSomethingWith(Thing& thing);
// called like this
doSomethingWith(*thing_ptr);
无需修改即可使用事物。
// declaration
void doSomethingWith(const Thing& thing);
// called like this
doSomethingWith(*thing_ptr);
您唯一想在函数签名中提及 unique_ptr
的情况是您要转让所有权:
// declaration
void takeMyThing(std::unique_ptr<Thing> p);
// call site
takeMyThing(std::move(thing_ptr));
您永远不需要这样做:
void useMyThing(const std::unique_ptr<Thing>& p);
之所以这不是一个好主意,是因为它混淆了 useMyThing 的逻辑和所有权的概念,从而缩小了重用的范围。
考虑:
useMyThing(const Thing& thing);
Thing x;
std::unique_ptr<Thing> thing_ptr = makeAThing();
useMyThing(x);
useMyThing(*thing_ptr);
更新:
注意问题的更新 - 存储(非拥有)对此对象的引用。
执行此操作的一种方法确实是存储指针。但是,指针可能会出现逻辑错误,因为它们可以合法地为空。指针的另一个问题是它们不能很好地与 std:: algorithms
和容器一起使用 - 需要自定义比较函数等。
有一种 std::-compliant
方法可以做到这一点 - std::reference_wrapper<>
所以而不是这样:
std::vector<Thing*> my_thing_ptrs;
这样做:
std::vector<std::reference_wrapper<Thing>> my_thing_refs;
由于 std::reference_wrapper<T>
定义了运算符 T&
,您可以在任何需要 T
.
的表达式中使用 reference_wrapped
对象
例如:
std::unique_ptr<Thing> t1 = make_thing();
std::unique_ptr<Thing> t2 = make_thing();
std::unique_ptr<Thing> t3 = make_thing();
std::vector<std::reference_wrapper<const Thing>> thing_cache;
store_thing(*t1);
store_thing(*t2);
store_thing(*t3);
int total = 0;
for(const auto& t : thing_cache) {
total += value_of_thing(t);
}
其中:
void store_thing(const Thing& t) {
thing_cache.push_back(std::cref(t));
}
int value_of_thing(const Thing& t) {
return <some calculation on t>;
}
问题更新后,我更喜欢:
std::reference_wrapper<>
正如 Richard Hodges 所建议的,只要您不需要 NULL 值的选项。
请注意,尽管您的用例似乎确实需要默认构造函数,所以除非您有一些 public 静态实例用作默认构造函数,否则这是不可行的。
一些具有所需语义的自定义 not_your_pointer<T>
类型:默认可构造、可复制和可分配、可从 unique_ptr
转换和非拥有。可能只有当你经常使用它时才值得,因为编写一个新的智能指针 class 需要一些思考。
如果您需要优雅地处理悬挂引用,请使用 std::weak_ptr
并将所有权更改为 shared_ptr
(即只有 one shared_ptr 存在:关于所有权没有歧义并且对象生命周期不受影响)
问题更新前,我更喜欢:
表示所有权不通过引用转移:
void foo(T &obj); // no-one expects this to transfer ownership
称为:
foo(*ptr);
你确实失去了通过 nullptr
的能力,如果这很重要的话。
表示明确禁止不转让所有权:
void foo(std::unique_ptr<T> const &p) {
// can't move or reset p to take ownership
// still get non-const access to T
}
这很清楚,但确实向被调用者公开了您的内存管理策略。
传递原始指针和文档,表明所有权不应转移。这是最弱和最容易出错的。别这样。
不要过分关注来电者的要求。函数需要传入什么?如果它只会在调用期间使用该对象,则使用引用。
void func(const Foo& foo);
如果它需要保留对象的所有权超过其持续时间,则传入 shared_ptr
void func(std::shared_ptr<Foo> foo);
Herb Sutter 在 GotW #91 - Smart Pointer Parameters 中已经涵盖了这一领域。
它也涉及性能方面,而其他答案虽然很棒,但侧重于内存管理。
上面有人建议将智能指针作为常量引用传递 - 在后来的编辑中改变了主意。我们在代码中滥用了它 - 它使签名 'smarter' - 即更复杂但没有好处。最糟糕的是当初级开发人员接手时——他不会像@Michael 和 'learn' 那样质疑如何从一个糟糕的例子中做到这一点。它有效,但是...
对我来说,智能指针参数传递问题非常严重,可以在解释什么是智能指针后立即在任何 book/article/post 中突出提及。
现在想知道 lint-like 程序是否应该将 const 引用传递标记为样式元素。
假设我有一个由 std::unique_ptr
管理的对象。我代码的其他部分需要访问这个对象。传递指针的正确解决方案是什么?我应该只通过 std::unique_ptr::get
传递普通指针还是应该使用并传递 std::shared_ptr
而不是 std::unique_ptr
?
我对 std::unique_ptr
有一些偏好,因为该指针的所有者实际上负责清理。如果我使用共享指针,即使实际上应该销毁对象,也有可能由于共享指针而使对象保持活动状态。
编辑:不幸的是,我忘了提到指针不仅是函数调用的参数,而且还将存储在其他对象中以构建对象的网络结构。我不喜欢共享指针,因为这样就不清楚谁真正拥有该对象了。
通常,您只需将引用或普通指针传递给希望观察对象的代码的其他部分。
通过引用:
void func(const Foo& foo);
std::unique_ptr<Foo> ptr;
// allocate ptr...
if(ptr)
func(*ptr);
通过原始指针传递:
void func(const Foo* foo);
std::unique_ptr<Foo> ptr;
// allocate ptr...
func(ptr.get());
选择将取决于传递空指针的需要。
您有责任通过设计确保观察者在 unique_ptr
被销毁后不使用指针或引用。如果您不能保证,那么您必须使用 shared_ptr
而不是 unique_ptr
。观察者可以持有weak_ptr
表示他们没有所有权。
编辑:即使观察者希望保留指针或引用也是可以的,但这确实让确保在 unique_ptr
被销毁后它不会被使用变得更加困难。
如果托管对象的所有权未被转移(并且因为它是一个 unique_ptr
,所有权不能共享),那么将被调用函数中的逻辑与所有权概念分开更为正确。我们通过引用调用来做到这一点。
这是一种令人费解的说法:
给定:
std::unique_ptr<Thing> thing_ptr;
更改事物:
// declaration
void doSomethingWith(Thing& thing);
// called like this
doSomethingWith(*thing_ptr);
无需修改即可使用事物。
// declaration
void doSomethingWith(const Thing& thing);
// called like this
doSomethingWith(*thing_ptr);
您唯一想在函数签名中提及 unique_ptr
的情况是您要转让所有权:
// declaration
void takeMyThing(std::unique_ptr<Thing> p);
// call site
takeMyThing(std::move(thing_ptr));
您永远不需要这样做:
void useMyThing(const std::unique_ptr<Thing>& p);
之所以这不是一个好主意,是因为它混淆了 useMyThing 的逻辑和所有权的概念,从而缩小了重用的范围。
考虑:
useMyThing(const Thing& thing);
Thing x;
std::unique_ptr<Thing> thing_ptr = makeAThing();
useMyThing(x);
useMyThing(*thing_ptr);
更新:
注意问题的更新 - 存储(非拥有)对此对象的引用。
执行此操作的一种方法确实是存储指针。但是,指针可能会出现逻辑错误,因为它们可以合法地为空。指针的另一个问题是它们不能很好地与 std:: algorithms
和容器一起使用 - 需要自定义比较函数等。
有一种 std::-compliant
方法可以做到这一点 - std::reference_wrapper<>
所以而不是这样:
std::vector<Thing*> my_thing_ptrs;
这样做:
std::vector<std::reference_wrapper<Thing>> my_thing_refs;
由于 std::reference_wrapper<T>
定义了运算符 T&
,您可以在任何需要 T
.
reference_wrapped
对象
例如:
std::unique_ptr<Thing> t1 = make_thing();
std::unique_ptr<Thing> t2 = make_thing();
std::unique_ptr<Thing> t3 = make_thing();
std::vector<std::reference_wrapper<const Thing>> thing_cache;
store_thing(*t1);
store_thing(*t2);
store_thing(*t3);
int total = 0;
for(const auto& t : thing_cache) {
total += value_of_thing(t);
}
其中:
void store_thing(const Thing& t) {
thing_cache.push_back(std::cref(t));
}
int value_of_thing(const Thing& t) {
return <some calculation on t>;
}
问题更新后,我更喜欢:
std::reference_wrapper<>
正如 Richard Hodges 所建议的,只要您不需要 NULL 值的选项。请注意,尽管您的用例似乎确实需要默认构造函数,所以除非您有一些 public 静态实例用作默认构造函数,否则这是不可行的。
一些具有所需语义的自定义
not_your_pointer<T>
类型:默认可构造、可复制和可分配、可从unique_ptr
转换和非拥有。可能只有当你经常使用它时才值得,因为编写一个新的智能指针 class 需要一些思考。如果您需要优雅地处理悬挂引用,请使用
std::weak_ptr
并将所有权更改为shared_ptr
(即只有 one shared_ptr 存在:关于所有权没有歧义并且对象生命周期不受影响)
问题更新前,我更喜欢:
表示所有权不通过引用转移:
void foo(T &obj); // no-one expects this to transfer ownership
称为:
foo(*ptr);
你确实失去了通过
nullptr
的能力,如果这很重要的话。表示明确禁止不转让所有权:
void foo(std::unique_ptr<T> const &p) { // can't move or reset p to take ownership // still get non-const access to T }
这很清楚,但确实向被调用者公开了您的内存管理策略。
传递原始指针和文档,表明所有权不应转移。这是最弱和最容易出错的。别这样。
不要过分关注来电者的要求。函数需要传入什么?如果它只会在调用期间使用该对象,则使用引用。
void func(const Foo& foo);
如果它需要保留对象的所有权超过其持续时间,则传入 shared_ptr
void func(std::shared_ptr<Foo> foo);
Herb Sutter 在 GotW #91 - Smart Pointer Parameters 中已经涵盖了这一领域。 它也涉及性能方面,而其他答案虽然很棒,但侧重于内存管理。
上面有人建议将智能指针作为常量引用传递 - 在后来的编辑中改变了主意。我们在代码中滥用了它 - 它使签名 'smarter' - 即更复杂但没有好处。最糟糕的是当初级开发人员接手时——他不会像@Michael 和 'learn' 那样质疑如何从一个糟糕的例子中做到这一点。它有效,但是...
对我来说,智能指针参数传递问题非常严重,可以在解释什么是智能指针后立即在任何 book/article/post 中突出提及。
现在想知道 lint-like 程序是否应该将 const 引用传递标记为样式元素。