【问题标题】:How to create pointer to different instantiation of template class如何创建指向模板类的不同实例化的指针
【发布时间】:2021-04-20 13:21:19
【问题描述】:

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

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

它可以满足我的需求,并且看起来像是互联网上的大部分示例。

但是在这个版本的抽象工厂中,如果我想在层次结构(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_ 的不同实例。这是抽象工厂没有预料到的。 在第一个版本中,由于虚拟继承可以在基类指针上调用Create。但在这里我有模板基类。所以我不能调用它指针Create()。 所以问题是如何使指针或其他东西可能是 std::any o std::variant 以使其在这个抽象工厂类设计中成为可能?我想要工作的客户端代码和第一个版本一样。

    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]

【问题讨论】:

    标签: c++ pointers abstract-factory


    【解决方案1】:

    您可能会进行一些更改:

    // 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

    【讨论】:

    • 哇。这是超级通用的。只需添加新的层次结构元素而不改变工厂。有没有像这样的抽象工厂讨论一些源代码或更多示例的链接?
    【解决方案2】:

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

    此解决方案的优点是它保留了您想要的语法:

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

    所以问题是如何使指针或其他东西成为 std::any o std::variant 以使其在这个抽象工厂类设计中成为可能?我想要工作的客户端代码和第一个版本一样。

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

    如果您想返回 std::any,请使用完整的 std::any 并始终转换为正确的类型并完全跳过继承。如果您想使用std::variant,请移除继承并跳过虚拟内容,只需将所有类型放入变体中即可。

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

    它会在您的代码中添加强制转换,几乎无处不在。此外,您还要重复自己,因为您必须在工厂类中重复整个继承结构。

    此时,我将简单地使用类型擦除。我将使用泛型类型concrete,而不是深度、复杂的工厂层次结构,它会根据你构建它的方式来改变它的行为。您可以自己滚动,但也可以使用标准库提供的一种。最简单的是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 子类的构造策略耦合到类的层次结构中,您只需在需要该类型擦除工厂函数的地方分配所需的行为。

    如果你不想使用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&lt;T&gt; 代替T*,使用std::make_unique&lt;T&gt;() 代替new T。我在我的回答中应用了它。
    • 使用结构时,继承中不需要public。结构默认公开继承。
    • 添加override 关键字时,不要输入virtual。这是多余的。

    【讨论】:

    • 感谢您如此详细的回答。在这里使用覆盖对我来说是新技巧。你能澄清一下在 std::function 中使用类型擦除吗?我看到它像伪代码,但我不明白在 ctor 收到 std::unique_ptr 之后myClass 会有什么类型?它仍然是 ClassThatNeedToMakeD1 吗?那么这里的类型擦除在哪里?如果我需要创建新变量和新 classToMakeD2,它如何减少代码编写量...至少这是我理解所有这些模式的“含义”的方式。
    • @AlexeyChicherin myClass 的类型就是ClassThatNeedToMakeD1。我通常将类型放在变量名的右侧,这是一种编码风格,我认为它读起来更好。它不是伪代码,it compiles and run。我发送给myClass 构造函数的东西是一个lambda 函数。 [] 是捕获,我省略的() 是参数,{} 是实现。如您所见,它非常适合工厂使用。
    • 类型擦除是必需的,因为所有 lambda 表达式都有自己不同的类型。 std::function 是一个类型擦除实用程序,可以包含任何类型的可调用类型,如函数指针或 lambda 表达式。
    • @AlexeyChicherin 变化最大的是我没有实现复杂的工厂类,而是在需要它的类中添加了std::function。手动类型擦除也是compiles and run。如您所见,多态性隐藏在AnyFactory 中,并没有创建复杂的类层次结构,而是模板化而不是手动完成。
    猜你喜欢
    • 1970-01-01
    • 2016-07-25
    • 1970-01-01
    • 1970-01-01
    • 2013-05-28
    • 2020-10-20
    • 2012-01-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多