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

天地不仁,以万物为Googol!

天行有常,不以物喜,不以己悲……

 
 
 

日志

 
 

最近的研究  

2005-12-10 18:31:23|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
其实没研究啥,就是最近waiss同学告诉我用模板可以在c++中写出与oo属性行为类似的一个类,且小日本的游戏代码中已经有了类似的玩意儿。耐不住性子,也就顺手研究了一下,还顺便研究了一下事件类的写法,结果……唉,都失败了。
先说属性类吧。这东西说穿了比较简单,就是一个实例,用法像变量,行为像函数。比如,一个对话框类具有标题属性,可以这样表示:
Dlg::title
使用上(括号里是等同的mfc的语法):
Dlg aDlg;
aDlg.title = "Something";( aDlg.SetWindowText("Something"); )
print(aDlg.title = "Something";( aDlg.GetWindowText(aCString); print(aCString); )
实际上,使用c++的运算符重载,可以很轻易的写出:
template<class T>
class Property
{
public:
    T operator=(T arg)
    {
        //do set function
    }
    operator T()
    {
         //do get function
    }
}
我如何将所要用到的函数传进去呢?可以利用函数指针,但我试验后是继续利用模板,而且不会损失效率:
template<class T, typename Func>
class Property
{
public:
    T operator=(T arg)
    {
        Func::set(arg);
        return arg;
    }
    operator T()
    {
         Func::get(arg);
    }
}
这样,如果我写:
class Func
{
public:
    static set(string arg) { SetWindowText(arg); }
    static string get() { GetWindowText(aCString); return string(aCString); }
}
貌似就可以这样调用了:
Property<string, Func> title;
title = "Something";
print(title);
但是,很可惜,不行。姑且不论print(title)时,vc的编译器无法将title自动转型operator string(),(不过基本类型好像可以,像int, float),最重要的是,SetWindowText和GetWindowText都只是CWnd的成员函数,我必须在调用时指明类的实例。或许我可以这样写:
class SomeDlg : public CDlg
{
    class Func
    {
    public:
        static set(string arg) { SetWindowText(arg); }
        static string get() { GetWindowText(aCString); return string(aCString); }
    }
    Property<string, Func> title;
}
很可惜,vc6和2003下都无法编译通过。2003的提示是:Func只能通过SomgDlg的实例使用,因此无法在编译期连接到模板。vc6的提示没记住,反正比这个还可怕……
或许我可以通过继承:
class Title : public Property<string>//将set/get写为Property的纯虚函数,并在Title实现
...
不过,实际检验,2003和vc6下,子类Title都无法继承Property重载的两个运算符operator=和operator T(也就是operator string)。实际中,下面的语句:
aTitle = "string";
提示编译错误:不能找到形如operator=(Title &)的重载运算符。因此,这种方法实际上没有任何意义:(
至此,属性类我已经想不出更好的写法了,研究到此失败……
再来说说事件类,这个研究要好得多,不过,也可以说,要糟糕的多……
很快,我就实现了如下的事件类:
template<class T>
class EventHandler
{
public:
    virtual void run(T arg) = 0;
}
class Event
{
public:
    void operator +=(EventHandler *arg)
    {
        _handlers.push_back(arg);
    }
    void operator()(T arg)
    {
         vector<EventHandler<T>*>::iterator i = _handlers.begin(), n = _handlers.end();
         for(;i != n; i++)
            i->run(arg);
    }
    ~Event()
    {
        while(_handlers.size() != 0)
        {
            delete _handlers.back();
            _handlers.pop_back();
        }
    }
private:
    vector<EventHandler<T>*> _handlers;
}
由此可以这样用。这里,我注意到了先前的失败经验,构造时传入了类的指针(可是属性类却无法这样做,因为属性类只有一个类,而事件类包含两个:事件发起类和事件响应类):
class SomeDlg
{
public:
    ...
    Event mouseClickEvent;
    ....
    SomeDlg()
    {
        class MouseClickHandler : public EventHandler<CPoint>
        {
        public:
            MouseClickHandler(SomeDlg* dlg) : _dlg(dlg) {}
            virtual void run(CPoint arg)
            {
                //received the event with mouse at CPoint
            }
        private:
            SomeDlg *_dlg;
        }
        mouseClickEvent += new MouseClickHandler(this);
    }
private:
    void ReceivedMouseClick(CPoint arg)
    {
        mouseClickEvent();
    }
}
看起来一切都很完美不是么?
不过,这里隐藏了极为危险使用方法,比如:
void SomeDlg::SetEventHandler()
{
    MouseClickHandler aHandler(this);//MouseClickHandler is the same as before.
    mouseClickEvent += &aHandler;
}
这样,当你点鼠标时,不会收到事件,反而会收到一个异常!因为aHandler是定义在SetEventHandler中的临时变量,其生命期仅在函数内部。出了函数,mouseClickEvent含有的EventHandler指针不再有效,而是一个野指针,当点鼠标时,调用这个Handler,自然会异常!
比这个更可怕的用法是:
void SomeDlg::SetEventHandler()
{
    MouseClickHandler *aHandler = new MouseClickHandler(this);//MouseClickHandler is the same as before.
 
    mouseClickEvent += aHandler;
    ....
    mouseClickEvent += aHandler;
}
编译没有问题,链接没有问题,运行没有问题,但是,退出时有异常!原因是重复释放了aHandler的地址(还记得Event类的析构么?使用一个循环delete掉所有持有的指针)。这个异常在实际中太难定位了,而且,同一个aHandler可能会插入到两个不同的Event中,而这两个插入的位置又相隔很远。
比较狠的做法是,规定Event的+=操作后面只能跟new出来的Handler实例,但这这个无法在语言层面作限制,或者只能+=一个smart_ptr的实例,这样做倒不是没有想过,但因为以前没有用过smart_ptr,而且听说这东西副作用太大,所以没有贸然使用。
另一种做法是使EventHandler只能通过某个函数创建,这个函数每次创造一个实例,并将实例指针返回,但这样的函数只能放在EventHandler的子类中,否则无法知晓要创建的实例是什么类型,而实际运用中,无法保证所有人都遵守这个约定。也不是好地解决方案。
至此,我又没招了。事件类可以运作得很好,但隐藏了巨大的错用机会,这也是我开始说的:对这个的研究要好得多,不过,也可以说,要糟糕的多……
不过,在研究EventHandler的实例化过程中,想到了这么一点:如果将一个类的构造函数声明为私有,却将重载的operator new()公开,是不是能达到“创造一个不可继承的类”的目的呢?而且这个“不可继承的类”,除了不能在栈中建立外,使用上与其他类的使用完全一样?
留待后续研究……
  评论这张
 
阅读(149)| 评论(0)
推荐 转载

历史上的今天

评论

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

页脚

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