Access to protected constructor from static factory method in base class

728 Views Asked by At

I've got a static factory method in a base class. Because of some reasons I want to each derived class will be instantiated by this factory method hence all these classes have protected ctors.

In real situation the Create function does more additional logic along with error handling.

class Base
{
public:
    virtual ~Base() {}

    template <typename T>
    static void Create(std::unique_ptr<T>& pOut)
    {        
        pOut = std::unique_ptr<T>(new T);
        // ... 
    }
protected:
    Base() {}
};

class Derived : public Base
{
protected:
    Derived() {}
};

int main()
{
    std::unique_ptr<Derived> ptr;
    Derived::Create(ptr);
}

That code obviously doesn't compile since we don't have access to the protected ctor.

prog.cc: In instantiation of 'static void Base::Create(std::unique_ptr<_Tp>&) [with T = Derived]':
prog.cc:33:24:   required from here
prog.cc:17:35: error: 'Derived::Derived()' is protected within this context
   17 |         pOut = std::unique_ptr<T>(new T);
      |                                   ^~~~~
prog.cc:26:5: note: declared protected here
   26 |     Derived() {}
      |     ^~~~~~~

The first solution that seems to be the most common is friend declaration in derived class. However it works I don't like it because:

  1. I have to add such declaration in each derived class
  2. This is a friend
class Derived : public Base
{
protected:
    Derived() {}
    friend void Base::Create<Derived>(std::unique_ptr<Derived>&);
};

Thinking about more generic approach I was trying something like this:

 template <typename T>
    static void Create(std::unique_ptr<T>& pOut)
    {
        static_assert(std::is_base_of_v<Base, T>, "T should be Base-family class.");
        class CreateHelper : public T
        {
            public:
                static void InternalCreate(std::unique_ptr<T>& pOut)
                {
                    pOut = std::unique_ptr<CreateHelper>(new CreateHelper);
                    // ... 
                }
        };
        
        CreateHelper::InternalCreate(pOut);
    }

It works but looks weird to me and:

  1. The real pointer type is CreateHelper however outside this function we don't see that
  2. This approach needs that Base-familiy should be polimorphic since we use pointer to base class (it's seems this condition should be always met but still it's worth to mention about that)

My questions are

  1. What do you think about the last approach?
  2. Is it considered as a bad design?
1

There are 1 best solutions below

6
On

In general, a far better approach here is to simply refer to simple construction helper classes, then you can refer to make_unique() too:

class Base
{
protected:
    struct Accessor
    {
      explicit Accessor() = default;
    };
 
public:
    Base(Accessor) {}
    virtual ~Base() {}

    template <typename T>
    static void Create(std::unique_ptr<T>& pOut)
    {        
        pOut = std::make_unique<T>(Accessor());
        // ... 
    }
};

class Derived : public Base
{
public:
    Derived(Accessor) : Base(Accessor()) {}
};

Only drawback: Derived classes have to adapt their constructor(s) accordingly.

A general point: A factory should almost always be aware of its relevant types in general, at least of partial aspects of the relevant types, provided by polymorphism (interfaces) or/and via traits. So I think, this is a bit more convenient:

template <class T, typename std::enable_if<std::is_base_of<Base, T>::value>::type* = nullptr>
static void Create(std::unique_ptr<T>& pOut)
{        
    pOut = std::make_unique<T>(Accessor());
    // ... 
}

Further on: You might have to rethink about your general creation design here. The common approach here is to return the created object, not to fill a reference. With your current approach, you have to think about exception safety (contracts..) here at least twice for instance...

Possible approach:

template <class T, typename std::enable_if<std::is_base_of<Base, T>::value>::type* = nullptr>
static std::unique_ptr<T> Create()
{        
    auto pOut = std::make_unique<T>(Accessor());
    // ... 

    return pOut;
}