函数对象的妙用-避免函数重定义

2013年04月24日

大名鼎鼎的Numerical Recipes出的C++版程序不知方便了多少苦逼的研究僧,但是好歹不是专业程序员开发的,很多地方还存在语法规范上的问题。

类私有变量的初始化顺序有问题也就算了,我可以给改过来,但是函数或类的定义和申明没有分开就是个大问题了(所有代码都在头文件中),使得我的代码中老是出现重定义错误。初略地分析了一下发现,对于struct,申明和定义放在一起是没有问题的,但是函数就头疼了。

遇到这样的问题,如果把函数定义和申明分开,那么这个代码库就不能单独存在,于是乎最后我的各种源代码里散落着NR的代码,碰到NR语法上的一些瑕疵改起来就很不方便。今晚突然灵机一动,既然struct不会出现重定义错误,那么何不把函数也包装成struct呢?

C++真是个好东西,括弧()操作符重载使得对一个对象可以像函数一样使用。下面是我的基本解决思路:

// func.h中有一个函数叫A
bool A(int a){
    return a<2;
}

// 换成下面这个样子
struct A_func{
    bool operator()(int a){
       return a<2;
    }
}
#define A A_func() //这是一种解决方式,但是这样做有个弊端,那就是不能再有叫A的对象。
static A_func A; //用静态外部变量来做更好,不会出现A重定义的问题

通过这样一个包装的过程,在不同的源文件中include “func.h”均不会引起最后链接时的重定义问题,因为静态外部变量仅在当前文件内有效。更爽的是,实测发现采用函数对象竟然比直接使用函数效率更高,这就是内联的成果?

PS: 好吧,我SB了,函数也可以定义成静态的(在前面加上static),从而将其限制在当前文件,就不会出现重定义错误了。
再次PS: 好吧,原来还有更好的方法,就是把多个函数放到未命名名字空间中去即可避免函数重定义,即形如

namespace{
     void func1(){}
     void func2(){}
     .........
}

下面是采用函数对象时内联优化的效果测试:

/*   g++  -Wall -fopenmp -std=c++0x main.cpp -lrt  */
#include <iostream>
#include <cmath>
#include <time.h>
using namespace std;
#define Doub double
#define Int int

struct gammln_func{
    Doub operator()(const Doub xx) {
        Int j;
        Doub x,tmp,y,ser;
        static const Doub cof[14]={57.1562356658629235,-59.5979603554754912,
            14.1360979747417471,-0.491913816097620199,.339946499848118887e-4,
            .465236289270485756e-4,-.983744753048795646e-4,.158088703224912494e-3,
            -.210264441724104883e-3,.217439618115212643e-3,-.164318106536763890e-3,
            .844182239838527433e-4,-.261908384015814087e-4,.368991826595316234e-5};
        if (xx <= 0) throw("bad arg in gammln");
        y=x=xx;
        tmp = x+5.24218750000000000;
        tmp = (x+0.5)*log(tmp)-tmp;
        ser = 0.999999999999997092;
        for (j=0;j<14;j++) ser += cof[j]/++y;
        return tmp+log(2.5066282746310005*ser/x);
    }
};
static struct gammln_func gammlno;

Doub gammlnf(const Doub xx) {
    Int j;
    Doub x,tmp,y,ser;
    static const Doub cof[14]={57.1562356658629235,-59.5979603554754912,
        14.1360979747417471,-0.491913816097620199,.339946499848118887e-4,
        .465236289270485756e-4,-.983744753048795646e-4,.158088703224912494e-3,
        -.210264441724104883e-3,.217439618115212643e-3,-.164318106536763890e-3,
        .844182239838527433e-4,-.261908384015814087e-4,.368991826595316234e-5};
    if (xx <= 0) throw("bad arg in gammln");
    y=x=xx;
    tmp = x+5.24218750000000000;
    tmp = (x+0.5)*log(tmp)-tmp;
    ser = 0.999999999999997092;
    for (j=0;j<14;j++) ser += cof[j]/++y;
    return tmp+log(2.5066282746310005*ser/x);
}

const int N=100000;
int main(int argc, const char *argv[])
{   
    {
        struct timespec tpstart,tpend;
        clock_gettime(CLOCK_MONOTONIC, &tpstart);
        Doub tmp = 0;
        for(int i=0; i<N; i++) {
            tmp=gammlnf(10.0);
        }
        clock_gettime(CLOCK_MONOTONIC, &tpend);
        long timedif = (tpend.tv_sec-tpstart.tv_sec)*1000*1000+(tpend.tv_nsec-tpstart.tv_nsec)/1000;
        cout<<"func:\t"<<tmp<<"\ttime:"<<timedif<<endl;
    }
    {
        struct timespec tpstart,tpend;
        clock_gettime(CLOCK_MONOTONIC, &tpstart);
        Doub tmp = 0;
        for(int i=0; i<N; i++) {
            tmp=gammlno(10.0);
        }
        clock_gettime(CLOCK_MONOTONIC, &tpend);
        long timedif = (tpend.tv_sec-tpstart.tv_sec)*1000*1000+(tpend.tv_nsec-tpstart.tv_nsec)/1000;
        cout<<"obj:\t"<<tmp<<"\ttime:"<<timedif<<endl;
    }
    return 0;
}