泛化仿函数用法及参数绑定的问题

news/2024/10/3 2:20:58

《C++设计新思维》一书里的泛化仿函数从Command模式讲起。Command模式主要用来降低系统中命令的调用者和执行者间的依存性。设计模式的书里面一般都采用多态的机制,调用者持有Command对象的基类接口,在此处我们称为Command接口,Command接口不知道自己将被用于执行什么命令,一般只包含一个触发命令执行的虚函数,假设名为Excute。各种不同的实际执行命令的Command对象从则Command接口派生,并重写Excute虚函数。这样调用者通过Command接口来触发命令执行时,因为虚函数机制的关系,实际上调用的都是从Command接口派生的Command对象的Excute函数。在这样的设计中,调用者和各种实际的命令对象互不相见,只持有一个Command接口。在C风格的设计中,一般用回调预先保存的函数指针来实现Command模式。

    泛化仿函数可以说是一种回调,但它不但可以保存函数指针,还可以处理实现了operator()的C++对象,C++对象的成员函数。具体实现方法书里面已描述得非常详细,就不再多啰嗦,在此主要说一下项目中运用泛化仿函数的一些心得。基本如下用法:
Functor<RETURN_TYPE,PARAM_TYPE_LIST> cmd1(...);
Functor有两个泛型参数,第一个为函数返回值,第二个为函数的参数列表。构造函数可接受仿函数、类成员函数以及一般函数指针。下面示例代码演示了其用法: 

#include "loki/functor.h"
#include <iostream>
#include < string>
using  namespace std;
using  namespace Loki;

struct TestFunctor
{
    int operator()(string str)
    {
        cout << str << endl;
        return 0;
    }
};

struct TestFunctor2
{
    int output(int i)
    {
        cout << i << endl;
        return 2;
    }
};

void TestFunction( int i, int j)
{
    cout << i << "," << j << endl;
}

Functor< int,NullType> BindCmd1()
{
    TestFunctor f;

    Functor<int,LOKI_TYPELIST_1(string) > cmd1( f );
    Functor<int,NullType> bcmd1 = BindFirst( cmd1, "another bind cmd1" );

    return bcmd1;
}

int _tmain( int argc, _TCHAR* argv[])
{
    //泛化仿函数基本用法
    
//Functor<RETURN_TYPE, PARAM_TYPELIST> cmd(...);

    TestFunctor f;
    TestFunctor2 f2;

    //调用operator()仿函数----------------------------
    Functor<int,LOKI_TYPELIST_1(string) > cmd1( f );
    cmd1( "1" );
    //end of 调用operator()仿函数----------------------


    
//调用类成员函数----------------------------------
    Functor<int,LOKI_TYPELIST_1(int) > cmd2( &f2,  &TestFunctor2::output );
    cmd2( 2 );
    //end of调用类成员函数------------------------------


    
//调用一般函数指针---------------------------------
    Functor<void,LOKI_TYPELIST_2(int,int) > cmd3( TestFunction );
    cmd3( 3,4 );
    //end of调用一般函数指针---------------------------

    
//预先绑定命令的参数的调用1
    Functor<int,NullType> bcmd1 = BindFirst( cmd1, "bind cmd1" );
    bcmd1();
    //end of 预先绑定命令的参数的调用1

    
//预先绑定命令的参数的调用2
    Functor<int,NullType> bcmd1_1 = BindCmd1();
    bcmd1_1();
    //end of 预先绑定命令的参数的调用2


    return 0;
}

    用法很简单,上面的几个用法都只有两行,第一行定义泛化仿函数,第二行执行仿函数。实际运用中定义和执行一般都各在不同的地方,如Command模式一样,即它们在时间和空间上是分离的。

    如果您使用了LOKI0.1.5的库,在“预先绑定命令的参数的调用2”的用法中会出现运行时错误,这是我在项目过程中碰到的,经过分析LOKI中Functor实现的代码,终于找到了原因。项目中实际的运用当然不是这样,示例代码只说明了在什么情况下运用才会出错。如代码所示,调用绑定了参数的仿函数时,如果已经离开了所绑定参数的作用域则会出错。而其罪魁祸首在于对绑定的参数作了优化。

  《C++设计新思维》P123中讲到为避免函数转发的成本对函数参数作了优化,如果参数为非基本类型(非内置类型,如自定义的struct,class),则将参数类型更改为该参数的引用类型。如示例中的int operator()(string str)被优化后str参数变成string &str。而当一个引用已离开其所引用对象的作用域后,该引用会成为一个dead reference,使用了dead reference,不可避免地结局就是运行时错误。或许作者不许我们这么使用绑定参数,或者作者没想到我们会这么使用绑定,但至少说明了一点,过多地优化未必是件好事。

    既然知道了为什么会出错,那就容易解决问题了。绑定的原理是将参数保存起来,在调用的时候取出预先保存的参数传递给要调用的函数。如果保存的类型是值类型,那不管是否离开原参数的作用域都不会出错。现在我们来找实现参数绑定的类定义。在Functor.h中找到class BinderFirst,该类中有一个 类型定义如下:
typedef typename Private::BinderFirstBoundTypeStorage<
 typename Private::BinderFirstTraits<OriginalFunctor>
 ::OriginalParm1>::RefOrValue
 BoundTypeStorage;
BoundTypeStorage即保存所绑定参数的类型定义,观其定义可以知道该类型也是做了优化,非基本类型都变成了引用。现在我们来做一点小改动,使用参数原来的类型来保存参数,修改后的定义如下: 
typedef typename Private::BinderFirstTraits<OriginalFunctor>
   ::OriginalParm1
   BoundTypeStorage;
修改完毕后,重新编译运行,一切OK了。

    其实上述改动并不能解决所有问题,这种做法是使用参数的原始类型来保存参数,假如参数本身是个引用类型,那在离开了所引用对象的作用域时调用还是会出错的。最彻底的改法是将引用参数去掉引用作为保存参数的类型。我的做法是在使用的时候做文章,不管参数是否引用类型,定义绑定的Functor时都定义成非引用类型,这样再配合上面的改动,参数必然会使用值类型保存。如果明确地知道调用时不会离开原参数的作用域,那就不必如此了。只要心中有个底,具体用法就视个人运用的环境以及个人做法的喜好了。


https://dhexx.cn/news/show-4468413.html

相关文章

How many '1's are there题解

Description: Description: 第一行输入数字n&#xff08;n<50)&#xff0c;表示有n组测试用例&#xff0c;第2到第n1行每行输入数m&#xff08;m为整数&#xff09;&#xff0c;统计并输出m用二进制表示时&#xff0c;1的个数。 例如&#xff1a;m9时&#xff0c;二进制表示…

oracle索引先导列,Oracle复合B*tree索引branch block内是否包含非先导列

Oracle复合B*tree索引branch block内是否包含非先导列键值&#xff1f;&#xff0c;branchblock 好久不碰数据库底层细节的东西&#xff0c;前几天&#xff0c;一个小家伙跑来找我&#xff0c;非要说复合b*tree index branch block中只包含先导列键值信息&#xff0c;并不包含非…

数据库面试题之COUNT(*),COUNT(字段),CONUT(DISTINCT 字段)的区别

COUNT(*).明确的返回数据表中的数据个数,是最准确的 COUNT(列),返回数据表中的数据个数,不统计值为null的字段 COUNT(DISTINCT 字段) 返回数据表中不重复的的数据个数,不统计值为null的字段

JAVA堆外内存

JVM可以使用的内存分外2种&#xff1a;堆内存和堆外内存. 堆内存完全由JVM负责分配和释放&#xff0c;如果程序没有缺陷代码导致内存泄露&#xff0c;那么就不会遇到java.lang.OutOfMemoryError这个错误。 使用堆外内存&#xff0c;就是为了能直接分配和释放内存&#xff0c;提…

.gitignore不生效

在git中如果想忽略掉某个文件&#xff0c;不让这个文件提交到版本库中&#xff0c;可以使用修改根目录中 .gitignore 文件的方法&#xff08;如无&#xff0c;则需自己手工建立此文件&#xff09;。这个文件每一行保存了一个匹配的规则例如&#xff1a; # 此为注释– 将被 Git …

肖磊c语言答案,初级查分时间提前?这批考生被幸运砸中!

21年初级会计考试已进行第5天&#xff0c;考完的考生一定很想对一下答案&#xff0c;更想知道何时能查成绩。对于5月22日-23日考试的考生来说&#xff1a;考试将要考什么&#xff1f;考试难不难&#xff1f;自己往哪个方向复习&#xff1f;都是大家想只知道的&#xff0c;今天奥…

Angularjs select总结

定义列表和选中项 $scope.Current "Writing code"//定义选中项 $scope.TotalItems [//定义列表{ id: 1, type: "Work", name: "Writing code" },{ id: 2, type: "Work", name: "Testing code" },{ id: 3, type: "Work…

android 设置动态曲线图,Android 使用MPAndroidChart:v3.1.0绘制动态折线图

工作需要绘制一张可动态添加的折线图&#xff0c;经过筛选&#xff0c;选择MPAndroidChart:v3.1.0。 **使用方法&#xff1a;** 1、添加build gradle 在项目的build gradle中上述位置中添加“maven { url ‘https://jitpack.io’ }”这串代码。 在APP的build gradle的dependenc…

个人信息建设随笔

也许是老了或者在互联网上时间长了&#xff0c;很多网站&#xff0c;应用的密码时间长了都会忘记了&#xff0c;有人会说&#xff1a;你不会只用一个帐号和密码么&#xff1f; 事情没有想的那么简单&#xff0c;主要两个方面的&#xff0c;第一&#xff1a;你注册的帐号通常情况…

android 锁屏显示音乐播放器,Android锁屏界面控制音乐播放

目前&#xff0c;在锁屏界面控制音乐播放有两种常用方式。 第一种方式&#xff1a;原生Android系统及自带音乐播放器。 锁屏界面端&#xff1a; 原生Android中&#xff0c;锁屏界面相关的UI由KeyguardHostView提供&#xff0c;KeyguardHostView向KeyguardUpdateMonitor注册一个…