是否可以将算法放入类中?

时间:2013-12-04 10:00:04

标签: c++ algorithm class

我有一个复杂的算法。它使用许多变量,在初始化时计算辅助数组,并沿途计算数组。由于算法很复杂,我将其分解为几个函数。

现在,我实际上并没有从惯用的方式看出这可能是一个类;我的意思是,我只是习惯将算法作为函数。用法只是:

Calculation calc(/* several parameters */);
calc.calculate();
// get the heterogenous results via getters

另一方面,将它放入课堂有以下优点:

  • 我没有必要将所有变量传递给其他函数/方法
  • 在算法开始时初始化的数组可以在每个函数的整个类中访问
  • 我的代码更短,(imo)更清晰

混合方式是将算法类放入源文件中,并通过使用它的函数访问它。算法的用户不会看到该类。

有没有人有宝贵的想法可以帮助我?

非常感谢你!

7 个答案:

答案 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#中的异步/等待相同),这是另一个模糊了函数和类之间的区别(可恢复函数就像状态机类的构造函数一样)。