我有一些关于使用指向基类的shared_ptr的问题。他们的答案相互影响,对于这三个问题,我都需要相同的代码片段来以尽可能小的方式设置上下文,就像这样(所有问题都与Foo
及其held_object_
有关):
#include <memory>
#include <utility>
class Bar // interface for Scene (see linked question)
{
public:
virtual ~Bar() = 0;
// more virtual methods to work with Scene
};
Bar::~Bar() = default;
class Baz : public Bar // interface for Foo
{
public:
virtual ~Baz() = 0;
// more virtual methods to work with Foo
};
Baz::~Baz() = default;
class ConcreteBaz : public Baz // actual thing I'm acquiring and releasing
{
// overrides and whatnot
};
class Foo
{
public:
void acquire(const std::shared_ptr<Baz>& object) {};
void release(/* actually takes a place to release into */) {};
private:
std::shared_ptr<Baz> held_object_;
};
int main()
{
auto foo = std::make_unique<Foo>();
auto p_foo = foo.get();
while (/* condition */)
{
auto cbaz = std::make_shared<ConcreteBaz>();
// Scene gets a copy here
p_foo->acquire(cbaz);
// do physical things to cbaz
p_foo->release(/* actually takes a place to release into */);
// do other physical things, then acquire cbaz again
p_foo->acquire(cbaz);
// do physical things to cbaz
p_foo->release(/* actually takes a place to release into */);
}
}
正如您所看到的,cbaz
是一个指向多态层次结构的指针,它可以同时使用Scene
和Foo
。ConcreteBaz
实际上代表了一个物理实体,正如代码中所述,Foo
可以拥有它(它是shared_ptr
,因为Scene
和Foo
都拥有它。我在这里删除了Scene
的详细信息,因为它是OT)。
问题
1)如何初始化held_object_
?我可以用一次分配吗
事实上,foo
在(物理上)获得cbaz
之前并不拥有它,因此构造函数应该将指针初始化为nullptr。最初我想按照这个使用make_shared
,但我在这里读到它的值初始化,这意味着如果我要进行
Foo::Foo()
: held_object_ {std::make_shared<Baz>()} // equivalent to std::make_shared<Baz>(nullptr)
{
}
编译器会向CCD_ 16抱怨。因此,这个将成为这个问题的副本,但接受的答案要么使shared_ptr
未初始化,要么创建另一个unique_ptr
……基本上不清楚我将如何在这里应用它,我认为我不能在构造函数中调用reset
(?)。
我应该明确地说吗
Foo::Foo()
: held_object_ {std::shared_ptr<Baz> {nullptr}}
{
}
而不关心CCD_ 20避免的双重分配?在这一点上,它不是几乎完全等同于: held_object_ {}
吗(将ctor与默认ctor放在一边)?编辑:我也可以像make_shared
那样用单个分配吗?
2)如何管理held_object_
现在,在调用acquire
之后,为了获得cbaz
的所有权,我最终调用了方法
void Foo::setHeldObject_(std::shared_ptr<Baz> object)
{
this->held_object_ = std::move(object);
}
然而,在release
中,我将不得不销毁拥有的shared_ptr
(然后在下一个循环中再次设置它),并使我的Foo
实例的状态与其物理状态IRL一致。这让我想到了使用std::shared_ptr::reset(甚至在读取之前的相关答案),因为如果我调用setHeldObject()
并设置cbaz
,我会用nullptr替换指针。然而,我无法将方法主体的正确语法理解为:
this->held_object_.reset(object);
显然是错误的,因为我想管理指针对象,而不是指针(因此不编译)this->held_object_.reset(&object);
看起来不对,产生error: cannot convert ‘std::shared_ptr<Baz>*’ to ‘Baz*’ in initialization
this->held_object_.reset(object.get());
似乎也错了,因为我不认为我会以这种方式提升user_count
(不过它确实会编译)
EDIT:我认为应该可以用相同的方法调用来完成,不管是否给出参数。reset
可能吗?
3)实际上只有两个所有者
我在这里写的main
函数实际上是Process
类的run
方法,但这样定义它会使use_count在Scene
和Foo
拥有自己的shared_ptr
时变为3。从我make_shared
开始,我就处于一个循环中,而且逻辑上说应该只有两个所有者。这是weak_ptr
的一个用例吗?在这个用例中,做一些类似的事情是有意义的吗
int main()
{
auto foo = std::make_unique<Foo>();
auto p_foo = foo.get();
while (/* condition */)
{
auto cbaz = std::make_shared<ConcreteBaz>();
auto p_cbaz = std::weak_ptr<ConcreteBaz> {cbaz};
// Scene gets a && via std::move here
p_foo->acquire(p_cbaz.lock()); // this instantly gets converted to a shared_ptr EDIT: BUT IT DOESN'T INCREASE THE USE COUNT
// do physical things to cbaz
p_foo->release(/* actually takes a place to release into */);
// do other physical things, then acquire cbaz again
p_foo->acquire(p_cbaz.lock()); // this instantly gets converted to a shared_ptr and would be nullptr had I moved in the first acquire call EDIT: BUT IT DOESN'T INCREASE THE USE COUNT
// do physical things to cbaz
p_foo->release(/* actually takes a place to release into */);
}
}
或者有更好的方法吗?
如何初始化
held_object_
如果你想让held_object_
成为nullptr
,那么你什么都不用做。编译器生成的Foo
的默认构造函数将调用held_object_
的默认构造函数,并将其保留为空指针。
如何管理
held_object_
当您需要释放指针的所有权时,您只需调用reset
,而不需要任何参数。这有效地完成了shared_ptr().swap(*this);
,它给您留下了一个空指针。
1)如何初始化held_object_?
你可以这样做:
Foo::Foo()
: held_object_ {nullptr}
{
}
但这当然相当于: held_object_ {}
,尽管你的意图稍微明确一些。另一种选择是采用
class Foo
{
// ...
private:
std::shared_ptr<Baz> held_object_ = nullptr;
};
2)如何管理held_object_?
您使用std::move
的第一个版本很好。请注意,您只能通过这种方式参与所有权,而不能获得独占所有权(这就是为什么我不将方法命名为acquire
)。
3)实际上只有两个所有者
我不认为你能得到这个问题的真正答案,因为它既取决于你的确切情况,也(即使考虑到这一点)有点基于意见。此外,除非你真的是为了表现,否则不要为使用次数而烦恼。因为担心一些微小的性能细节(shared_ptr
中的引用计数成本)而编写不同的代码是过早的优化,除非你有充分的理由相信(或证明)这将是一个问题。你这样做是在一个内部循环中吗?如果没有,就不会感觉到虚假的上/下refcount。