A common problem that I have come across exists when attempting to create shared pointers of objects which have protected constructors. The following class design details a class that can only be accessed through a shared reference.
class A
{
protected:
A(int foo)
: _foo(foo)
{
}
public:
static std::shared_ptr<A> Create(int foo)
{
return std::shared_ptr<A>(new A(foo));
}
int _foo;
};
However, there is a problem with this implementation. The STL's smart pointers point to the usage of std::make_shared
and std::make_unique
for the instantiation of smart pointers, since these function calls perform one heap allocation instead of two when contrasted with std::shared_ptr<A>(new A())
.
However this opens a can of worms, since the std::make_*
function calls cannot access private or protected constructors of an object they are instantiating, which is a desired property in certain designs. So simply substituting the std::shared_ptr<A>(new A())
call above with a std::make_shared
will not compile. One solution, offered by this post is delineated below.
class A
{
protected:
A(int foo)
: _foo(foo)
{
}
public:
static std::shared_ptr<A> CreateWithBoilerplate(int foo)
{
struct BoilerplateCreator : public A
{
BoilerplateCreator(int foo)
: A(foo)
{
}
};
return std::make_shared<BoilerplateCreator>(foo);
}
int _foo;
};
As you can see, the boiler-plate present in this solution is substantial, and grows with the number of arguments in the function signiature of A.
I would like to propose a general solution to this design paradigm:
template <typename T>
class Creator : public T
{
public:
template <typename... Args>
Creator(Args... args)
: T(std::move(args)...)
{
}
};
class A
{
protected:
A(int foo)
: _foo(foo)
{
}
public:
static std::shared_ptr<A> CreateWithoutBoilerPlate(int foo)
{
return std::make_shared<Creator<A>>(foo);
}
int _foo;
};
Here we essentially provide a solution to elide the privacy of the constructor of the class used within the Creator
template. This solution inspired by the curiously recurring template pattern. Inheritance from a template argument is used to remove the need for a new class definition with each definition of a creator method.