如何创建指向模板不同实例化的指针 class

How to create pointer to different instantiation of template class

在用 C++ 进行抽象工厂的第一次实验时(在阅读现代 C++ 设计 - A. Alexandrescu - 第 9 部分时)我有一个问题。 如果 classes 层次结构如下所示:

struct B {}; 

struct D1 :public B {};
struct DD1 : public D1 {};
struct DD2 : public D1 {};

struct D2 :public B {};
struct DD3 : public D2 {};
struct DD4 : public D2 {};

我有这个抽象工厂代码:


//abstract factory version 1

struct AbstractFactoryImpl
{
    virtual D1* CreateD1() = 0;
    virtual D2* CreateD2() = 0;
};

struct AbstractFactory : public AbstractFactoryImpl
{
    virtual D1* CreateD1() { return new D1; };
    virtual D2* CreateD2() { return new D2; };
};

template<class ... Ts>
struct ConcreteFactory :public AbstractFactory
{
    using params = std::tuple<Ts...>;
    using T1 = typename std::tuple_element_t<0, params>;
    using T2 = typename std::tuple_element_t<1, params>;

    virtual D1* CreateD1()override
    {
        static_assert(std::is_base_of_v<D1, T1>);
        return new T1;
    };
    virtual D2* CreateD2()override
    {
        static_assert(std::is_base_of_v<D2, T2>);
        return new T2;
    };
};

并像这样在客户端代码中使用它。

//version 1
    AbstractFactory* pFactory;
    pFactory = new ConcreteFactory<DD1, DD3>;
    D1* pD1 = pFactory->CreateD1();
    D2* pD2 = pFactory->CreateD2();

    pFactory = new ConcreteFactory<DD2, DD4>;
    pD1 = pFactory->CreateD1();
    pD2 = pFactory->CreateD2();

它按我的要求工作,看起来像互联网上的大部分示例。

但是在这个版本的抽象工厂中,如果我想在层次结构中添加更多 classes(D3、DD5、DD6...),我必须手动创建太多代码,所以我想编写更多代码通用。

//abstract factory version 2
template<typename T>
struct AbstractFactoryImpl_
{
    virtual T* Create() = 0;
};

template<typename T>
struct AbstractFactory_ :public AbstractFactoryImpl_<T>
{
    virtual T* Create() override
    {
        return new T;
    }
};

template<class ...Ts>
struct ConcreteFactory_ : public AbstractFactory_<Ts>...
{
    using params = std::tuple<Ts...>;
    using T1 = typename std::tuple_element_t<0, params>;
    using T2 = typename std::tuple_element_t<1, params>;

    template<class T>
    T* Create()
    {
        if constexpr (std::is_base_of_v<T, T1>)return AbstractFactory_<T1>::Create();
        else if constexpr (std::is_base_of_v<T, T2>)return AbstractFactory_<T2>::Create();

    }
};

它在客户端代码中的工作方式如下:

        //version 2
    //AbstractFactory* pFactory; -- can`t use because AbstractFactory in version 2 is template class.
    auto pFactory_1 = new ConcreteFactory_<DD1, DD3>;
    D1* pD1_ = pFactory_1->Create<D1>();
    D2* pD2_ = pFactory_1->Create<D2>();

    auto pFactory_2 = new ConcreteFactory_<DD2, DD4>;
    pD1_ = pFactory_2->Create<D1>();
    pD2_ = pFactory_2->Create<D2>();

所以它有效,但我必须创建两个不同的指针(pFactory_1pFactory_2)指向 ConcreteFactory_ 的不同实例。这是抽象工厂所没有想到的。 在第一个版本中,由于虚拟继承可以在 Base class 指针上调用 Create。但这里我有模板库 class。所以我不能调用它的指针 Create()。 所以问题是如何使指针或其他东西可能 std::any o std::variant 使在这个抽象工厂 class 设计中成为可能?我想要使​​用的客户端代码与第一个版本相同。

    TYPE* pFactory_ = new ConcreteFactory_<DD1, DD3>;
    D1* pD1_ = pFactory_->Create<D1>();
    D2* pD2_ = pFactory_->Create<D2>();

    auto pFactory_ = new ConcreteFactory_<DD2, DD4>;
    pD1_ = pFactory_->Create<D1>();
    pD2_ = pFactory_->Create<D2>();

完整代码: [https://cppinsights.io/s/552bbe01]

您可能需要进行一些更改:

// Way to "pass" Type.
template <typename T> struct Tag{};

template <typename T>
struct AbstractFactory
{
    virtual ~AbstractFactory() = default;
    virtual std::unique_ptr<T> Create(Tag<T>) const = 0;
};

template <typename ... Ts>
struct AbstractFactories : virtual AbstractFactory<Ts>...
{
    using AbstractFactory<Ts>::Create ...;
};

template <typename T, typename T2>
struct ConcreteFactory : virtual AbstractFactory<T>
{
    std::unique_ptr<T> Create(Tag<T>) const override { return std::make_unique<T2>(); }
};

template <typename Base, typename ...Ts>
struct ConcreteFactories;

template <typename ... Ts1, typename ...Ts2>
struct ConcreteFactories<AbstractFactories <Ts1...>, Ts2...> : AbstractFactories <Ts1...>, ConcreteFactory<Ts1, Ts2>...
{
    using ConcreteFactory<Ts1, Ts2>::Create ...;
};

随着使用

void Test(AbstractFactories<D1, D2>& factory)
{
    std::unique_ptr<D1> d1 = factory.Create(Tag<D1>{});
    std::unique_ptr<D2> d2 = factory.Create(Tag<D2>{});
    // ...
}

int main()
{
    ConcreteFactories<AbstractFactories<D1, D2>, DD1, DD3> factory1;
    ConcreteFactories<AbstractFactories<D1, D2>, DD2, DD4> factory2;
    
    Test(factory1);
    Test(factory2);
}

Demo

一个简单的解决方案是在您的具体 classes 中添加一个别名。

此解决方案的优势在于它保留了您想要的语法:

struct B {}; 

struct D1 : B {};
struct DD1 : D1 {
    using overrides = D1;
};
struct DD2 : D1 {
    using overrides = D1;
};

struct D2 : B {};
struct DD3 : D2 {
    using overrides = D2;
};
struct DD4 : D2 {
    using overrides = D2;
};

然后,只需将您的 AbstractFactory_ 类型更改为:

template<typename T>
struct AbstractFactoryImpl_
{
    virtual std::unique_ptr<T> Create() = 0;
};

template<typename T>
struct AbstractFactory_ : AbstractFactoryImpl_<typename T::overrrides>
{
    auto Create() -> std::unique_ptr<typename T::overrrides> override
    {
        return std::make_unique<T>();
    }
};

使用它,您的代码将简单地工作:

auto pFactory1 = ConcreteFactory_<DD1, DD3>{};
std::unique_ptr<D1> pD1_ = pFactory_.Create<D1>();
std::unique_ptr<D2> pD2_ = pFactory_.Create<D2>();

auto pFactory2 = ConcreteFactory_<DD2, DD4>{};
pD1_ = pFactory_.Create<D1>();
pD2_ = pFactory_.Create<D2>();

So the question is how to make pointer or something else may be std::any o std::variant to make it possible in this Abstract Factory class design? The client code i wanna make to work is same like first version.

不要那样做。如果您将指针和继承用于多态性,我建议您不要混合使用其他实用程序。例如,如果您选择 return a std::any,您将有两层间接寻址:any class,然后是指针。并且要实际使用指针,您必须将其转换为正确的类型,从而使基于继承的间接寻址变得无用。

如果您想 return std::any,请完整 std::any 并始终转换为正确的类型并完全跳过继承。如果你想使用 std::variant,删除继承并跳过虚拟的东西,只需将你所有的类型放在变体中。

看你的继承结构,确实很深(多级继承)。解决方案是让 BFactory 具有纯虚函数 return 指向 B 的指针,并使您的所有工厂都从它扩展。

它会在您的代码中添加转换,几乎无处不在。另外,你自己重复一遍,因为你必须在你的工厂中重复整个继承结构 classes.

在这一点上,我会简单地使用类型擦除来代替。我将使用泛型,具体的,而不是深度复杂的工厂层次结构,它会根据您构造它的方式改变其行为。您可以自己推出,但也可以使用标准库提供的。最简单的就是std::function:

struct ClassThatNeedToMakeD1 {
    std::function<std::unique_ptr<D1>()> makeD1;

    void stuff() {
        auto myD1ptr = makeD1();
    }
};

int main() {
    auto myClass = ClassThatNeedToMakeD1{
        []{ return std::make_unique<DD2>(); }
    };

    myClass.stuff();
}

这保持简单、可扩展,并且会在你想要的地方做你想做的事。不是将所有 B 子 classes 的构造策略耦合到 classes 的层次结构中,您只需在需要该类型擦除工厂函数的地方分配想要的行为。

如果你不想使用std::function,你可以推出你自己的有限等价物:

template<typename T>
struct AnyFactory {
    template<typename Function>
    AnyFactory(Function f) : _factory{std::make_unique<Concrete<Function>>(std::move(f))} {}

    auto operator()() const -> std::unique_ptr<T> {
        return _factory->create();
    }

private:
    struct Abstract {
        virtual ~Abstract() = default;
        virtual auto create() const -> std::unique_ptr<T> = 0;
    };

    template<typename Function>
    struct Concrete : Abstract {
        Concrete(Function c) noexcept : _create{std::move(c)} {}

        auto create() const -> std::unique_ptr<T> override {
            return _create();
        }

        Function _create;
    };

    // Copy on write. The const is very important, don't remove it.
    // If you want to remove the const, change to unique_ptr + clone function in Abstract
    std::shared_ptr<const Abstract> _factory;
};

struct ClassThatNeedToMakeD1 {
    AnyFactory<D1> makeD1;

    void stuff() {
        auto myD1ptr = makeD1();
    }
};
 
int main() {
    // Usage stays the same
    auto myClass = ClassThatNeedToMakeD1{
        []{ return std::make_unique<DD2>(); }
    };

    myClass.stuff();
}

小建议:

  • 使用 std::unique_ptr<T> 代替 T*,使用 std::make_unique<T>() 代替 new T。我在我的回答中应用了它。
  • 使用结构时,不需要public继承。结构默认公开继承。
  • 添加override关键字时,不要输入virtual。这是多余的。