在C ++中定义许多组合对象的最佳方法?

时间:2012-01-05 00:23:44

标签: c++ templates memory-management composition

我是素食主义者,所以假设我们有蔬菜:

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...

因此,我们在堆上创建了许多小对象,这些对象通过构造函数参数组合在一起,并在运行时推送到std :: vector。显然,这种设计的缺点是,我们必须自己管理内存并删除我们的膳食析构函数中的蔬菜对象,并在超出范围时删除我们的菜谱餐。

因此,一个可能的设计选择是使用智能指针来消除我们的膳食和蔬菜进行记忆管理的负担。

但是我想知道是否可以在编译时编写这本食谱,也许还有一些模板魔法?食谱不一定必须是std :: vector,但我们仍然希望能够迭代它,获取Meal对象并在组合餐中调用成员函数。有更好的方法吗?

4 个答案:

答案 0 :(得分:1)

我发现代码生成对我来说正在成为一个非常有用的工具。也许写一个简短的程序,甚至可能只是一个脚本,而不是为你生成样板代码。

每当你添加到你的食谱中时,你都会运行程序/脚本,这将生成每餐的标题和标题,甚至是食谱本身的一些来源。

你和你这一代人的深入程度取决于你。您可以编辑代码生成器的源代码来添加新的菜单,您可以进行一些简单的基于文本的解析,或者,如果真的值得花时间和精力进行维护,请将代码生成器转换为编辑器(将代码生成作为其输出)

至少有一个非常了解的AAA游戏引擎实际上为任何标记为与本机代码交互的脚本生成C ++头文件。从那里,源文件中的宏实现了样板方法。其余的方法由开发人员实现。

更新: C ++ 11实际上支持variadic template arguments。我对C ++ 11没有任何经验,所以我不确定可变参数模板参数是否会支持我们正在查看的内容。

答案 1 :(得分: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], ...

这极易出错,但如果你遵循Sion Sheevok的回答,那么对于生成什么样的C ++代码是一个很好的选择。

答案 2 :(得分: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类型创建了一个(有效)成员的cookbook对象,以及一个用于获取每个{em>成员的硬编码迭代器。

我怀疑这实际上是否会编译,因为我从未尝试过这样的事情并且没有使用可变参数模板的编译器。另外,我只实现了它的一小部分。你可以用

做另一层
 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.

此外,当他们看到这个时,人们可能会讨厌你(和我)。

答案 3 :(得分:-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>()));

当然,为了在某种形状或形式上有用,你仍然需要提供一些访问器来检索各种成分。为此,您需要恢复原始的Salad实例化 - 除了丢失。您可以在Meal中创建一个虚拟函数,该函数在Salad中实现,并汇集各种令人难以置信的内容。