STL学习之函数对象

0.由来

Effective STL第46条:考虑使用函数对象而不是函数作为STL算法的参数
书里说到:

你或许会对本条款将要讨论的话题感到惊讶:将函数对象(即可以被伪装成函数的对象)传递给STL算法往往比传递实际的函数更高效。

书中提到了性能比较的数值:

我在4个不同的STL平台上,针对一个包含100w个double数据的vector对象测试了两种调用,并都设置了速度优化,
……最差的情况下,使用函数对象的版本快了50%,最好的情况下快了160%

书中进行了解释:

1.函数对象时,编译器将该函数内联进去了
2.使用函数时,编译器会隐式的将它转化成一个指向该函数的指针,并将该指针传递过去,而大多数编译器不会对函数指针执行的函数进行内联优化

1.理解

看书100本不如写代码1行。这里记录下整个理解过程。
在实际操作中,经常是用函数直接传给STL的,像下面这样:

1.1函数作为参数的简单代码

#define NUM 10000000

typedef std::vector<double> VecData;
typedef VecData::iterator   VecDataIT;
//排序函数
inline bool doubleGreater(double d1, double d2)
{
    return d1 > d2;
}

VecData data(NUM),dataplus(NUM);
std::generate(data.begin(), data.end(), rand);
//用函数做STL的参数
std::sort(data.begin(), data.end(), doubleGreater);

1.2使用函数对象做参数

//引用库
#include <functional>

调用的时候

std::sort(data.begin(), data.end(), std::greater<double>());

1.3性能对比

采用高精度计时器类写的1ms精度定时器,
判断transform前后所需时间。
测试平台如下

Win10 64bit
VS2015 默认优化
基于对话框 Release模式
耗时取10次平均值

方法 耗时(ms)
函数对象 900
函数 1600

结论
和书中预言的一样,使用函数对象,也就是特殊的类,反而更快。(在csdn上有人说传递函数实际上在汇编里只有2个命令,而函数对象构造一个类至少要50个命令,
但结果就是传递类进去更快)

这里函数对象时调用的库函数,库函数一般都是大神优化很多的结果,如果是自己写的函数对象呢,还能一样对函数碾压吗?
下面继续试验,试验自定义函数和函数对象,同样做1000w浮点排序

1.4稍复杂的排序

//自定义类
typedef struct _tData
{
    double data1;
    double data2;
    _tData()
    {
        data1 = rand();
        data2 = rand();
    }
    inline _tData& operator= (_tData& d)
    {
        data1 = d.data1;
        data2 = d.data2;
        return(*this);
    }
    inline bool operator<(_tData& d)
    {
        return(data1 < d.data1);
    }

}Data;
typedef std::vector<Data>   VecData;

//比较函数
inline bool doubleGreater(Data& d1, Data& d2)
{
    return d1.data1 > d2.data1;
}

排序时

std::sort(data.begin(), data.end());//doubleGreater

测试结果

方法 耗时(ms)
函数对象 930
函数 1670

结论
还是一样,使用函数对象,反而更快。

2.transform的应用

将一个double向量里的数据经过一个复杂的运算赋值到另外一个double向量里去,这里对比3种方法效率

2.0数据定义

    VecDouble data(NUM),datacalc(NUM);
    srand((unsigned)time(NULL));
    std::generate(data.begin(), data.end(), rand);

2.1直接for循环法

    for (UINT i = 0; i < data.size(); i++)
    { datacalc.push_back(sqrt(data[i] * 3.552 / 2.1 + 66.3)); }

2.2STL模板+内联函数

inline double calc(double data)
{
    return(sqrt(data * 3.552 / 2.1 + 66.3));
}

调用:

std::transform(data.begin(), data.end(), datacalc.begin(), Calc());

2.3STL模板+函数对象

typedef struct _Calc
{
    double operator()(const double data)
    {
        return(sqrt(data * 3.552 / 2.1 + 66.3));
    }
}Calc;

调用:

std::transform(data.begin(), data.end(), datacalc.begin(), Calc());

2.4性能对比

测试结果

方法 耗时(ms)
for循环 270
函数对象 58
内联函数 59

结论
STL太牛逼了,甩一直用的for循环几条街都不止,以后能用STL的都要用STL。
Effective STL第43条

算法调用优先于手写的循环

作者给出了3个理由

1.效率
2.正确性
3.可维护性

3.带参数的函数对象

上面提到的都是简单的不带参数函数对象,在STL的算法里很多是可以带参数的函数对象。
Effective STL第40条

若一个类是函数子,则应使它可配接

3.1方法

书中写到:

在STL中,4个标准的函数配接器(not1,not2,bind1st,bind2nd)都要求特殊的类型定义,而非标的,boost提供的模板很多也包含了这样的组件。提供这些类型定义最简便的方法便是让函数子从特定的基类继承。
如果函数子类的operator()只有一个实参,那么它应该从std::unary_function继承
如果函数子类的operator()有两个实参,那么它应该从std::binary_function继承

具体为:

对于std::unary_function,必须指定函数子类operator()所带的参数的类型,以及返回类型。
对于std::binary_function,必须指定三个类型:operator()的第一个和第二个参数的类型函数子类所带的参数的类型,以及返回类型。

3.2代码

将上面的一个结构改为下面

typedef struct _Calc : public std::unary_function<double,double>
{
    double operator()(const double data)
    {
        return(sqrt(data * 3.552 / 2.1 + 66.3));
    }
}Calc;

注意: std::unary_function<double,double>里第一个doubleoperator()的输入参数类型,第二个doubleoperator()的返回类型。

3.3指针or引用

书中原话:

一般情况下,传递给std::unary_functionstd::binary_function的非指针类型要去掉const和引用&
而如果operator()带有指针,则传递给std::unary_functionstd::binary_function的类型也要为指针。

你可能感兴趣的:(STL,effective,函数对象)