在不产生动态查找成本的情况下,为代码提供通用接口的好方法是什么?

时间:2010-02-07 23:23:46

标签: c++ polymorphism function-pointers

我正在编写一些处理数据的代码。用户可以选择许多处理函数组,然后将这些处理函数应用于数据集。我想在不同的地方实现所有这些组,但由于它们都采用相同的参数并且都做类似的事情,我希望它们有一个共同的接口。

作为一个优秀的小型c ++程序员,我首先想到的是简单地使用多态。只需使用所需的接口创建一些抽象类,然后从中派生每组处理对象。当我想到另一个皱纹时,我的希望很快就破灭了。这些数据集是巨大的,导致所讨论的函数被称为数十亿次。虽然动态查找相当便宜,但据我所知,它比标准函数调用慢得多。

我目前解决这个问题的想法是使用函数指针,其方式如下:

void dataProcessFunc1(mpz_class &input){...}
void dataProcessFunc2(mpz_class &input){...}
...
class DataProcessInterface
{
    ...
    void (*func1)(mpz_class);
    void (*func2)(mpz_class);
    ...
}

使用某种构造函数或某些东西来设置指向正确事物的指针。

所以我想我的问题是:这是一个好方法吗?还有另外一种方法吗?或者我应该学会停止担心并喜欢动态查找?

4 个答案:

答案 0 :(得分:7)

虚函数调用通过指针进行的函数调用。开销通常与通过指针的显式函数调用大致相同。换句话说,你的想法可能会获得很少(很可能根本没有)。

我的立即反应是从虚拟功能开始,只有在探查器显示虚拟呼叫的开销变得显着时才会担心其他问题。

当/如果发生这种情况,另一种可能性是在类模板中定义接口,然后将该接口的各种实现放入模板的特化中。这通常会消除所有运行时开销(尽管通常需要额外的工作量)。

答案 1 :(得分:3)

我不同意上面的一个答案,即基于模板的解决方案可能会产生最差的开销或运行时间。事实上,基于模板的解决方案允许编写更快的代码,无需虚拟函数或逐个调用(但我同意,使用这些机制仍然不会产生很大的开销。)

假设您使用一系列“特征”配置处理接口,即处理可由客户端配置以调整处理接口的部件或功能。想象一个有三个(看一个例子)处理参数化的类:

template <typename Proc1, Proc2 = do_nothing, Proc3 = do_nothing>
struct ProcessingInterface
{
    static void process(mpz_class& element) {
        Proc1::process(element);
        Proc2::process(element);
        Proc3::process(element);
    }
};

如果客户端具有不同的“处理器”,其静态功能“进程”知道如何处理元素,则可以编写这样的类来“组合”这三个处理。请注意,默认的do_nothing类具有空的处理方法:

class do_nothing
{
public:
    static void process(mpz_class&) {}
};

这些电话没有开销。它们是普通呼叫,客户端可以使用ProcessingInterface<Facet1, Facet2>::process(data);配置处理。

这仅适用于您在编译时知道不同的“构面”或“处理器”,这似乎是您的第一个示例。

另请注意,您可以使用元编程工具(例如boost.mpl库)编写更复杂的类,以包含更多类,迭代它们等等。

答案 2 :(得分:2)

从编码的角度来看,抽象接口方法当然是最干净的,并且更倾向于使用函数指针来混淆代码,这实际上是用C ++编写C语言。

您是否确实确定接口方法存在性能问题?

最好首先编写可读可维护代码,并且只有在需要时才进行优化。

答案 3 :(得分:1)

  

这些数据集非常庞大,导致所涉及的函数被称为数十亿次。虽然动态查找相当便宜,但据我所知,它比标准函数调用慢得多。

数十亿次多少时间?如果你的应用程序运行一个小时,十亿次函数调用就没什么了,也不会影响性能。但如果整个数据集在100ms内处理,则十亿次函数调用是一个重要的开销来源。简单地说一下函数的调用次数是没有意义的。在性能方面重要的是如何经常被调用。每个时间单位的呼叫次数。

如果这实际上是一个性能问题,我会采用模板方法。用户不会在每次调用之间决定应用哪些操作。他将做出一次决定,然后解决所有数十亿的电话。

只需为用户可以选择的每组功能定义一个类,确保它们公开相同的接口(可能使用CRTP简化流程并轻松分解公共代码),然后根据用户的选择策略,将适当的类传递给负责进行所有处理的(模板化)函数。

但正如其他答案所说,这可能根本不是性能瓶颈。不要浪费你的时间来尝试优化不重要的代码。