假设我有一个由std::unique_ptr
管理的对象。我的代码的其他部分需要访问此对象。传递指针的正确解决方案是什么?我应该只通过std::unique_ptr::get
传递普通指针,还是应该使用并传递std::shared_ptr
而不是std::unique_ptr
?
我对std::unique_ptr
有一些偏好,因为该指针的所有者实际负责清理。如果我使用共享指针,那么即使它实际上被销毁,对象仍有可能由于共享指针而保持活动状态。
编辑:不幸的是,我忘了提到指针不仅仅是函数调用的参数,而是存储在其他对象中以构建对象的网络结构。我不喜欢共享指针,因为它不再清楚,谁拥有该对象。
答案 0 :(得分:18)
如果未传输托管对象的所有权(并且因为它是unique_ptr
,则无法共享所有权)那么将被调用函数中的逻辑与所有权概念分开更为正确。我们通过引用来调用。
这是一种令人费解的说法:
假设:
std::unique_ptr<Thing> thing_ptr;
改变事情:
// declaration
void doSomethingWith(Thing& thing);
// called like this
doSomethingWith(*thing_ptr);
使用Thing而不修改它。
// 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&
,因此您可以在任何期望reference_wrapped
的表达式中使用T
对象。
例如:
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>;
}
答案 1 :(得分:6)
通常,您只需将引用或普通指针传递给希望观察对象的代码的其他部分。
通过引用传递:
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
被销毁后不会使用它。
答案 2 :(得分:4)
不要过分关注来电者的要求。该功能需要传递什么?如果它只在呼叫期间使用该对象,则使用引用。
void func(const Foo& foo);
如果需要保留对象超过其持续时间的所有权,则传入shared_ptr
void func(std::shared_ptr<Foo> foo);
答案 3 :(得分:3)
自问题更新以来,我更喜欢:
std::reference_wrapper<>
,只要你不要求选择NULL值。
请注意,您的用例似乎确实需要一个默认构造函数,所以除非您有一些公共静态实例用作默认值,否则这样做。
一些具有所需语义的自定义not_your_pointer<T>
类型:默认可构造,可复制和可分配,可从unique_ptr
转换和非拥有。如果您经常使用它,可能也是值得的,因为编写一个新的智能指针类需要一些思考。
如果您需要优雅地处理悬空引用,请使用std::weak_ptr
并将所有权更改为shared_ptr
(也就是说,只有一个 shared_ptr存在:那里&#39 ;没有关于所有权和对象生命周期的歧义不受影响)
在更新问题之前,我更喜欢:
表示通过传递引用不转移所有权:
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
}
这很清楚,但确实会将您的内存管理策略暴露给被调用方。
传递一个原始指针和文档,不应转移。这是最弱的,最容易出错的。不要这样做。
答案 4 :(得分:0)
Herb Sutter已在GotW #91 - Smart Pointer Parameters中介绍了这一理由。 它也触及了性能方面,而另一方面则很好地关注内存管理。
上面有人建议将智能指针作为const引用传递 - 在以后的编辑中改变了主意。我们有代码,这被滥用 - 它使签名更聪明&#39; - 即更复杂但没有任何好处。最糟糕的是,当一个初级开发人员接管时 - 他不会质疑@Michael和学习&#39;如何从一个糟糕的例子做到这一点。它有效,但是......
对我而言,智能指针参数传递问题非常严重,可以在解释智能指针后立即在任何书籍/文章/帖子中突出显示。
现在想知道类似lint的程序是否应该将const引用作为样式元素传递。