2010-09-14 38 views
4

我有一個抽象基類,像這樣:如何使我的工廠標題不依賴於它創建的模板對象?

class AbstractBaseClass 
{}; 

模板化的具體類從它派生:

template<class T> 
class ConcreteClass : public AbstractBaseClass 
{ 
public: 
    ConcreteClass(T input) : data(input) {} 
private: 
    T data; 
}; 

安迪有一個創建AbstractBaseClasses

class MyFactory 
{ 
public: 
    boost::shared_ptr<AbstractBaseClass> CreateBlah(); 
    boost::shared_ptr<AbstractBaseClass> CreateFoo(); 

    template<class T> 
    boost::shared_ptr<AbstractBaseClass> Create(T input) 
    { 
     return boost::shared_ptr<AbstractBaseClass>(new ConcreteClass<T>(input)); 
    } 
}; 

的一個工廠類問題在於,現在所有使用MyFactory的東西都必須將整個實現包含到ConcreteClass中。理想情況下,我只需要MyFactory就可以瞭解ConcreteClass。

有沒有什麼辦法來達到這個目標? (除了手動在MyFactory中爲每個類型創建一個新的Create函數,而不是模板化它)。

+0

'ConcreteClass'是可見的,但是你可以使它成爲'private'來減少它的可訪問性。完全隱藏用戶是否值得麻煩? – 2010-09-14 06:52:56

+0

我在一個非常非常大的代碼庫中,它對我們來說非常重要,可以考慮減少頭文件依賴和編譯時間等等,這就是爲什麼我很擔心這個問題。 – 2010-09-14 06:57:10

回答

5

你需要把工廠實現放到實現文件中(你提到過你希望避免,但是除非接口很小,或者你的項目很小,否則它是較小的惡意)。當然,還有其他一些方法可以解決這個問題,比如將實現放入基類,派生基類工廠,或者使用一些其他非常奇怪的模板語法來減少從屬語言中的實例化。這真的歸結爲您的項目的便利和規模。如果您正在處理一個或多個大型項目,那麼從實例化的全面抽象將最好地滿足您的需求(假設您需要動態多態性和內存)。

您也可以嘗試其他方法(例如重載)以通過使用類型安全來減少錯誤。

簡短的回答是,你真的需要將接口/實例化抽象爲一個或多個實現文件,以去除頭文件依賴 - 非常常見的習慣用法,以及解決它的許多方法。你可以進一步劃分和使用工廠的多態性。

您也可以使用模板前向聲明將集合最小化到編譯單元。提供:

/** in MyIntermediateFactory.hpp */ 
class MyIntermediateFactory { 
public: 
    static template<class T> boost::shared_ptr<T> Create(float); 
}; 

/** in Concrete.hpp */ 
template<Concrete> 
boost::shared_ptr<T> MyIntermediateFactory::Create<Concrete>(float arg) { 
    /* … */ 
} 

使用這個,你可以選擇你的圖書館需要的程序/接口部分,然後把它包起來都在一個真正的工廠(手頭的版本)。如果您實際嘗試請求不可見的創建,鏈接器/實例化應該會一路失敗。

有很多選擇,真的 - 你需要弄清楚你的比例有多大才能確定抽象(或不抽取)什麼。實例化需要接口,爲了移除頭文件依賴關係,你必須在某個地方抽象實例化。

0

您正在尋找「PIMPL」成語。有一個很好的解釋Herb Sutter's GOTW site

+0

林不知道PImpl會如何幫助我。由於我正在處理模板,所以在編譯時需要實現。由於Factory的創建方法是模板化的,因此需要從頭文件調用PImpl的函數。如果我錯過了一些東西,你能否更具體一些? – 2010-09-14 04:55:28

+0

它會減少對界面的依賴,但不能完全刪除它。要獲得完全免費,您需要使用註冊表類型的方法,其中Factory直到運行時才知道它正在加載什麼。這種東西在插件體系結構中很常見,在這種體系結構中,工廠讀取註冊表以找出如何創建一些東西。它有點多毛,但是當你使用它時非常酷。 – 2010-09-14 05:37:12

+0

如果您的實現取決於未在源代碼中編寫的內容,您將會失去編譯器靜態檢查。 – YeenFei 2010-09-14 05:45:13

0

它不能完成,因爲ConcreteClass是一個模板,這意味着你需要在編譯時提供完整的實現。同樣的原因,爲什麼你不能預編譯模板,而必須將它們全部寫入頭文件。

+0

你可以 - 通過顯式模板實例 – 2010-09-14 08:26:11

1

您可以使用顯式模板實例。試圖用模板參數調用工廠方法不明確instanciated會給你一個鏈接器錯誤。請注意MyFactory中的顯式模板實例。CPP

template AbstractBaseClass* MyFactory::Create(int input); 

都放在一起看起來像這樣(我刪除的shared_ptr爲簡單起見):

Main.cpp的:

#include "AbstractBaseClass.h" 
#include "MyFactory.h" 

//we do not need to know nothing about concreteclass (neither MyFactory.h includes it)  

int main() 
{ 
    MyFactory f; 
    AbstractBaseClass* ab = f.Create(10); 
    ab = f.Create(10.0f); 
    return 0; 
} 

MyFactory.h:

#include "AbstractBaseClass.h" 

class MyFactory 
{ 
public: 

    template<class T> 
    AbstractBaseClass* Create(T input); 
}; 

MyFactory.cpp:

AbstractBaseClass.h:

class AbstractBaseClass{}; 

ConcreteClass.h:

#include "AbstractBaseClass.h" 

template<class T> 
class ConcreteClass : public AbstractBaseClass 
{ 
public: 
    ConcreteClass(T input) : data(input) {} 
private: 
    T data; 
}; 
+0

有趣,不知道我可以做到這一點。 – 2010-09-14 15:50:06

+0

@lima不適合你的問題? – 2010-09-14 17:40:30

+0

不幸的是,沒有任何編譯器(Comeau的除外)可以編譯MyFactory.h。您無法聲明模板化函數並將其定義在另一個文件中。這確實是在標準中,但沒有人支持這個功能。 – 2010-09-16 14:22:15

1

我在過去同樣的問題,方法是一組具體工廠的創建(每一個類型)在全球工廠註冊(出於說明目的,按目標名稱索引):

class AbstractBaseClass; 
class ConcreteFactory 
{ 
public: 
    AbstractBaseClass * create(); 
}; 
class AbstractFactory 
{ 
public: 
    void registerFactory(std::string const & name, std::shared_ptr<ConcreteFactory> const & f) 
    { 
     factory[ name ] = f; // check for collisions, complain if so ... 
    } 
    AbstractBaseClass * create(std::string const & name) 
    { 
     return factory[name]->create(); // check for existence before dereferencing... 
    } 
private: 
    std::map<std::string, std::shared_ptr<ConcreteFactory> > factory; 
}; 

我在一段代碼中使用了這段代碼,這段代碼大量模板化以減少編譯時間。每個混凝土工廠和它創建的類只需要在一個單一的翻譯單元中註冊混凝土工廠。其餘代碼只需要使用AbstractBaseClass的通用接口。

0

我意識到我在回答這五年之後。從那時起,這種語言可能會變得非常有趣。如果我能夠正確理解這個問題,如果除了幫助其他人可能會發現這個問題並且想知道他們可以做什麼之外,沒有別的意義,我想提供一些看起來不錯的東西。


factory.hpp

#include "base.hpp" 

namespace tvr 
{ 
    namespace test 
    { 
     class factory 
     { 
     public: 
      typedef base::ptr Ptr; 

      enum eSpecial 
      { 
       eDerived 
      }; 

      template<typename Type> 
      Ptr create() 
      { 
       Ptr result; 
       result.reset(new Type()); 
       return result; 
      } 

      template<typename Type, typename DataType> 
      Ptr create(const DataType& data) 
      { 
       Ptr result; 
       result.reset(new Type(data)); 
       return result; 
      } 

      template<typename Type, typename DataType> 
      Ptr create(const DataType& data, eSpecial tag) 
      { 
       Ptr result; 
       result.reset(new Type()); 
       static_cast<Type*>(result.get())->set_item(data); 
       return result; 
      } 
     }; 
    } 
} 

base.hpp

#include <memory> 

namespace tvr 
{ 
    namespace test 
    { 
     class base 
     { 
     public: 
      typedef std::shared_ptr<base> ptr; 

     public: 
      base() {} 
      virtual ~base() {} 
      virtual void do_something() = 0; 
     }; 
    } 
} 

some_class .HPP

#include <ostream> 

namespace tvr 
{ 
    namespace test 
    { 
     struct some_class 
     { 
     }; 
    } 
} 

std::ostream& operator<<(std::ostream& out, const tvr::test::some_class& item) 
{ 
    out << "This is just some class."; 
    return out; 
} 

template_derived.hpp

#include <iostream> 

#include "base.hpp" 

namespace tvr 
{ 
    namespace test 
    { 
     template<typename Type> 
     class template_derived : public base 
     { 
     public: 
      template_derived(){} 
      virtual ~template_derived(){} 
      virtual void do_something() 
      { 
       std::cout << "Doing something, like printing _item as \"" << _item << "\"." << std::endl; 
      } 

      void set_item(const Type data) 
      { 
       _item = data; 
      } 
     private: 
      Type _item; 
     }; 
    } 
} 

,最後是主。CPP

#include <vector> 

#include "base.hpp" 
#include "factory.hpp" 

namespace tvr 
{ 
    namespace test 
    { 
     typedef std::vector<tvr::test::base::ptr> ptr_collection; 

     struct iterate_collection 
     { 
      void operator()(const ptr_collection& col) 
      { 
       for (ptr_collection::const_iterator iter = col.begin(); 
        iter != col.end(); 
        ++iter) 
       { 
        iter->get()->do_something(); 
       } 
      } 
     }; 
    } 
} 

#include "template_derived.hpp" 
#include "some_class.hpp" 

namespace tvr 
{ 
    namespace test 
    { 
     inline int test() 
     { 
      ptr_collection items; 

      tvr::test::factory Factory; 

      typedef template_derived<unsigned int> UIntConcrete; 
      typedef template_derived<double> DoubleConcrete; 
      typedef template_derived<std::string> StringConcrete; 
      typedef template_derived<some_class> SomeClassConcrete; 

      items.push_back(Factory.create<SomeClassConcrete>(some_class(), tvr::test::factory::eDerived)); 
      for (unsigned int i = 5; i < 7; ++i) 
      { 
       items.push_back(Factory.create<UIntConcrete>(i, tvr::test::factory::eDerived)); 
      } 
      items.push_back(Factory.create<DoubleConcrete>(4.5, tvr::test::factory::eDerived)); 
      items.push_back(Factory.create<StringConcrete>(std::string("Hi there!"), tvr::test::factory::eDerived)); 

      iterate_collection DoThem; 
      DoThem(items); 

      return 0; 
     } 
    } 
} 

int main(int argc, const char* argv[]) 
{ 
    tvr::test::test(); 
} 

輸出

Doing something, like printing _item as "This is just some class.". 
Doing something, like printing _item as "5". 
Doing something, like printing _item as "6". 
Doing something, like printing _item as "4.5". 
Doing something, like printing _item as "Hi there!". 

這使用的模板,函數重載組合,並通過枚舉標記,以幫助建立一個靈活的工廠類,不需要了解更多關於它實例化的單個類,以便包括OP詢問的模板化具體類。

'eDerived'標記(以enum的形式)告訴編譯器使用工廠的create函數的版本,該函數採用類template_derived類,該函數允許我將數據分配給一個的成員。正如你從main.cpp中訂購頭文件的方式可以看出的那樣,工廠對template_derived一無所知。調用基類的虛函數(do_something)的函數也沒有。我認爲這是OP想要的,但不需要在該工廠可能生成的每個類中添加各種創建函數。

我還展示瞭如何不必爲工廠應創建的每個類顯式創建函數。工廠的重載創建函數可以創建與適當簽名匹配的基類派生的任何東西。

我沒有對這段代碼做廣泛的性能分析,但是我足夠了解大部分工作發生在流式運算符中。這在我的3.30Ghz四核機器上編譯了大約1秒鐘。您可能需要嘗試使用更健壯的代碼,以查看編譯器有多糟糕,儘管如此。

我已經在VC++ 2015中測試了這個代碼,雖然它可能很容易在其他編譯器中工作。如果你想複製這個,你需要添加你自己的警衛頭。無論如何,我希望這是有用的。