Лучший способ определить много составных объектов в C ++? - PullRequest
1 голос
/ 05 января 2012

Я вегетарианец, поэтому предположим, что у нас есть овощи:

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 ]

1 голос
/ 05 января 2012

Я обнаружил, что генерация кода становится невероятно полезным инструментом для меня.Может быть, написать короткую программу, возможно, даже просто сценарий, который сгенерирует шаблонный код для вас.

Каждый раз, когда вы добавляете в свою кулинарную книгу, вы запускаете программу / сценарий, который будет генерировать заголовки для каждого приема пищи.и заголовок, может быть, даже какой-то из источника, для самой кулинарной книги.

Насколько глубоко вы разбираетесь со своим поколением, зависит от вас.Вы можете просто отредактировать исходный код генератора кода, чтобы добавить новые блюда, вы можете выполнить простой анализ на основе текста или, если это действительно стоит времени и усилий, превратить ваш генератор кода в редактор (с генерацией кода в качестве вывода).

По крайней мере, один очень хорошо известный игровой движок AAA на самом деле генерирует заголовочные файлы C ++ для любых сценариев, помеченных для взаимодействия с собственным кодом.Оттуда макрос в исходном файле реализует стандартные методы.Остальные методы реализованы разработчиками.

Обновление: C ++ 11 фактически поддерживает аргументы шаблона variadic .У меня нет никакого опыта работы с C ++ 11, поэтому я не уверен, что аргументы шаблона с переменным числом аргументов будут соответствовать тому, на что мы смотрим.

1 голос
/ 05 января 2012

Ну, с 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
};

Конечно, это все равно приводит к запуску оператора new и конструкторов во время выполнения, а не во время компиляции, но, по крайней мере, немного большеcompact.

edit

К сожалению, 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 ++ генерировать, если вы следуете ответу Сиона Шивока.

0 голосов
/ 05 января 2012

Ну, если все это известно во время компиляции, то вот плохая идея:

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 голосов
/ 05 января 2012

Даже с шаблонами вам понадобится какое-то стирание типа, чтобы поместить полученную инстанцирование куда-нибудь.У вас может быть базовый класс 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 и объединяет различные элементы.

...