[英]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_1
, pFactory_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
。 我在我的回答中應用了它。public
。 結構默認公開繼承。override
關鍵字時,不要放置virtual
。 這是多余的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.