2012-01-05 97 views
1

我是一個素食主義者,所以假設我們有蔬菜:在C++中定義許多組合對象的最佳方法?

class Vegetable {}; // base class for vegetables 

class Tomato : public Vegetable {}; 
class Potato : public Vegetable {}; 
class Carrot : public Vegetable {}; 
class Broccoli : public Vegetable {}; 

並假設我們想要做的飯菜與他們:

class Meal {}; // base class for meals 

class Soup : public Meal { 
    ... 
    Soup(Vegetable *veg1, Vegetable *veg2) : veg1(veg1), veg2(veg2) {}; 
}; 

class Salad : public Meal { 
    ... 
    Salad(Vegetable *veg1, Vegetable *veg2, Vegetable *veg3) : veg1(veg1), veg2(veg2), veg3(veg3) {}; 
}; 

class VeggieBurger : public Meal { 
    ... 
    VeggieBurger(Vegetable *veg) : veg(veg) {}; 
}; 

現在我們想定義不同的蔬菜不同的組合餐在食譜:

std::vector<Meal *> cookbook; 

cookbook.push_back(new Soup(new Tomato, new Potato)); 
cookbook.push_back(new Soup(new Potato, new Broccoli)); 
cookbook.push_back(new Salad(new Tomato, new Carrot, new Broccoli)); 
cookbook.push_back(new Salad(new Tomato, new Potato, new Tomato)); 
cookbook.push_back(new Salad(new Broccoli, new Potato, new Carrot)); 
cookbook.push_back(new VeggieBurger(new Potato)); 
// many more meals... 

因此,我們正在創造越來越通過constructo共同組成堆上許多小物件r參數並在運行時推送到std :: vector。顯然,這種設計的缺點是,我們必須自己管理記憶,並在我們的膳食析構器中刪除Vegetable對象,並在超出範圍時刪除我們的食譜膳食。

因此,一個可能的設計選擇是使用智能指針來爲我們的膳食和蔬菜做記憶管理的負擔。

但我想知道是否有可能在編譯時編寫食譜,也許有某種模板魔法?食譜不一定是std :: vector,但我們仍然可以遍歷它,獲取Meal對象並在構成餐上調用成員函數。有沒有更好的方法來做到這一點?

+0

當然,番茄是一種水果。 :-P另外,請看[Boost.Fusion](http://www.boost.org/libs/fusion/)的'boost :: fusion :: vector <>'。 – ildjarn 2012-01-05 00:26:28

+0

您可能已經走上了一條糟糕的設計道路。你的例子是否準確,你想定義許多類,全部是空的?並且你想分配相同空類的許多實例,例如'Tomato'? – 2012-01-05 00:29:43

+1

在現實世界中,一個'Vegetable'具有狀態和成員函數。 – 2012-01-05 00:43:28

回答

1

我發現代碼生成對我來說正變得非常有用。也許編寫一個簡短的程序,甚至可能只是一個腳本,而不是爲你生成樣板代碼。

無論何時添加到您的食譜,您都可以運行程序/腳本,該程序/腳本將爲Cookbook本身生成每頓飯和頭部的標題,甚至可能包含一些源代碼。

你如何深入你的世代取決於你。您可以編輯代碼生成器的源代碼以添加新的用餐,您可以執行一些簡單的基於文本的解析,或者,如果確實值得花費時間和精力進行維護,請將代碼生成器轉換爲編輯器(將代碼生成爲輸出) 。

至少有一個非常知名的AAA遊戲引擎實際上爲任何被標記爲與本地代碼接口的腳本生成C++頭文件。從那裏,源文件中的宏實現樣板方法。其餘的方法由開發人員實施。

更新: C++ 11實際上支持variadic template arguments。我對C++ 11沒有任何經驗,因此我不確定variadic模板參數是否支持我們正在查看的內容。

+0

+1 http://pragprog.com/the-pragmatic-programmer/extracts/tips「寫代碼寫代碼」 – 2012-01-05 01:05:05

-1

即使使用模板,您也需要某種類型的擦除來將結果實例化到某處。你可以有一個Meal基類,它通過模板參數獲取其成分。例如:

template <typename... T> 
class Salad: public Meal { 
}; 

std::vector<std::unique_ptr<Meal>> cookbook; 
cookbook.push_back(std::unique_ptr<Meal>(new Salad<Tomato>())); 
cookbook.push_back(std::unique_ptr<Meal>(new Salad<Tomato, Carrot>())); 
cookbook.push_back(std::unique_ptr<Meal>(new Salad<Tomato, Tomato, Carrot>())); 

當然,爲了在某些形狀或形式上有用,您仍然需要提供一些訪問器來檢索各種成分。要做到這一點,你需要恢復原來的實例 - 除了丟失。您可以在Meal中創建一個虛擬功能,該功能在Salad中執行,並彙編各種成分。

+0

如果你真的想存儲類型'番茄'等參數,你需要一個'std :: tuple'。 – pmr 2012-01-05 00:43:13

+1

爲什麼這麼麻煩?你可以說'cookbook.emplace_back(新沙拉());'等 – 2012-01-05 00:54:35

1

那麼,與C++ 11,你可以這樣做:

std::vector<Meal *> cookbook = { 
    new Soup(new Tomato(), new Potato()), 
    new Soup(new Potato(), new Broccoli()), 
    new Salad(new Tomato(), new Potato(), new Tomato()), 
    // etc 
}; 

當然,這最終還是運行operator new和運行時的建設者,而不是編譯時間,但更緊湊至少位。

編輯

不幸的是C++ 11不提供一種方式來創建靜態存儲持續時間的未命名的對象,並採取其地址用在這樣的構建。你需要給這些對象的名稱,像這樣:

static Tomato tomatoes[] = { 
    { /* first tomato initializer */ }, 
    { /* second */ }, 
    /* more */ 
} 
static Potato potatoes[] = { ... 
static Soup soups[] = { 
    { &tomatoes[0], &potatoes[0] }, 
    ... 
static Salad salads[] = { 
    { &tomatoes[4], &potatoes[2], &tomatoes[5] }, 
    ... 
std::vector<Meal *> cookbook = { 
    &soups[0], &soups[1], &soups[2], ... 
    &salads[0], &salads[1], ... 

這是錯誤的極端容易,但它是一個不錯的選擇什麼樣的C++,如果你遵循錫安Sheevok的答案代碼來生成。

+0

也許'constexpr'可能有助於在運行時完成所有這些操作?代替'新番茄',寫一個函數'constexpr Tomato newTomato',它創建一個番茄*值*。在我做一些實驗的同時忍受我。 – 2012-01-05 01:13:57

+0

@AaronMcDaid:除了通過指針(除了通過boost :: variadic),不能在容器中存儲不同的類型,所以你不會走得很遠。 – 2012-01-05 01:29:42

+0

@MooingDuck,我明白了。我做了一些「sizeof」實驗,我意識到數組元素互相破壞。沒關係。我應該刪除我的答案:-( – 2012-01-05 01:38:58

0

好吧,如果這一切都在編譯時已知,那麼這裏是一個壞主意:

template <class... Types> 
struct cookbook { 
    std::tuple<Types...> data; 

    template<int i, bool safe> 
    struct safe { 
     static const Meal* get(const std::tuple<Types...>& data) 
     {return std::get<i, Types...>(data);} 
    }; 
    template<int i, false> 
    struct safe { 
     static const Meal* get(const std::tuple<Types...>& data) 
     {return NULL;} 
    }; 

    class iterator { 
    protected: 
     friend cookbook; 
     cookbook* parent; 
     int index; 
    public: 
     iterator(cookbook* p, int i) : parent(p), index(i) {} 
     iterator& operator++() {++index; return *this;} 
     iterator& operator+=(int i) {index += i; return *this;} 
     const Meal& operator*() const { 
      switch (i) { 
      case 0: return p->safe<0, Types...>::get(data); 
      case 1: return p->safe<1, Types...>::get(data); 
      case 2: return p->safe<2, Types...>::get(data); 
      case 3: return p->safe<3, Types...>::get(data); 
      case 3: return p->safe<4, Types...>::get(data); 
      case 3: return p->safe<5, Types...>::get(data); 
      } 
     } 
     bool operator==(const iterator& r) { 
      return parent==r.parent && index==r.index; 
     } 
     bool operator!=(const iterator& r) { 
      return index!=r.index || parent!=r.parent; 
     } 
    }; 
    iterator begin() {return iterator(this, 0);} 
    iterator end() {return iterator(this, tuple_size<Types...>::value+1);} 
}; 

這實際上(有效)一成員每個Meal類型創建一個菜譜對象和硬編碼迭代器來抓取每一個。

我懷疑這會實際編譯,因爲我從來沒有嘗試過這樣的事情,並沒有可變數據模板的編譯器。另外,我只實現了它的一小部分。你可以用

template<class RecipieType, class....Types> 
struct Recipie : RecipieType { 
     //same as above 

這將讓你

#define soup1types Tomoato,Potato 
#define soup2types Potato,Broccoli 
#define salad1types Tomato,Carrot,Broccoli 
#define cooktypes Recipie<Soup,soup1types>\ 
        Recipie<Soup,soup2types>\ 
        Recipie<Salad,salad1types> 
cookbook<cooktypes> book; //bam. recipies exist. 

還做的這另一層,人們可能會恨你(和我),當他們看到這一點。

+0

即時通訊告訴boost :: fusion做到了這一點,但更好,更安全。一如既往,boost有你所需要的。 – 2012-01-05 01:54:44

相關問題