【问题标题】:C++ template meta-programming kung-fu challenge (replacing a macro function definition)C++模板元编程功夫挑战(替换宏函数定义)
【发布时间】:2010-07-22 05:52:39
【问题描述】:

情况

我要实现复合模式:

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_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));
}

【问题讨论】:

  • 下面的函子解决了更难的问题,函子存储参数,并用容器中的每个对象调用它,然后它可以用任何参数调用每个对象的任何方法。您可以根据需要构建任意数量的函子,它们很简单。

标签: c++ templates metaprogramming


【解决方案1】:

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

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的信息。

【讨论】:

  • std::bind2nd 对我来说很陌生。如果我有 3 个参数怎么办?
  • 我认为这里的循环迭代是一个特殊情况,我对这个糟糕的例子不利。假设我们有一个 LoggedCompositeAnimal 记录所有操作 beforeafter 迭代(“Ready to call Run on 3 animals...”、“Done call run on 3动物。花费时间 50 秒”)。
  • @kizzx2:实际上,一个是极限。 :) 标准库的这部分设计糟糕透顶。如果你想认真对待绑定的东西,请查看Boost.Bind。它有一个更简单、更富有表现力和更强大的界面。 (否则,您必须手动制作仿函数。)在您的第二条评论中,除了制作可重用的代码外,也没什么可做的。我将添加一个示例,看看我们是否在同一页面上。
  • @GMan:添加的是一些严肃的功夫!这非常接近理想的解决方案(我将研究 boost::bind)。我想“然后更改for_each”部分将驻留在LoggedCompositeAnimal.cpp 中,并且不会为其他所有人更改for_each,对吗? (这一切让我想知道在这种情况下“只使用宏”是否是“正确”的解决方案)
  • @kizz:抱歉,我将LoggedCompositeAnimal 视为我们唯一的复合类。我已经编辑以反映我将如何以CompositeAnimal 为基础实现它。
【解决方案2】:

你可以用函子代替方法:

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 &lt;algorithm&gt;):

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!

【讨论】:

  • 感谢函子的信息!但是,这种方法的一个潜在问题是它破坏了复合模式。我的来电者不应该知道我给的Animal 实际上是Composite。所以我的来电者不必知道打电话给apply,他应该直接打电话给EatRun
  • 我注意到了,因此做了一个小修复 :)
  • 你可以添加 'template void operator->*(T t) { apply(t); }and then even write ca->*Run(); ca->*Eat("布丁");`:-)
  • 仔细观察,我不确定 Functor 方法如何解决原始前提(DRY)。由于我需要为每个方法创建新方法,然后委托给 Functor,它的唯一功能是提供 RunEat 的功能,我为什么不直接在 Run 和 @ 中编写函数体987654348@ 但必须委托给函子?
  • 这取决于您的设计。你可以只提供apply 和仿函数。那么唯一的区别就是 Compound 类 apply 会将其应用于所有对象。您可以通过添加新函子来添加新功能。
猜你喜欢
  • 2014-03-18
  • 2023-03-31
  • 2016-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-18
相关资源
最近更新 更多