I have some questions related to the use of a shared_ptr pointing to a base class. Their answers influence each other, and for all three I need the same code snippet to set the context in as minimal a way as possible, like this (all the questions relate to Foo
and its 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 */);
}
}
As you can see cbaz
is a pointer to a polymorphic hierarchy that can work with both Scene
and Foo
. ConcreteBaz
actually represents a physical entity which a Foo
can, as stated in the code, take ownership of (it's a shared_ptr
because both Scene
and Foo
own it, as per this. I removed details of Scene
here because it's OT).
Questions
1) How do I initialize held_object_
? Can I do it with a single allocation?
In reality, foo
doesn't own cbaz
until it (physically) gets it, therefore the constructor should initialize the pointer to nullptr. Initially I wanted to use make_shared
as per this but I read here that it value-initializes, meaning if I were to do
Foo::Foo()
: held_object_ {std::make_shared<Baz>()} // equivalent to std::make_shared<Baz>(nullptr)
{
}
the compiler would complain with error: invalid new-expression of abstract class type
. This would thus become a duplicate of this question but the accepted answer either leaves the shared_ptr
uninitialized or creates a further unique_ptr
to... basically it's unclear how I would apply it here, and I don't think I can call reset
in the constructor (?).
Should I instead be explicit and say
Foo::Foo()
: held_object_ {std::shared_ptr<Baz> {nullptr}}
{
}
without caring for the double allocation which make_shared
avoids? At this point wouldn't it be almost exactly identical to just : held_object_ {}
(move ctor vs. default ctor aside)?
EDIT: Can I do it with a single allocation as well, like make_shared
does?
2) How do I manage held_object_
?
Right now to get ownership of cbaz
after calling acquire
I eventually get into a call for method
void Foo::setHeldObject_(std::shared_ptr<Baz> object)
{
this->held_object_ = std::move(object);
}
however in release
I will have to destroy the owning shared_ptr
(to then set it again come next loop) and make the state of my Foo
instance coherent with its physical state IRL. This had me thinking of using std::shared_ptr::reset (before even reading the previous related answer) because I would replace the pointee with nullptr if I called setHeldObject()
and set cbaz
otherwise. However I can't figure the correct syntax to the method's body as:
this->held_object_.reset(object);
is obviously wrong, as I want to manage the pointee, not the pointer (and doesn't therefore compile);this->held_object_.reset(&object);
looks wrong, and yieldserror: cannot convert ‘std::shared_ptr<Baz>*’ to ‘Baz*’ in initialization
this->held_object_.reset(object.get());
also seems wrong because I don't think I'm upping theuser_count
this way (it does compile though).
EDIT: I believe it should be possible to do it with the same method call, giving or not giving the argument. Would that be possible with reset
?
3) There are really only two owners
The main
function I wrote here is actually a Process
class' run
method, but having it defined like this makes it so the use_count bumps to 3 by the time Scene
and Foo
have their own shared_ptr
. From when I make_shared
onwards, I'm inside a loop, moreover the logic says there should be only two owners. Is this a use case for weak_ptr
where it could make sense to do something like:
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 */);
}
}
or is there a better way?
If you want
held_object_
to benullptr
then you don't have to do anything. The compiler generated default constructor ofFoo
will callheld_object_
s default constructor leaving it as a null pointer.When you need to release the ownership of the pointer you just call
reset
without any parameters. That effectively doesshared_ptr().swap(*this);
which leaves you with a null pointer.