注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

一路

To find the final symmetry & beauty

 
 
 

日志

 
 
 
 

一个简单的delegate的C++实现[原创]  

2010-10-17 22:59:49|  分类: c++学习笔记 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Delegate在很多的编程语言中都有广泛的应用,但是很可惜C++语言本身并没有提供这个功能,这可以认为是C++这门语言的一种遗憾,delegate在有些时候也叫closure,其功能之所以重要是因为其可以实现对象之前的弱耦合,这对一些framework,尤其是GUIframework的设计尤为重要,一些设计模式,command,subject/observer,action/transaction 的设计中对这种delegate功能有很强的依赖性。更可喜的是如果在语言中由编译器实现delegate是十分容易且是十分高效的,但是很遗憾C++没能把这个功能加入进来,要想使用这个功能可以使用一个库实现,这些库还是比较多的,比如ligsgic++,这些实现普遍的一个问题是兼容性不好,运行效率差。希望有一天这个重要功能能加入到C++语言的核心中来。想想Qt中的Signal Slot的功能强大和易用性就令人兴奋。

本文不想设计一个功能强大而又高效的delegate库,因为本人不是大牛,而且这个功能真的应该是属于编译器的份内工作,理应由编译器来完成。本文只是想通过实现一个简单的版本,不见行有很强的通用性甚至兼容性,也没见得有多高的效率,如果你通过本文知道了什么是delegate或者已经知道delegate的人知道其实现过程中的一些问题,那么本文就没有白写。

1.       什么是delegate(代理)

Delegate在不同的编程语言中理解不同,概念比较混乱,其中核心就是一种传递消息的机制,delegate就是尽可能在我们不知传送方是谁和接收方是谁的情况下来传递信息,你可以想一想在程序中做到这个功能是否容易和有什么用处。我们的做法就是把这一消息传递给一个中间的代理人(delegate),之后让这个代理人把信息传出去。至于这个代理人是否真的把消息传递出去或者甚至传递错误的信息都不关发原发送信息者的事了。做为程序员要做的只是设置好代理的(设置好接收者),之后把这个代理给发送者,或是先把一个代理给发送者,之后再动态绑定接收者或者取消一些接收者,信息传递就交给代理去办吧。那么这个代理功能有什么实际用处呢?我们举一个例子来看一下,比如GUI库,一个类似于word的字处理程序,我们可以设置菜单项,比如复制项,如果我们把这个菜单项写死,每次复制都是触发一个固定的函数,但是一种字处理程序是可能处理不同种的文档格式,不同格式的文档的操作可能千差万别,当然复制操作也是千差万别的,它们可以继承自同一个抽象的文档类,或者是一些文档类,而且在程序升级时就可能支持新的文档格式,我们如果把GUI的复制项写死,那么以后升级或是在不同的上下文中处理将是十分的不便,代码也十分不易维护。再有一点就是一个GUI framework的原菜单项并不知道你的程序中要用到什么功能,和你自己的应用关系十分紧密,这时使用delegate将十分有用,我们设计自己的文档类,之后用一个delegate把复制功能绑定到这个代理中,甚至可以把N个文档的复制都绑定到一个代理上,只要点击一下菜单项,触发代理的相关功能,之后代理把消息dispatch到各个文档,GUI framework甚至不知有什么文档存在的情况下就把功作做好了。

2.       C++中如何实现代理

C++本身没有代理这个概念,但是其有一个类成员函数指针,这个功能将有助于实现代理,这个成员函数指针在实际应用中很少有人用到它,因为其语法格式古怪难用,而且在一般应用中也不见得有什么实际用处,更糟糕的是C++标准中对于成员函数指针的文档规定不够详细,也没有严格的要求(可以是他们认为这个功能不太重要吧),这使得这个功能的实现在不同的编译器中十分不同,使用这个功能的代码很难兼容并且在不同编译器上成员函数指针的大小也不相同,甚至在同一编译器下的不同时候这成员函数指针大小也不相同,如果在加上类的继承关系,多重继承,虚拟继承,RTTI功能,类型转换,这个成员函数指针的功能情况更加糟糕,一般表现为函数调用失败而出现运行时错误,有时还表现为函数调用错误而程依旧运行,但是结果却不正确,在多线程序中还表现为有时函数调用正确,有时调用错误,这类编译器的BUG十分难以追踪,如果你还没遇到这类错误,那么你应该买两瓶啤酒庆祝一番,同是祈祷自己一辈子也不要遇到这种问题,说这是C++语言中最混乱的地带一点也不为过。

3.       实现细节

要实现这样一个代理得先复习一下成员函数指针是怎么用的,一般C++基础的书中都有一些介绍,如下面的代码:

//member pointer test

//written by saturnman

#include<iostream>

using namespace std;

class Test

{

    public:

    void foo()

    {

        cout<<"Foo()"<<endl;

    }

};

int main()

{

    typedef void (Test::*mem_pointer)();  //define type

    mem_pointer mp = &Test::foo;           //define member pointer

    Test t;                                  //define object

    Test* pt = &t;                          //define pointer to object

    (t.*mp)();                             //invode through reference

    (pt->*mp)();                           //invoke through pointer

    return 0;

}

我已经加了注释,语法什么的都是不重要的,其中重要的一点就是不能直接通过函数指针mp直接调用成员函数,原因在于成员函数指针是以类和其成员函数来定义的,不是通过对象来定义的。要调用成员函数,成员函数可能要操作类对象的成员变量,因此要想通过一个成员函数指针来调用成员函数,必须通过对象,因为成员函数不是有一个稳性的this指针,因为C++标准没有规定这个this指针在调用时一定要放在什么地主,也就是没有内存模型上的规定。因些要调用一定要通过对象或是对象指针,OK成员函数就介绍到这里。

         要实现delegate我们要做的就是把类的指针和成员函数指针都是保存起来,但是类的指针和类的成员函数指针都是有类型的,类指针的类型可以理解,而成员函数为什么有指针就十分不可理解了,我没搞清楚为什么成员函数指针要有自己的类型>_<!,我们要把这些东西存储起来的话C++的强类型检查会极力阻止你这么做。因此我们要使用强制类型转换,为了实现一个类成员函数的转换,我使用了一个类似的技巧,用一个类的来包装一个成员函数指针并对其进行转换,其中使用一个些特别的技巧,嗯。但是我们的强制类型转换已经把类型丢了,在运行期我们要把类型转换回来,因此我们要做一些标记,我们的目地是把所有的类都能放入这个代理的接收者队列中,因此我们不能去改造接收者,否则应用范围将十分有限,因为一些类是库中是不能重新编译代码的,因些我们的办法就是把类注册到一个结构中,之后为每个要用代理的类分配一个tag,也把tag取储起来,之后在运行时转换回来。嗯,代码我发在下面:

//file command.h

//written by saturnman

#ifndef COMMAND_H_

#define COMMAND_H_

#include<iostream>

#include<list>

#include<utility>

#include<iomanip>

#include "class.h"

using namespace std;

template<class T>

class Mem_pointer

{

    public:

    typedef void (T::*t_mem_pointer)();

    t_mem_pointer m_mem_pointer;

    Mem_pointer(){}

    Mem_pointer(void(T::*mem_pointer)())

    {

        m_mem_pointer = mem_pointer;

    }

    template<class U>

    Mem_pointer<T>& operator=(const Mem_pointer<U>& rhs)

    {

        new (this) Mem_pointer<U>(rhs);

        return *this;

    }

    template<class U>

    Mem_pointer(const Mem_pointer<U>& rhs)

    {

        new (this) Mem_pointer<U>(rhs);

    }

};

class Command

{

    public:

        enum CLASS_TAG{Command_tag,A_tag,B_tag};

        void pass(){};

        class tag_this_pointer

        {

            public:

                template<class U>

                tag_this_pointer(CLASS_TAG tag,void* pointer,const Mem_pointer<U>& mem_pointer)

                {

                    this->class_tag = tag;

                    this->this_pointer = pointer;

                    this->mem_pointer = mem_pointer;

                }

                CLASS_TAG class_tag ;

                void* this_pointer;

                Mem_pointer<Command> mem_pointer;

        };

        void Execute()

        {

            for(list<tag_this_pointer>::iterator itor = m_list.begin();itor!=m_list.end();++itor)

            {

                if(itor->class_tag==A_tag)

                {

                    ((A*)(itor->this_pointer)->*((void (A::*)())((itor->mem_pointer).m_mem_pointer)))();

                }

                else if(itor->class_tag==B_tag)

                {

                    ((B*)(itor->this_pointer)->*((void (B::*)())((itor->mem_pointer).m_mem_pointer)))();

                }

            }

        }

        list<tag_this_pointer> m_list;

        template<class T>

        void Append(CLASS_TAG tag,T* obj_pointer,void (T::*mem_pointer)())

        {

            m_list.push_back(tag_this_pointer(tag,(void*)(obj_pointer),Mem_pointer<T>(mem_pointer)));

        }

};

#endif /*COMMAND_H_*/

下面是测试代码.

//file class.h

//written by saturnman

#ifndef CLASS_H_

#define CLASS_H_

#include<iostream>

#include<string>

using namespace std;

class A

{

    public:

        A(const string& str)

        {

            this->str = str;

        }

        void func()

        {

            cout<<"A::Hello"<<endl;

            cout<<"internal string:"<<str<<endl;

        }

    private:

        string str;

};

class B

{

    public:

        B(const string& str)

        {

            this->str = str;

        }

        void func()

        {

            cout<<"B::Hello"<<endl;

            cout<<"internal string:"<<str<<endl;

        }

    private:

        string str;

};

#endif

 

//file test1.cpp

//written by saturnman

#include "command.h"

#include "class.h"

#include<iostream>

using namespace std;

int main()

{

    A a("classA");

    B b("classB");

    Command com;

    com.Append(Command::A_tag,&a,&A::func);

    com.Append(Command::B_tag,&b,&B::func);

    com.Append(Command::A_tag,&a,&A::func);

    com.Append(Command::B_tag,&b,&B::func);

    com.Execute();

    return 0;

}

 

代码是写了,可是问题还是有很多,为了把每个类加入接收者队列,我们要在enum中加为每个类加入一个新的tag,还要在CommandExecuteif else 队列中加入一个新的项,由于用到库中的一些高级的结构,效率不高,如果编译器能从根本上解决这个问题,效率将有极大的提高,就像switch cast 的跳转表一样。但这不是我们的问题,只要写一个简单的工具就可以通过预处理一遍代码来解决问题。但是这个不是主要问题,问题目是C++为什么没的提供这样的功能,这里我就要发几句牢骚了,C++是一种强静态类型的语言,而且还要实现一些其它动态语言的功能,因此语言本身要有一些十分必要的分析能力,而可惜的是C++没有提供这些功能。在C++对于对象的操作是十分多的,但是数一数对类定义本身的操作,屈指可数啊。比如要你实现一个判断任意类定义是否有虚函数,是否有纯虚函数,是否则POD(注意我说的实现是静态,如果你不明白也没有办法),这是十分困难的。而这些在静态判断在代码中是十分必要的,C++的元编程能力还是有待提高的。我们为实现delegate所手工做的注册都是静态部分,可以在编码时自动完成的,C++标准里不单单是缺少delegate这一个功能,而是缺少一类功能,如果哪天又发明了新的东西,C++又缺少了。C++是现代静态语言的巅峰,应该提供足够的功能让使用者在不改变编译器的情况下改造语言本身的东西,嗯J

  评论这张
 
阅读(2148)| 评论(2)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017