第18 章探讨 C++新标准.包装器,包装器 function 及模板的低效性,修复问题,其他方式
第18 章探讨 C++新标准.包装器,包装器 function 及模板的低效性,修复问题,其他方式
文章目录
18.5包装器
C++提供了多个包装器(wrapper,也叫适配器[adapter])。这些对象用于给其他编程接口提供更一致或更合适的接口。例如,第16章讨论了 bindlst 和 bind2ed,它们让接受两个参数的函数能够与这样的 STI算法匹配,即它要求将接受一个参数的函数作为参数。C++11提供了其他的包装器,包括模板 bind、men f和 reference_wrapper 以及包装器 function。其中模板 bind 可替代 bindlst和 bind2nd,但更灵活;模板 mem f让您能够将成员函数作为常规函数进行传递;模板reference_wrapper 让您能够创建行为像引用但可被复制的对象;而包装器fnction 让您能够以统一的方式处理多种类似于函数的形式。
下面更详细地介绍包装器function 及其解决的问题。
18.5.1包装器 function 及模板的低效性
请看下面的代码行:
answer =ef(g);
ef是什么呢?它可以是函数名、函数指针、函数对象或有名称的lambda表达式。所有这些都是可调用的类型(callabletype)。鉴于可调用的类型如此丰富,这可能导致模板的效率极低。为明白这一点,来看一个简单的案例。
首先,在头文件中定义一些模板,如程序清单18.6所示
程序清单18.6 somedefs.h
// somedefs.h
#include <iostream>
template <typename T, typename F>
T use_f(T v, F f)
{
static int count = 0;
count++;
std::cout << "use_f count = " << count
<< ", &count = " << &count << endl;
return f(v);
}
class Fp
{
private:
double z_;
public:
Fp(double z = 1.0) : z_(z) {}
double operator()(double p) { return z_*p; }
};
class Fq
{
private:
double z_;
public:
Fq(double z = 1.0) : z_(z) {}
double operator()(double q) { return z_+ q; }
};
程序清单18.7 callable.cpp
// callable.cpp -- callable types and templates
#include <iostream>
#include <math.h>
using namespace std;
template <typename T, typename F>
T use_f(T v, F f)
{
static int count = 0;
count++;
cout << " use_f count = " << count << ", &count = " << &count << endl;
return f(v);
}
class Fp
{
private:
double z_;
public:
Fp(double z = 1.0) : z_(z) {}
double operator()(double p) { return z_*p; }
};
class Fq
{
private:
double z_;
public:
Fq(double z = 1.0) : z_(z) {}
double operator()(double q) { return z_+ q; }
};
double dub(double x) {return 2.0*x;}
int main()
{
double y = 1.21;
cout << "Function pointer dub:\n";
cout << " " << use_f(y, dub) << endl;
cout << "Function pointer sqrt:\n";
cout << " " << use_f(y, sqrt) << endl;
cout << "Function object Fp:\n";
cout << " " << use_f(y, Fp(5.0)) << endl;
cout << "Function object Fq:\n";
cout << " " << use_f(y, Fq(5.0)) << endl;
cout << "Lambda expression 1:\n";
cout << " " << use_f(y, [](double u) {return u*u;}) << endl;
cout << "Lambda expresson 2:\n";
cout << " " << use_f(y, [](double u) {return u+u/2.0;}) << endl;
cin.get();
return 0;
}
在每次调用中,模板参数T都被设置为类型 double。模板参数F呢?每次调用时,F都接受一个 double值并返回一个 double值,因此在6次use of)调用中,好像F的类型都相同,因此只会实例化模板一次。但正如下面的输出表明的,这种想法太天真了:
模板函数usef)有一个静态成员count,可根据它的地址确定模板实例化了多少次。有5个不同的地这表明模板uset)有5个不同的实例化。址,
为了解其中的原因,请考虑编译器如何判断模板参数F的类型。首先,来看下面的调用:use f(y,dub);
其中的 dub 是一个函数的名称,该函数接受一个 double参数并返回一个 double值。函数名是指针,因此参数F的类型为 double(*)(double):一个指向这样的函数的指针,即它接受一个 double 参数并返回一个double 值。
下一个调用如下:
use f(y,square);
第二个参数的类型也是 double(*)(double),因此该调用使用的use1()实例化与第一个调用相同。在接下来的两个 use()调用中,第二个参数为对象,F的类型分别为Fp和Fq,因为将为这些F值实例化 usef()模板两次。最后,最后两个调用将F的类型设置为编译器为lambda 表达式使用的类型。
18.5.2 修复问题
包装器 fiunction 让您能够重写上述程序,使其只使用use()的一个实例而不是5个。注意到程序清单18.7中的函数指针、函数对象和 lambda 表达式有一个相同的地方,它们都接受一个 double 参数并返回一个double 值。可以说它们的调用特征标(call signature)相同。调用特征标是有返回类型以及用括号括起并用头号分隔的参数类型列表定义的,因此,这六个实例的调用特征标都是 double(double)。模板 fiunction 是在头文件 fiunctional 中声明的,它从调用特征标的角度定义了一个对象,可用于包装调用特征标相同的函数指针、函数对象或lambda表达式。例如,下面的声明创建一个名为fdci的 finction 对象,它接受一个 char 参数和一个 int 参数,并返回一个 double 值:
std::function<double(char,int)>fdci;
然后,可以将接受一个 char 参数和一个int 参数,并返回一个 double值的任何函数指针、函数对象或
lambda 表达式赋给它。在程序清单18.7中,所有可调用参数的调用特征标都相同:double(double)。要修复程序清单18.7以减少实例化次数,可使用 fiunction<double(double)>创建六个包装器,用于表示6个函数、函数符和 lambda。这样,在对usef)的全部6次调用中,让F的类型都相同(fiunction<double(double)>),因此只实例化一次。据此修改后的程序如程序清单18.8所示。
程序清单18.8 wrapped.cpp
// wrapped0.cpp -- using a function wrapper
#include "somedefs.h"
#include <iostream>
#include <math.h>
#include <functional>
double dub(double x) {return 2.0*x;}
int main()
{
using std::cout;
using std::endl;
using std::function;
typedef function<double(double)> fdd;
double y = 1.21;
function<double(double)> ef1 = dub;
function<double(double)> ef2 = std::sqrt;
function<double(double)> ef3 = Fq(10.0);
function<double(double)> ef4 = Fp(10.0);
function<double(double)> ef5 = [](double u) {return u*u;};
function<double(double)> ef6 = [](double u) {return u+u/2.0;};
cout << use_f(y, function<double(double)>(dub)) << endl;
cout << use_f(y, fdd(sqrt)) << endl;
cout << use_f(y, ef3) << endl;
cout << use_f(y, ef4) << endl;
cout << use_f(y, ef5) << endl;
cout << use_f(y, ef6) << endl;
std::cin.get();
return 0;
}
从上述输出可知,count的地址都相同,而count的值表明,use()被调用了6次。这表明只有一个实例,并调用了该实例6次,这缩小了可执行代码的规模。
18.5.3 其他方式
下面介绍使用 fnction 可完成的其他两项任务。首先,在程序清单18.8中,不用声明6个fmction<double(double)>对象,而只使用一个临时 function<double(double)>对象,将其用作函数usef()的参数:
typedef function<double(double)>fdd; // simplify the type declaration
cout << use f(y,fdd(dub))<< endl;//create and initialize object to dub
cout <<use f(y,fdd(square))<< endl;
其次,程序清单18.8让use()的第二个实参与形参f匹配,但另一种方法是让形参f的类型与原始实参匹配。为此,可在模板usef()的定义中,将第二个参数声明为 fiunction 包装器对象,如下所示:
#include <functional>
template <typename T>
T use_f(T v, std::function<T(T)>f) //f call signature is T(T)T
{
static intcount =0;
Count++;
std::cout <<use fcount=" count<< &count ="< &count << std::endl;
return f(v);
}
这样函数调用将如下:
Cout << ""<<use f<double>(y,dub)<< endl;
...
cout<<""<<use f<double>(y,Fp(5.0))<< endl;
...
cout <<""<<use f<double>(y,[](double u)(return u*u;})<< endl;
参数 dub、Fp(5.0)等本身的类型并不是 function<double(double)>,因此在 usef后面使用了来指出所需的具体化。这样,T被设置为 double,而 std:.function<T(T)>变成了 std::function<double(double)>。