Я хочу реализовать Составной шаблон:
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));
}
Я не уверен, что действительно вижу проблему, как таковую. Почему бы не сделать что-то вроде:
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.
Вы можете сделать функторы вместо методов:
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!