簡體   English   中英

如何創建指向模板 class 不同實例化的指針

[英]How to create pointer to different instantiation of template class

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

所以它可以工作,但我必須為ConcreteFactory_的不同實例創建兩個不同的指針( pFactory_1pFactory_2 )。 這是抽象工廠沒有預料到的。 在第一個版本中,由於虛擬繼承可以在 Base class 指針上調用Create是可以的。 但在這里我有模板基礎 class。 所以我不能調用它指針Create() 所以問題是如何在這個抽象工廠 class 設計中制作指針或其他東西可能是 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]

通過一些更改,您可能會有:

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

演示

一個簡單的解決方案是將別名添加到您的具體類中。

此解決方案的優點是它保留了您想要的語法:

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

所以問題是如何在這個抽象工廠 class 設計中制作指針或其他東西可能是 std::any o std::variant ? 我想要工作的客戶端代碼與第一個版本相同。

不要那樣做。 如果您使用指針和 inheritance 進行多態性,我建議不要混合使用其他實用程序。 例如,如果您選擇返回std::any ,您將有兩層間接:any class,然后是指針。 並且要實際使用指針,您必須將其轉換為正確的類型,使基於 inheritance 的間接無用。

如果您想返回std::any , go 完整std::any並始終轉換為正確的類型並完全跳過 inheritance 。 如果要使用std::variant ,請刪除 inheritance 並跳過虛擬內容,只需將所有類型放入變體中即可。

查看您的 inheritance 結構,它確實是一個很深的結構(多級繼承)。 解決方案是讓BFactory具有純虛擬 function 返回指向B的指針,並使您的所有工廠都從它擴展。

它會在你的代碼中添加強制轉換,幾乎無處不在。 此外,您重復自己,因為您必須在工廠類中重復整個 inheritance 結構。

在這一點上,我會簡單地使用類型擦除。 我將使用泛型類型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子類的構造策略耦合到類層次結構中,您只需在需要該類型擦除工廠 function 的地方分配所需的行為。

如果您不想使用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 我在我的回答中應用了它。
  • 使用結構時,inheritance 中不需要public 結構默認公開繼承。
  • 添加override關鍵字時,不要放置virtual 這是多余的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM