Creating objects only as shared pointers via the create-base method

I am trying to achieve that some objects in my application can only be created as shared_ptr by calling a static method called "create".

Of course, I could do this by adding a static "create" method to all relevant classes. However, this would mean that I have to repeat very similar code in almost all of my classes. The macro will work, but I don't find it very elegant.

I came up with an alternative way to do this, inferring all classes from the BaseObject templated class, which implements the create method and returns a pointer. This almost works, except that std :: make_shared cannot access the constructor of its child class when protected.

The non-solution would be to make the constructor of the child class public (see (1) in the example below). But now Foo can usually be built again and that wins the whole point. An alternative solution would be to find BaseObject in a child class and use shared_ptr directly (see (2) in the example).

Both solutions put additional burden on the child class executor. Because they have to either find an alternative way to make the constructor non-public, or put a friend's declaration. Solution (2) has the additional problem of not being able to use the more efficient make_shared.

My question is, is there a better way to do 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:

The pass-key idiom seems to be the best solution for me at the 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;
};

      

Good things:

  • It is only possible to create a Foo object as a shared_ptr via Foo :: create.
  • No need to add complex declarations to Foo.
  • std :: make_shared still works.

The only problem with this solution is the requirement to have the "key" as the first argument in the constructor. But I can live with it.

+3


source to share


1 answer


Better subjective, but I think it would be a little intuitive if you made your constructor private, but std::make_shared

a friend function. So 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:

Foo::SharedPtr bla = Foo::create(1);

      

This way, any future reader of your code would understand what you mean by looking at the class Foo

.

UPDATE



I tried what I wrote but didn't actually work. Here's an answer to a similar question that most likely works for your question as well:

Using make_shared with protected constructor + abstract interface

UPDATE 2

This is how you can get it to 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

used internally make_shared

, so you need to make friends too.

+2


source







All Articles