2013-01-24 28 views
3

我有一個超類的幾個子類,想根據給定的字符串根據C++字符串創建一個對象

Superclass instantiateSubclass(string s); 

而不是有一個巨大的,如果級聯我創建一個特定的類的實例想要使用配置文件來完成此操作。

這使我可以在不重新編譯的情況下更改字符串s的可能值,我希望它能夠生成更簡潔的代碼。

配置文件應該包含像「subclass1」,「subclass2」這樣的字符串,但是如何根據字符串創建類呢?

基本上我需要一個從字符串到類的映射,這可能在C++中嗎?我認爲其他語言爲這個問題提供了反思的可能性。

+0

你要創建的對象,其類型(類名稱)字符串指定? – LihO

+0

@ LihO:是的,確切地說 – marktani

+0

沒有辦法在C++中不用重新編譯就添加子類。某處需要創建具體類的實例,編譯器需要查看它。或者你在說s.th.其他? –

回答

9

註冊你的類:

struct Base; 

struct Derived1 : Base 
{ 
    static Base * create() { return new Derived1; } 
}; 

std::map<std::string, Base * (*)()> registry = { {"derived1", &Derived1::create}, 
               /* ... */ 
               }; 

要:

Base * create_from_string(std::string const & s) 
{ 
    auto it = registry.find(s); 
    return it == registry.end() ? nullptr : (it->second)(); 
} 
0

無論您使用的配置文件或以其他方式,在某些時候,你需要將字符串翻譯成「新SomeClass的」調用[或者沿着這些線]。這將涉及比較一個字符串,並選擇正確的項目,使新的。或者是一個if/else if/else if的長鏈或者一個表,然後你可以在switch-statement中根據你得到的表中第二個條目的某個整數常量進行選擇。

當然,另一種可能性是將問題完全移到不同的級別,併爲每個類實現一個共享庫,並根據其名稱加載相關庫,然後在其中定義一個名爲/ ordinal的函數共享庫,你可以打電話給「讓我成爲你的對象之一」。

3

我以前做過類似的事情。構建字符串(類型)/函數指針(工廠)對的unordered_map。每一個都指向一個創建類型實例的靜態函數。

這仍然需要你擁有那些小存根工廠函數,通常它們是創建該類型的單行工具。宏可以用來生成工廠方法。

typedef std::unordered_map<std::string, Superclass *(*)()> TypeDirectory; 
TypeDirectory types; 

#define NAMEFACTORY(name_) static Superclass *Create() { return new name_; } 

class Hello : public Superclass 
{ 
    ... 

    NAMEFACTORY(Hello) 
}; 

static void RegisterFactoryNames() 
{ 
    types.emplace_back("Hello", &Hello::Create); 
    ... 
} 

static Superclass *MakeInstance(std::string &name) 
{ 
    auto i = types.find(name); 
    return i != types.end() ? types->second() : 0; 
} 

只有你知道工廠數據的名稱屬於哪裏,我沒有把它放在我的例子中的任何東西中。注意:如果您使用的是MSVC 10或更低版本(2010或更低版本),則使用types.push_back(TypeDirectory::value_type("Hello", &Hello::Create));,因爲emplace_back在這些版本中的實現完全不正確。

+0

RegisterFactoryNames()何時運行? – einpoklum

+0

@einpoklum你可以檢查types.empty()並從'MakeInstance'中調用它。這不會是線程安全的。如果需要線程安全,那麼只要確保在'MakeInstance'之前的任何時候調用'RegisterFactoryNames'。 – doug65536

1

通過以下代碼,您可以註冊從基地INTERFACE派生的任意數量的類,並使用從register_class返回的字符串將它們實例化。

不好的一面是鍵盤在平臺之間可能會有所不同,你必須知道typeid(CLASS)是什麼。名稱()返回爲每個類,以確保你把正確的類關鍵在你的配置文件(也可能不一樣的類名)

瞭解更多關於typeid的學習這裏發生了什麼

template <class INTERFACE> class factory 
    { 
    public: 
     template <class CLASS> const std::string register_class() 
     { 
      static const std::string key = typeid(CLASS).name(); 
      class class_factory : public ifactory 
      { 
      private: 
       virtual std::shared_ptr<INTERFACE> create() const 
       { 
        return new CLASS; 
       } 
      }; 
      m_factory_map[key] = new class_factory; 
      return key; 
     } 
     std::shared_ptr<INTERFACE> create(const std::string& key) const 
     { 
      const factory_map::const_iterator ifind = m_factory_map.find(key); 
      if(ifind == m_factory_map.end()) 
       return 0; 
      return ifind->second->create(); 
     } 
    private: 
     class ifactory 
     { 
     public: 
      virtual ~ifactory() {} 
      virtual std::shared_ptr<INTERFACE> create() const = 0; 
     }; 
     typedef std::map<std::string, std::shared_ptr<ifactory> > factory_map; 
     factory_map m_factory_map; 
    }; 

使用方法如下

factory<MyInterface> fact; 
const std::string key1 = fact.register_class<MyClass1>(); 
const std::string key2 = fact.register_class<MyClass2>(); 
const std::string key3 = fact.register_class<MyClass3>(); 
std::shared_ptr<MyInterface> p1 = fact.create(key1); 
std::shared_ptr<MyInterface> p2 = fact.create(key2); 
std::shared_ptr<MyInterface> p3 = fact.create(key3);