C++模板元编程功夫挑战(取代宏函数定义)

kiz*_*zx2 11 c++ templates metaprogramming

情况

我想实现Composite模式:

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;
};
Run Code Online (Sandbox Code Playgroud)

问题

你看,由于我对复合模式的简单要求,我最终写了很多相同的重复代码迭代在同一个数组上.

宏的可能解决方案

#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;                             \
        }                                                            \
    }
Run Code Online (Sandbox Code Playgroud)

现在我可以像这样使用它:

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
};
Run Code Online (Sandbox Code Playgroud)

这个问题

有没有办法用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"                                                \
    }
Run Code Online (Sandbox Code Playgroud)

看起来这个不能被替换 for_each

后果

看看GMan的优秀答案,C++的这一部分肯定是非平凡的.就个人而言,如果我们只是想减少样板代码的数量,我认为宏可能是适合这种特殊情况的工具.

GMan建议std::mem_funstd::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));
}
Run Code Online (Sandbox Code Playgroud)

GMa*_*ckG 10

我不确定我是否真的看到了这个问题.为什么不是这样的:

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));
}
Run Code Online (Sandbox Code Playgroud)

还不错.


如果你真的想摆脱(小)样板代码,添加:

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

作为私人实用程序成员,然后使用:

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));
}
Run Code Online (Sandbox Code Playgroud)

更简洁一点.不需要元编程.

实际上,元编程最终会失败.您正在尝试生成以文本方式定义的函数.元编程无法生成文本,因此您不可避免地会在某处使用宏来生成文本.

在下一级,您将编写该函数,然后尝试取出样板代码.std::for_each做得很好.当然,作为已经证明,如果你发现是太多的重复,只是因素说出来为好.


作为回应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;
};
Run Code Online (Sandbox Code Playgroud)

这主要是自动记录操作.然后:

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.";
    }
};
Run Code Online (Sandbox Code Playgroud)

像那样.有关lexical_cast的信息.