代码之家  ›  专栏  ›  技术社区  ›  kizzx2

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

  •  11
  • kizzx2  · 技术社区  · 15 年前

    形势

    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++的这一部分绝对是不平凡的。就个人而言,如果我们只是想减少样板代码的数量,我认为宏可能是适合这种特殊情况的合适工具。

    格曼建议 std::mem_fun std::bind2nd

    为了便于说明,下面是使用 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));
    }
    
    2 回复  |  直到 15 年前
        1
  •  10
  •   Community CDub    8 年前

    我不确定我真的看到了问题本身。为什么不这样做:

    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 做得很好。当然,正如已经证明的,如果你发现 那个 如果重复太多,也要考虑一下。


    为了回应 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

        2
  •  2
  •   stefanB    15 年前

    可以生成函子而不是方法:

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

    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!
    

    为了保持接口的一致性,您可以更进一步。

    Run Running 和结构 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!