我有一个复杂的算法。它使用许多变量,在初始化时计算辅助数组,并沿途计算数组。由于算法很复杂,我将其分解为几个函数。
现在,我实际上并没有从惯用的方式看出这可能是一个类;我的意思是,我只是习惯将算法作为函数。用法只是:
Calculation calc(/* several parameters */);
calc.calculate();
// get the heterogenous results via getters
另一方面,将它放入课堂有以下优点:
混合方式是将算法类放入源文件中,并通过使用它的函数访问它。算法的用户不会看到该类。
有没有人有宝贵的想法可以帮助我?
非常感谢你!
答案 0 :(得分:32)
我有一个复杂的算法。这使用了许多变量,在初始化时计算辅助数组,并在此过程中计算数组。[...]
现在,我实际上并没有看到这可能是一种习惯性的方式
事实并非如此,但很多人做同样的事情(我也做了几次)。
不要为算法创建类,而是考虑将输入和输出转换为类/结构。
即代替:
Calculation calc(a, b, c, d, e, f, g);
calc.calculate();
// use getters on calc from here on
你可以写:
CalcInputs inputs(a, b, c, d, e, f, g);
CalcResult output = calculate(inputs); // calculate is now free function
// use getters on output from here on
这不会产生任何问题并执行相同(实际上更好)的数据分组。
答案 1 :(得分:16)
我认为将算法(或者更好,计算)表示为类是非常惯用的。 OOP中对象类的定义之一是“对该数据进行操作的数据和函数”。具有输入,输出和中间数据的compex算法完美匹配此定义。
我已经多次完成了这项工作,它显着简化了(人工)代码流分析,使整个事情更容易推理,调试和测试。
答案 2 :(得分:6)
如果客户端代码的抽象是算法,那么 可能想保留一个纯粹的功能界面,而不是 在那里介绍其他类型。这很常见 另一方面,这种功能可以在源中实现 文件,为其定义公共数据结构或类 内部使用,所以你可能有:
double calculation( /* input parameters */ )
{
SupportClass calc( /* input parameters */ );
calc.part1();
calc.part2();
// etc...
return calc.results();
}
根据代码的组织方式,SupportClass
将会是。{1}}
在源文件中的未命名的命名空间中(可能是最多的
常见情况),或在“私人”标题中,仅包含在
参与算法的来源。
答案 3 :(得分:3)
它真的归结为:算法是否需要访问不应该公开的类的私有区域?如果答案是肯定的(除非你愿意重构你的类接口,具体取决于具体情况)你应该使用成员函数,如果没有,那么自由函数就足够了。
以标准库为例。大多数算法都是作为自由函数提供的,因为它们只访问类的公共接口(例如,标准容器的迭代器)。
答案 4 :(得分:3)
您是否需要每次以完全相同的顺序调用完全相同的函数?那么你不应该要求调用代码来执行此操作。将您的算法拆分成多个函数很好,但我仍然有一个调用下一个然后下一个调用,依此类推,结果/参数的结构一直传递。对于某些程序的一次性调用,类不适合。
我对类执行此操作的唯一方法是,如果类封装了所有输入数据本身,然后在其上调用myClass.nameOfMyAlgorithm()
,以及其他可能的操作。然后你有数据+操纵器。但只是操纵者?是的,我不太确定。
答案 5 :(得分:3)
这实际上取决于您想要封装的算法类型。一般来说,我同意John Carmack:" Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function."
答案 6 :(得分:3)
在现代C ++中,区别已被侵蚀了很多。即使从运算符重载前ANSI语言,您也可以创建一个类,其实例在语法上与函数类似:
struct Multiplier
{
int factor_;
Multiplier(int f) : factor_(f) { }
int operator()(int v) const
{
return v * _factor;
}
};
Multipler doubler(2);
std::cout << doubler(3) << std::endl; // prints 6
这样的类/结构被称为仿函数,并且可以在其构造函数中捕获“上下文”值。这允许您在两个阶段中有效地将参数传递给函数:一些在构造函数调用中,一些稍后在每次调用它时都是真实的。这称为部分功能应用。
要将此与您的示例相关联,您的calculate
成员函数可以转换为operator()
,然后Calculation
实例将成为一个函数! (或接近。)
为了统一这些想法,你可以尝试将普通函数看作一个只有一个实例的仿函数(因此不需要构造函数 - 尽管这并不能保证函数只依赖于它的形式参数:它可能取决于全局变量...)
而不是问“我应该把这个算法放在一个函数还是一个类中?”相反问问自己“能够在两个或更多阶段将参数传递给该算法是否有用?”在您的示例中,所有参数都进入构造函数,而后来调用calculate
则没有,因此要求您的类的用户进行两次调用是没有意义的。
在C ++ 11中,由于认识到这些想法的流动性,区别进一步分解(事情变得更方便):
auto doubler = [] (int val) { return val * 2; };
std::cout << doubler(3) << std::endl; // prints 6
这里,doubler
是一个lambda,它本质上是一种很好的方式来声明一个实现()
运算符的编译器生成的类的实例。
更准确地再现原始示例,我们需要一个名为multiplier
的类似函数的东西,它接受factor
,并返回另一个类似于函数的东西,它接受一个值v
和返回v * factor
。
auto multiplier = [] (int factor)
{
return [=] (int v) { return v * factor; };
};
auto doubler = multiplier(2);
std::cout << doubler(3) << std::endl; // prints 6
注意模式:最终我们将两个数相乘,但我们分两步指定数字。我们从调用multiplier
返回的仿函数就像一个包含第一个数字的“包”。
尽管lambdas相对较新,但它们很可能成为C ++风格中非常常见的一部分(因为它们已经被添加到其他语言中)。
但遗憾的是,在这一点上我们达到了“前沿”,因为上面的例子在GCC中工作但在MSVC 12中没有(我没有在MSVC 13中尝试过)。它确实通过了MSVC 12的intellisense检查(它们使用两个完全不同的编译器)!你可以通过用std::function<int(int)>( ... )
包裹内部lambda来修复它。
即便如此,在手工编写仿函数时,您可以在老式C ++中使用这些想法。
展望未来,可恢复功能可能会使其成为该语言的未来版本(微软正在努力推动它们,因为它们几乎与C#中的异步/等待相同),这是另一个模糊了函数和类之间的区别(可恢复函数就像状态机类的构造函数一样)。