1

我在基类中有一个静态工厂方法。由于某些原因,我希望每个派生类都将由该工厂方法实例化,因此所有这些类都具有受保护的 ctor。

在实际情况下,Create 函数会执行更多附加逻辑以及错误处理。

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);
}

该代码显然无法编译,因为我们无权访问受保护的 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() {}
      |     ^~~~~~~

第一个似乎最常见的解决方案是派生类中的友元声明。但是它有效,我不喜欢它,因为:

  1. 我必须在每个派生类中添加这样的声明
  2. 这是一个朋友
class Derived : public Base
{
protected:
    Derived() {}
    friend void Base::Create<Derived>(std::unique_ptr<Derived>&);
};

考虑更通用的方法,我正在尝试这样的事情:

 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);
    }

它有效,但对我来说看起来很奇怪,并且:

  1. 真正的指针类型是 CreateHelper 但是在这个函数之外我们看不到
  2. 这种方法需要 Base-familiy 应该是多态的,因为我们使用指向基类的指针(似乎应该始终满足这个条件,但仍然值得一提)

我的问题是

  1. 您如何看待最后一种方法?
  2. 它被认为是一个糟糕的设计吗?
4

1 回答 1

1

一般来说,这里更好的方法是简单地引用简单的构造辅助类,然后你也可以引用 make_unique() :

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()) {}
};

唯一的缺点:派生类必须相应地调整其构造函数。

一般观点:工厂应该几乎总是知道它的相关类型,至少是相关类型的部分方面,由多态性(接口)或/和通过特征提供。所以我认为,这更方便一点:

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());
    // ... 
}

进一步:您可能必须在这里重新考虑您的一般创作设计。这里常用的方法是返回创建的对象,而不是填充引用。使用您当前的方法,您必须在这里至少考虑两次异常安全(合同......),例如......

可能的方法:

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;
}
于 2021-02-13T11:43:30.203 回答