Шаблонная проблема кун-фу метапрограммирования C++ (заменяющий макро-функциональное определение)

Ситуация

Я хочу реализовать Составной шаблон:

class Animal
{
public:
    virtual void Run() = 0;
    virtual void Eat(const std::string & food) = 0;
    virtual ~Animal(){}
};

class Human : public Animal
{
public:
    void Run(){ std::cout << "Hey Guys I'm Running!" << std::endl; }
    void Eat(const std::string & food)
    {
        std::cout << "I am eating " << food << "; Yummy!" << std::endl;
    }
};

class Horse : public Animal
{
public:
    void Run(){ std::cout << "I am running real fast!" << std::endl; }
    void Eat(const std::string & food)
    {
        std::cout << "Meah!! " << food << ", Meah!!" << std::endl;
    }
};

class CompositeAnimal : public Animal
{
public:
    void Run()
    {
        for(std::vector<Animal *>::iterator i = animals.begin();
            i != animals.end(); ++i)
        {
            (*i)->Run();
        }
    }

    // It's not DRY. yuck!
    void Eat(const std::string & food)
    {
        for(std::vector<Animal *>::iterator i = animals.begin();
            i != animals.end(); ++i)
        {
            (*i)->Eat(food);
        }
    }

    void Add(Animal * animal)
    {
        animals.push_back(animal);
    }

private:
    std::vector<Animal *> animals;
};

Проблема

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

Возможное решение с макросами

#define COMPOSITE_ANIMAL_DELEGATE(_methodName, _paramArgs, _callArgs)\
    void _methodName _paramArgs                                      \
    {                                                                \
        for(std::vector<Animal *>::iterator i = animals.begin();     \
            i != animals.end(); ++i)                                 \
        {                                                            \
            (*i)->_methodName _callArgs;                             \
        }                                                            \
    }

Теперь я могу использовать его как это:

class CompositeAnimal : public Animal
{
public:
    // It "seems" DRY. Cool

    COMPOSITE_ANIMAL_DELEGATE(Run, (), ())
    COMPOSITE_ANIMAL_DELEGATE(Eat, (const std::string & food), (food))

    void Add(Animal * animal)
    {
        animals.push_back(animal);
    }

private:
    std::vector<Animal *> animals
};

Вопрос

Существует ли способ сделать это "инструмент для очистки" с метапрограммированием C++?

Более трудный вопрос

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

#define LOGGED_COMPOSITE_ANIMAL_DELEGATE(_methodName, _paramArgs, _callArgs)\
    void _methodName _paramArgs                                      \
    {                                                                \
        log << "Iterating over " << animals.size() << " animals";    \
        for(std::vector<Animal *>::iterator i = animals.begin();     \
            i != animals.end(); ++i)                                 \
        {                                                            \
            (*i)->_methodName _callArgs;                             \
        }                                                            \
        log << "Done"                                                \
    }

Похож это не может быть заменено for_each

Последствие

Смотря на превосходный ответ GMAN, эта часть C++ определенно нетривиальна. Лично, если мы просто хотим уменьшить объем шаблонного кода, я думаю, что макросы, вероятно, являются правильным инструментом для задания для этой конкретной ситуации.

GMan предлагается std::mem_fun и std::bind2nd возвратить функторы. К сожалению, этот API не поддерживает 3 параметра (я не могу полагать, что что-то вроде этого было выпущено в STL).

Для иллюстративной цели вот использование функций делегата boost::bind вместо этого:

void Run()
{
    for_each(boost::bind(&Animal::Run, _1));
}

void Eat(const std::string & food)
{
    for_each(boost::bind(&Animal::Eat, _1, food));
}
11
задан kizzx2 22 July 2010 в 17:18
поделиться

2 ответа

Я не уверен, что действительно вижу проблему, как таковую. Почему бы не сделать что-то вроде:

void Run()
{
    std::for_each(animals.begin(), animals.end(),
                    std::mem_fun(&Animal::Run));
}

void Eat(const std::string & food)
{
    std::for_each(animals.begin(), animals.end(),
                    std::bind2nd(std::mem_fun(&Animal::Eat), food));
}

Не так уж плохо.


Если вы действительно хотите избавиться от (небольшого) кодового кода, добавьте:

template <typename Func>
void for_each(Func func)
{
    std::for_each(animals.begin(), animals.end(), func);
}

Как частный член утилиты, тогда используйте это:

void Run()
{
    for_each(std::mem_fun(&Animal::Run));
}

void Eat(const std::string & food)
{
    for_each(std::bind2nd(std::mem_fun(&Animal::Eat), food));
}

Немного более лаконично. Нет необходимости в мета-программировании.

На самом деле, мета-программирование в конечном итоге потерпит неудачу. Вы пытаетесь генерировать функции, которые определены текстуально. Мета-программирование не может генерировать текст, поэтому вы неизбежно будете использовать макрос для генерации текста.

На следующем уровне вы напишете функцию, а затем попытаетесь убрать из нее шаблонный код. std::for_each делает это довольно хорошо. И конечно, как было продемонстрировано, если вы находите that слишком много повторений, просто вычтите и это.


В ответ на пример LoggedCompositeAnimal в комментарии, лучше всего сделать что-то вроде:

class log_action
{
public:
    // could also take the stream to output to
    log_action(const std::string& pMessage) :
    mMessage(pMessage),
    mTime(std::clock())
    {
        std::cout << "Ready to call " << pMessage << std::endl;
    }

    ~log_action(void)
    {
        const std::clock_t endTime = std::clock();

        std::cout << "Done calling " << pMessage << std::endl;
        std::cout << "Spent time: " << ((endTime - mTime) / CLOCKS_PER_SEC)
                    << " seconds." << std::endl;
    }

private:
    std::string mMessage;
    std::clock_t mTime;
};

Что просто автоматически регистрирует действия. Затем:

class LoggedCompositeAnimal : public CompositeAnimal
{
public:
    void Run()
    {
        log_action log(compose_message("Run"));
        CompositeAnimal::Run();
    }

    void Eat(const std::string & food)
    {
        log_action log(compose_message("Eat"));
        CompositeAnimal::Eat(food);
    }

private:
    const std::string compose_message(const std::string& pAction)
    {
        return pAction + " on " +
                    lexical_cast<std::string>(animals.size()) + " animals.";
    }
};

Например. Информация о lexical_cast.

10
ответ дан 3 December 2019 в 09:18
поделиться

Вы можете сделать функторы вместо методов:

struct Run
{
    void operator()(Animal * a)
    {
        a->Run();
    }
};

struct Eat
{
    std::string food;
    Eat(const std::string& food) : food(food) {}

    void operator()(Animal * a)
    {
        a->Eat(food);
    }
};

И добавить CompositeAnimal::apply (#include ):

template <typename Func>
void apply(Func& f)
{
    std::for_each(animals.begin(), animals.end(), f);
}

Тогда ваш код будет работать так:

int main()
{
    CompositeAnimal ca;
    ca.Add(new Horse());
    ca.Add(new Human());

    Run r;
    ca.apply(r);

    Eat e("dinner");
    ca.apply(e);
}

Выход:

> ./x
I am running real fast!
Hey Guys I'm Running!
Meah!! dinner, Meah!!
I am eating dinner; Yummy!

Чтобы сохранить согласованность интерфейса, вы можете пойти на шаг дальше.

Переименуйте struct Run в Running и struct Eat в Eating, чтобы предотвратить столкновение методов и структур.

Тогда CompositeAnimal::Run будет выглядеть следующим образом, используя метод apply и struct Running:

void Run()
{
    Running r;
    apply(r);
}

И аналогично CompositeAnimal: :Eat:

void Eat(const std::string & food)
{
    Eating e(food);
    apply(e);
}

И вы можете вызвать теперь:

ca.Run();
ca.Eat("dinner");

вывод все тот же:

I am running real fast!
Hey Guys I'm Running!
Meah!! dinner, Meah!!
I am eating dinner; Yummy!
2
ответ дан 3 December 2019 в 09:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: