动态构造函数



我担心这个网站上有这样的答案,但我找不到,因为我甚至不知道如何表达这个问题。所以问题来了:

我有一个体素drowing函数。首先,我计算偏移量、角度和其他东西,然后我做溺水。但我对每个函数都做了几个版本,因为有时我想复制像素,有时是blit,有时是为每个像素blit 3*3平方以获得平滑效果,有时只是将像素复制到屏幕上的n*n个像素(如果调整对象的大小)。一个函数中心的那个小部分有很多版本。

我能做些什么,而不是写10个相同的函数,它们只在代码的中心部分有所不同?出于性能原因,传递函数指针作为参数不是一种选择。我不确定让它们内联是否有效,因为我发送的参数不同:有时我计算体积(Z值),有时我知道像素是从下到上绘制的。

我想在C++中有一些方法可以做到这一点,每个人都知道。请告诉我做这件事需要学什么。谢谢

实现这一点的传统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));

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

CRTP模板方法

最后一种机制是模板方法的编译时多态性版本,它使用了Curioly Recurring模板模式。

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,并且希望独立地改变它们的实现),那么使用模板方法也非常痛苦。您要么会导致代码重复,要么会导致混合继承。

我建议使用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元素,那么这就是方法。

您可以简单地使用策略模式

所以,不是像这样的东西

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、函子、传入的策略类的虚拟成员函数。有很多选项。

你确定

"传递函数指针作为参数不是一个选项";

它真的会减慢速度吗?

如果您的"中心部分"可以很好地参数化,那么您可以使用更高阶的函数
下面是一个简单的函数示例,它返回一个在参数中添加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);
}

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

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

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

我不确定让它们内联是否有效,因为我发送的参数不同:有时我计算体积(Z值),有时我知道像素是从下到上绘制的。

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

最新更新