我担心在这个网站的某个地方会回答这样的事情,但我找不到它,因为我甚至不知道如何制定这个问题。所以这就是问题所在:
我有一个体素投掷功能。首先,我计算偏移,角度和东西,然后我做了瞌睡。但是我为每个函数制作了几个版本,因为有时我想复制像素,有时是blit,有时为每个像素点亮3 * 3平方以获得平滑效果,如果对象调整大小,有时只是将像素复制到屏幕上的n * n像素。在函数的中心有很多版本用于那个小部分。
我可以做什么而不是编写10个仅由代码的中心部分不同的相同功能?出于性能原因,将函数指针作为参数传递不是一种选择。我不确定让他们内联会做的伎俩,因为我发送的参数不同:有时我计算体积(Z值),有时我知道像素是从下到上绘制的。
我认为在C ++中有一些方法可以让每个人都知道。 请告诉我学习这项工作需要学习的内容。感谢。
答案 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 ©) {
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()
使我的策略调用看起来像常规函数,但是我的策略的常规方法。只要你选择一个并坚持下去,这只是一个品味问题。
有一个最终机制,即模板方法的编译时多态版本,并使用奇怪的重复模板模式。
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 pattern或Strategy pattern。 通常,模板方法模式用于白盒框架,当您需要了解框架的内部结构时,才能正确地对类进行子类化。 策略模式通常用在黑盒框架中,当你不应该知道框架的实现时,因为你只需要理解你应该实现的方法的契约。
出于性能原因,将函数指针作为参数传递不是一种选择。
您确定传递一个附加参数会导致性能问题吗?在这种情况下,如果您使用OOP技术(如模板方法或策略),则可能会有类似的性能损失。但通常需要使用profilier来确定性能下降的来源。与复杂算法相比,虚拟调用,传递附加参数,通过指针调用函数通常非常便宜。您可能会发现,与其他代码相比,这些技术消耗的CPU资源百分比微不足道。
我不确定让他们内联会做的伎俩,因为我发送的参数不同:有时我计算音量(Z值),有时我知道像素是从下到上绘制的。
在所有情况下,您都可以传递绘图所需的所有参数。或者,如果使用Tempate方法模式,则基类可以提供可以返回在不同情况下绘制所需的数据的方法。在策略模式中,您可以将可以提供此类数据的对象实例传递给策略实现。