Reputation: 13770
Usually I prefer returning unique_ptr
from Factories.
Recently I came to the problem of returning a unique_ptr
for a class that inherits enable_shared_from_this
. Users of this class may accidentally cause a call to shared_from_this()
, though it is not owned by any shared_ptr
, which results with a std::bad_weak_ptr
exception (or undefined behavior until C++17, which is usually implemented as an exception).
A simple version of the code:
class Foo: public enable_shared_from_this<Foo> {
string name;
Foo(const string& _name) : name(_name) {}
public:
static unique_ptr<Foo> create(const string& name) {
return std::unique_ptr<Foo>(new Foo(name));
}
shared_ptr<Foo> get_shared() {return shared_from_this();}
void doIt()const {cout << "Foo::doIt() <" << name << '>' << endl;}
virtual ~Foo() {cout << "~Foo() <" << name << '>' << endl;}
};
int main() {
// ok behavior
auto pb1 = Foo::create("pb1");
pb1->doIt();
shared_ptr<Foo> pb2 = shared_ptr<Foo>(std::move(pb1));
shared_ptr<Foo> pb3 = pb2->get_shared();
pb3->doIt();
// bad behavior
auto pb4 = Foo::create("pb4");
pb4->doIt();
shared_ptr<Foo> pb5 = pb4->get_shared(); // exception
pb5->doIt();
}
A possible solution is to change the factory method to return shared_ptr
but this is not what I'm looking for, as in many cases there is actually no need for sharing and this will make things less efficient.
The question is how to achieve all of the following:
unique_ptr
unique_ptr
of this class to become sharedshared_ptr
of this class to get shared copies (via shared_from_this()
)unique_ptr
of this class tries to get shared from this (calling get_shared
in above example)Items 1 to 3 are fulfilled with the code above, the problem is with item 4.
Upvotes: 6
Views: 3155
Reputation: 1
An old thread already, but I stumbled to ponder a question if its always quaranteed that weak_from_this().expired()==true
or weak_from_this().use_count() == 0
for an object not managed by shared_ptr
- including unique_ptr
. If yes, then simple answer to original question could be to realize non-throwing version of shared_from_this() via such checks for objects returned from factory.
Upvotes: 0
Reputation: 62884
Deriving from enable_shared_from_this
is a promise that your instances are owned by a (group of) shared_ptr
. Don't lie about this, only ever create shared_ptr<Foo>
and never hand out unique_ptr<Foo>
.
It's not worth the future pain when you have to disentangle the "safe" uses of unique_ptr<Foo>
from the case where a Foo &
deep in some logic that wants to call shared_from_this
but sometimes is actually a unique_ptr<Foo>
.
Upvotes: 2
Reputation: 4367
Use multiple static factory functions and conversion functions. To address your comments, I've added get_shared
to support copying a shared pointer. This compiles and is available here: http://ideone.com/UqIi3k
#include <iostream>
#include <memory>
class Foo
{
std::string name;
Foo(const std::string& _name) : name(_name) {}
public:
void doIt() const { std::cout << "Foo::doIt() <" << name << '>' << std::endl;}
virtual ~Foo() { std::cout << "~Foo() <" << name << '>' << std::endl;}
static std::unique_ptr<Foo> create_unique(const std::string & _name) {
return std::unique_ptr<Foo>(new Foo(_name));
}
static std::shared_ptr<Foo> create_shared(const std::string & _name) {
return std::shared_ptr<Foo>(new Foo(_name));
}
static std::shared_ptr<Foo> to_shared(std::unique_ptr<Foo> &&ptr ) {
return std::shared_ptr<Foo>(std::move(ptr));
}
static std::shared_ptr<Foo> get_shared(const std::shared_ptr<Foo> &ptr) {
return std::shared_ptr<Foo>(std::move(ptr));
}
};
int main() {
// ok behavior
auto pb1 = Foo::create_unique("pb1");
pb1->doIt();
std::shared_ptr<Foo> pb2 = Foo::get_shared(std::move(pb1));
//note the change below
std::shared_ptr<Foo> pb3 = Foo::get_shared(pb2);
pb3->doIt();
// also OK behavior
auto pb4 = Foo::create_unique("pb4");
pb4->doIt();
std::shared_ptr<Foo> pb5 = Foo::to_shared(std::move(pb4)); // no exception now
pb5->doIt();
std::shared_ptr<Foo> pb6 = Foo::create_shared("pb6");
pb6->doIt();
std::shared_ptr<Foo> pb7 = std::shared_ptr<Foo>(pb5);
pb7->doIt();
return 0;
}
Upvotes: 1
Reputation: 27365
That's exactly what I was looking for, making sure that clients may reach a point that sharing is required and it will work transparently without really caring if they are with a shared Pet or with a unique Pet (i.e. making the interface easy to use correctly etc.).
It sounds like the x-y problem to me.
To "make sure that clients can share if required", turn this into a separate tool and put this in your toolset (edit: but it still feels like you have the x-y problem):
namespace tools
{
/// @brief hides the details of sharing a unique pointer
/// behind a controlled point of access
///
/// to make sure that clients can share if required, use this as a
/// return type
template<typename T>
class pointer final
{
public:
// @note: implicit on purpose (to enable construction on return,
// in the client code below)
pointer(std::unique_ptr<T> value);
// @note: implicit on purpose (to enable construction on return,
// in the client code below)
pointer(std::shared_ptr<T> value);
T* const operator->() const { return get(); }
/// @note copy&swap
pointer& operator=(pointer p)
{
using std::swap;
swap(value1, p.value1);
swap(value2, p.value2);
return *this;
}
// example copy
pointer(const pointer<T>& value)
: value1{}, value2{ value.share() }
{
}
// example move
pointer(pointer<T>&& tmp)
: value1{ std::move(tmp.value1) }, value2{ std::move(tmp.value2) }
{
}
/// @note conceptually const, because it doesn't change the address
/// it points to
///
/// @post internal implementation is shared
std::shared_ptr<T> share() const
{
if(value2.get())
return value2;
value2.reset(value1.release());
return value2;
}
T* const get() const
{
if(auto p = value1.get())
return p;
return value2;
}
private:
mutable std::unique_ptr<T> value1;
mutable std::shared_ptr<T> value2;
};
}
Your client code then becomes:
class Foo
{
string name;
Foo(const string& _name) : name(_name) {}
public:
using pointer = tools::pointer<Foo>;
static pointer make_unique(const string& name)
{
return std::make_unique<Foo>(name);
}
void doIt()const {cout << "Foo::doIt() <" << name << '>' << endl;}
virtual ~Foo() {cout << "~Foo() <" << name << '>' << endl;}
};
int main() {
// ok behavior
auto pb1 = Foo::make_unique("pb1");
pb1->doIt(); // call through unique pointer
auto pb2 = pb1.share(); // get copy of shared pointer
auto pb3 = pb1; // get copy of shared pointer
auto pb4 = std::move(pb1); // move shared pointer
}
Upvotes: 1
Reputation: 99
May be we can template check of the calling variable ::
class Foo : public enable_shared_from_this<Foo> {
string name;
Foo(const string& _name) : name(_name) {}
public:
static unique_ptr<Foo> create(const string& name) {
return std::unique_ptr<Foo>(new Foo(name));
}
template <typename T>
shared_ptr<Foo> get_shared() { return shared_ptr<Foo>(); }
template <>
shared_ptr<Foo> get_shared<unique_ptr<Foo>>() { return shared_ptr<Foo>(); }
template <>
shared_ptr<Foo> get_shared<shared_ptr<Foo>>() { return shared_from_this(); }
void doIt()const { cout << "Foo::doIt() <" << name << '>' << endl; }
virtual ~Foo() { cout << "~Foo() <" << name << '>' << endl; }
};
int main()
{
// ok behavior
auto pb1 = Foo::create("pb1");
pb1->doIt();
shared_ptr<Foo> pb2{ std::move(pb1) };
shared_ptr<Foo> pb3 = pb2->get_shared<decltype(pb2)>();
pb3->doIt();
// bad behavior
auto pb4 = Foo::create("pb4");
pb4->doIt();
shared_ptr<Foo> pb5 = pb4->get_shared<decltype(pb4)>(); // exception
if (pb5 != nullptr)
pb5->doIt();
return 0;
}
I am not sure if that is exactly what you want, but might solve the point 4 you have mentioned.
Upvotes: -1
Reputation: 69892
As you will have found, once an object is managed by a shared_ptr
, there is no (safe) way to unshare it. This is by design, since once the lifetime of the object is shared, no one owner can guarantee that it will ever be the sole owner again (except during the execution of the deleter - I'll leave you to reason about whether that's useful to you).
However, the standard library does allow you to specialise any std class template provided it's specialised for a user defined class.
So what you can do is this:
namespace std {
template<class Deleter>
struct unique_ptr<Foo, Deleter> {
static_assert(!std::is_same<Deleter, Deleter>::value, "sorry, not allowed");
// or
static_assert(!sizeof(Deleter), "sorry, not allowed");
// (thanks to Jarod42 && md5i for cleaning up after me)
};
}
And now no unique_ptr can ever own your object (which is clearly designed to be owned only by a shared_ptr).
This is enforced at compile time.
Upvotes: 0
Reputation: 13770
The problem with the member function get_shared
in the question is that it allows calls by both unique_ptr
and shared_ptr
with a difficult to distinguish between the two, thus unique_ptr
is allowed to call this method and fails.
Moving the get_shared
to be a static method which gets the pointer to share from, allows the distinguishing between unique and share which fixes this problem:
class Foo: public enable_shared_from_this<Foo> {
string name;
Foo(const string& _name) : name(_name) {}
public:
static unique_ptr<Foo> create(const string& name) {
return std::unique_ptr<Foo>(new Foo(name));
}
static shared_ptr<Foo> get_shared(unique_ptr<Foo>&& unique_p) {
return shared_ptr<Foo>(std::move(unique_p));
}
static shared_ptr<Foo> get_shared(const shared_ptr<Foo>& shared_p) {
return shared_p->shared_from_this();
}
void doIt()const {cout << "Foo::doIt() <" << name << '>' << endl;}
virtual ~Foo() {cout << "~Foo() <" << name << '>' << endl;}
};
int main() {
// ok behavior - almost as before
auto pb1 = Foo::create("pb1");
pb1->doIt();
shared_ptr<Foo> pb2 = shared_ptr<Foo>(std::move(pb1));
shared_ptr<Foo> pb3 = Foo::get_shared(pb2);
pb3->doIt();
// ok behavior!
auto pb4 = Foo::create("pb4");
pb4->doIt();
shared_ptr<Foo> pb5 = Foo::get_shared(std::move(pb4));
pb5->doIt();
}
Code: http://coliru.stacked-crooked.com/a/7fd0d462ed486c44
Upvotes: 2