动态构建功能

时间:2013-07-27 09:12:11

标签: c++ function

我担心在这个网站的某个地方会回答这样的事情,但我找不到它,因为我甚至不知道如何制定这个问题。所以这就是问题所在:

我有一个体素投掷功能。首先,我计算偏移,角度和东西,然后我做了瞌睡。但是我为每个函数制作了几个版本,因为有时我想复制像素,有时是blit,有时为每个像素点亮3 * 3平方以获得平滑效果,如果对象调整大小,有时只是将像素复制到屏幕上的n * n像素。在函数的中心有很多版本用于那个小部分。

我可以做什么而不是编写10个仅由代码的中心部分不同的相同功能?出于性能原因,将函数指针作为参数传递不是一种选择。我不确定让他们内联会做的伎俩,因为我发送的参数不同:有时我计算体积(Z值),有时我知道像素是从下到上绘制的。

我认为在C ++中有一些方法可以让每个人都知道。 请告诉我学习这项工作需要学习的内容。感谢。

5 个答案:

答案 0 :(得分:7)

传统的OO方法是模板方法模式策略模式

模板方法

第一个是Vincenzo答案中描述的技术的扩展:您不是编写简单的非虚拟包装器,而是编写包含整个算法的非虚函数。那些可能不同的部分是虚函数调用。 给定实现所需的特定参数存储在提供该实现的派生类对象中。

例如

class VoxelDrawer {
protected:
  virtual void copy(Coord from, Coord to) = 0;
  // any other functions you might want to change
public:
  virtual ~VoxelDrawer() {}
  void draw(arg) {
    for (;;) {
      // implement full algorithm
      copy(a,b);
    }
  }
};
class SmoothedVoxelDrawer: public VoxelDrawer {
  int radius; // algorithm-specific argument
  void copy(Coord from, Coord to) {
    blit(from.dx(-radius).dy(-radius),
         to.dx(-radius).dy(-radius),
         2*radius, 2*radius);
  }
public:
  SmoothedVoxelDrawer(int r) : radius(r) {}
};

策略

这是类似的,但不是使用继承,而是将多态Copier对象作为参数传递给函数。它更灵活,它将您的各种复制策略与特定功能分离,您可以在其他功能中重复使用复制策略。

struct VoxelCopier {
  virtual void operator()(Coord from, Coord to) = 0;
};
struct SmoothedVoxelCopier: public VoxelCopier {
  // etc. as for SmoothedVoxelDrawer
};

void draw_voxels(arguments, VoxelCopier &copy) {
  for (;;) {
    // implement full algorithm
    copy(a,b);
  }
}

虽然比传入函数指针更整洁,但模板方法和策略都不可能比传递函数指针具有更好的性能:运行时多态仍然是间接函数调用。

政策

策略模式的现代C ++等价物是策略模式。这简单地将运行时多态性替换为编译时多态,以避免间接函数调用并启用内联

// you don't need a common base class for policies,
// since templates use duck typing
struct SmoothedVoxelCopier {
  int radius;
  void copy(Coord from, Coord to) { ... }
};
template <typename CopyPolicy>
void draw_voxels(arguments, CopyPolicy cp) {
  for (;;) {
    // implement full algorithm
    cp.copy(a,b);
  }
}

由于类型扣除,您只需拨打

即可
draw_voxels(arguments, SmoothedVoxelCopier(radius));
draw_voxels(arguments, OtherVoxelCopier(whatever));

NB。我在这里略微不一致:我使用operator()使我的策略调用看起来像常规函数,但是我的策略的常规方法。只要你选择一个并坚持下去,这只是一个品味问题。

CRTP模板方法

有一个最终机制,即模板方法的编译时多态版本,并使用奇怪的重复模板模式。

template <typename Impl>
class VoxelDrawerBase {
protected:
  Impl& impl() { return *static_cast<Impl*>(this); }

  void copy(Coord from, Coord to) {...}
  // *optional* default implementation, is *not* virtual
public:
  void draw(arg) {
    for (;;) {
      // implement full algorithm
      impl().copy(a,b);
    }
  }
};
class SmoothedVoxelDrawer: public VoxelDrawerBase<SmoothedVoxelDrawer> {
  int radius; // algorithm-specific argument
  void copy(Coord from, Coord to) {
    blit(from.dx(-radius).dy(-radius),
         to.dx(-radius).dy(-radius),
         2*radius, 2*radius);
  }
public:
  SmoothedVoxelDrawer(int r) : radius(r) {}
};

摘要

一般情况下,我更倾向于使用策略/策略模式来实现较低的耦合和更好的重用,并且只选择模板方法模式,只有在您参数化的顶级算法才能真正设置(即,当您'要么重构现有代码,要么真正确定你对变异点的分析,重用真的不是问题。

如果存在多个变异轴(即,您有多个方法,如copy,并希望独立改变其实现),则使用模板方法也非常痛苦。您最终会得到代码重复或mixin继承。

答案 1 :(得分:3)

我建议使用NVI成语。

你有一个公共方法,它调用一个私有函数来实现必须因大小写而异的逻辑。

派生类必须提供专用于其特定任务的私有函数的实现。

示例:

class A {
public:
    void do_base() {
        // [pre]
        specialized_do();
        // [post]
    }

private:
    virtual void specialized_do() = 0;
};

class B : public A {
private:
    void specialized_do() {
        // [implementation]
    }
};

优点是您可以在基类中保留一个公共实现,并根据任何子类的需要对其进行详细说明(只需要重新实现specialized_do方法)。

缺点是每个实现都需要不同的类型,但如果您的用例是绘制不同的UI元素,那么这就是您的选择。

答案 2 :(得分:1)

您只需使用strategy pattern

即可

所以,而不是像

那样的东西
void do_something_one_way(...)
{
    //blah
    //blah
    //blah
    one_way();
    //blah
    //blah
}

void do_something_another_way(...)
{
    //blah
    //blah
    //blah
    another_way();
    //blah
    //blah
}

你将

void do_something(...)
{
    //blah
    //blah
    //blah
    any_which_way();
    //blah
    //blah
}

any_which_way可以是lambda,functor,传入的策略类的虚拟成员函数。有很多选项。

你确定吗

  

“将函数指针作为参数传递不是一个选项”

它真的减慢了吗?

答案 3 :(得分:0)

如果你的“中心部分”可以很好地参数化,你可以使用更高阶的函数 下面是一个函数的简单示例,该函数返回一个函数,该函数将n添加到其参数中:

#include <iostream>
#include<functional>
std::function<int(int)> n_adder(int n)
{
    return [=](int x){return x+n;};
}
int main()
{
    auto add_one = n_adder(1);
    std::cout<<add_one(5);
}

答案 4 :(得分:0)

您可以使用Template Method patternStrategy pattern。 通常,模板方法模式用于白盒框架,当您需要了解框架的内部结构时,才能正确地对类进行子类化。 策略模式通常用在黑盒框架中,当你不应该知道框架的实现时,因为你只需要理解你应该实现的方法的契约。

  
    

出于性能原因,将函数指针作为参数传递不是一种选择。

  

您确定传递一个附加参数会导致性能问题吗?在这种情况下,如果您使用OOP技术(如模板方法或策略),则可能会有类似的性能损失。但通常需要使用profilier来确定性能下降的来源。与复杂算法相比,虚拟调用,传递附加参数,通过指针调用函数通常非常便宜。您可能会发现,与其他代码相比,这些技术消耗的CPU资源百分比微不足道。

  
    

我不确定让他们内联会做的伎俩,因为我发送的参数不同:有时我计算音量(Z值),有时我知道像素是从下到上绘制的。

  

在所有情况下,您都可以传递绘图所需的所有参数。或者,如果使用Tempate方法模式,则基类可以提供可以返回在不同情况下绘制所需的数据的方法。在策略模式中,您可以将可以提供此类数据的对象实例传递给策略实现。