Reputation: 63
I am trying to achieve that certain objects within my application can only be constructed as shared_ptr's by a call to a static method called "create".
Of course I could do this by directly adding the static 'create' method to all the respective class. However, this would mean I have to repeat very similar code in almost all my classes. A macro would work, but I do not find this very elegant.
I came up with an alternative way of doing this by deriving all classes from a templated 'BaseObject' class that implements the 'create' method and returns the pointer. This almost works, except that std::make_shared cannot access the constructor of its child class when it is protected.
The non-solution would be to make the child class constructor public (see (1) in the example below). But now Foo can be normally constructed again and this would defeat the entire point. An alternative solution would be to friend BaseObject in the child class and make use of shared_ptr directly (see (2) in the example).
Both solutions put extra burden on the implementer of the child class. Since they have to either find an alternative way of making the constructor non-public or put a friend declaration. The (2) solution has the additional problem of not being able to use the more efficient make_shared.
My question: is there a better way of doing this?
template<class T>
class BaseObject
{
public:
typedef std::shared_ptr<T> SharedPtr;
template<class... Args>
static typename BaseObject<T>::SharedPtr create(Args&&... args)
{
return std::make_shared<T>(args...);
//return std::shared_ptr<T>(new T(args...)); (2)
}
};
class Foo : public BaseObject<Foo>
{
//friend BaseObject<Foo>; (2)
protected: //public (1)
Foo(int a = 0) : m_foo(a) {}
private:
int m_foo;
};
int main(int argc, char* argv[])
{
Foo::SharedPtr bla = Foo::create(1);
return 0;
}
Update:
They pass-key idiom seems to provide the best solution for me at this moment:
template<class T>
class BaseObject
{
public:
typedef std::shared_ptr<T> SharedPtr;
class Key
{
friend class BaseObject<T>;
Key() {}
};
template<class... Args>
static typename BaseObject<T>::SharedPtr create(Args&&... args)
{
return std::make_shared<T>(Key(), args...);
}
};
class Foo : public BaseObject<Foo>
{
public:
Foo(BaseObject<Foo>::Key, int a = 0) : m_foo(a) {}
private:
int m_foo;
};
The good things:
The only problem with this solution is the requirement to have 'Key' as a first argument in the constructor. But I can live with that.
Upvotes: 3
Views: 2509
Reputation: 2255
Better is subjective, but I believe it would be a little more intuitive if you would make your constructor private, and std::make_shared
a friend function. This way the only function that could create your object would be std::make_shared
, and you could write
std::shared_ptr<Foo> ptr = std::make_shared<Foo>(12);
instead of:
Foo::SharedPtr bla = Foo::create(1);
So any future reader of your code would understand what you mean withou actually looking at the Foo
class.
UPDATE
I have tried out what I wrote, but did not really work. Here is an answer for a similar question instead, which most likely also aplies for your question:
Using make_shared with a protected constructor + abstract interface
UPDATE 2
Here is how you can make it work (VC++2013)
#include <memory>
using namespace std;
class Foo
{
protected:
Foo(int a = 0) : m_foo(a) {}
private:
int m_foo;
friend shared_ptr<Foo> make_shared<>();
friend class _Ref_count_obj<Foo>;
};
int main()
{
shared_ptr<Foo> foo = make_shared<Foo, int>(12);
return 0;
}
_Ref_count_obj
is internally used by make_shared
, that's why you need to befriend that too.
Upvotes: 2