随笔-90  评论-947  文章-0  trackbacks-0
 

目录:

2009-2010小结(一)毕业前夕
2009-2010小结(二)初入职场
2009-2010小结(三)加班考验
2009-2010小结(四)抑郁重重
2009-2010小结(五)离职始末

紧接着到了2009年4月份。公司空降了一位主管(称L2吧)。说是空降,其实也不完全是。据史料记载,在跨国公司存在以前,他与现在公司的一些中高层曾经是同事,后来历史推演时,他被留在了老公司。干了几年休息了一年以后,现在他又到这边来了。可是对于我来说,与空降无异。我心里很是紧张,刚刚才熟悉一个主管,又要熟悉另一个陌生的人。

L2开始慢慢接手原来L1兼管着的Windows端的活。一两周的相处之后,发现L2的做事风格和L1完全不同。当时对我来说最不适应的是时间管理。L2总是在下午5点以后来跟我说要做什么事什么事。有时候会问晚上有事情么,我确实没事情的,老实回答了,他会说那我们来看一下某某问题。而整个白天,有时候几乎无所事事。L2自己是个精力相当旺盛的人,也很喜欢加班,几乎天天加班。有时候到了晚上9点左右,他会让我们“早点回去”,而自己可能还呆着。尽管我很佩服他的精力,可是每当在晚上听到“早点回去”的话,一种无形的怒火便油然而生。

我彻底地感到耻辱。我想我一个堂堂正规大学毕业生,虽说不上怎么厉害,但还是自认为可以好好完成任务的。你怎么可以让我加班?还三天两头加?还有事没事的加?对了那时我还有我的毕设呢,根本没法开动。我陷入了沮丧之中。为此我找L1诉过一回苦,说到伤心处,眼里竟然也会渗出一些晶莹的东西。

毕设不紧不慢地进行着。导师让我们每两三周碰头一次。之前交给我的数据库设计等工作我在一开始就呈上去了,经过讨论修改之后差不多定下来了。导师告知大家做的过程中如果有数据库修改要跟我讨论过才可以改,并且让我如果有问题可以召集大家开小会——这让我备受压力。我极力推荐使用 SVN 管理和共享所有资料,被采纳了。于是我平时也经常看看他们做了什么。除了个别比较认真的在不断地尝试、不断地更新、不断地改进外,其他人基本上没怎么动。我也稍就微动动,保持着比最勤快的人不勤快,比其他人都勤快的位置。但是大家平均下来,每人一个页面还不到。逻辑层代码那一块几乎没有别人在动。每次我们碰头的时候导师总是一种失望的眼神看着我们。到了5月底,只剩最后一周了,我终于不得不请假回来了。那时几乎是公司最忙的时候。

那一周也是我们最忙的时候。所有人都在做毕设——当然白天也还是操作的多。我们组有一两个因为之前特别认真,也懂了不少;还有两个也是有些编程基础;当然也还有人完全不会的,其中有一个人自始至终啥事也没干。时下大家做的都还是一些临散的页面,能直接用的只有两三张。于是回来的那个晚上我让大家先暂停一天,然后安排第二天晚上再碰一次面。然后花了一夜把站点后台的各个功能入口,以及有关用户使用方式的玩意儿先整了出来,把现在已有的页面放到该放的位置。第二天再碰头讨论具体的整个使用流程,以及各种疑问,把全局性的东西让大家都达成共识。接下来三四天大多数人也都比之前认真地做了自己的部分,到最后一天还有人有没有完成的,就只能剥夺其开发权利让其他人顶上了,也算马马虎虎凑成一整个玩意儿了。当然出自每个人之手的界面看上去还是不能完全统一。介于我负责的那一块自己也没太快搞定,想想算了,就这样吧。最后答辩的时候,我们的会议管理系统看上去其实还是像模像样的。结果当然是皆大欢喜,不管大家实质上做没做过啥。只是导师不无遗憾的说,应该可以做得更好的。我知道我让他失望了。

毕设搞定之后,我又回到了公司。想不到赶上了一个更激烈的时期,跨国公司有史以来加班最严重的一阵子。6月12号左右的三天,公司搬家,本来是放假的,因为赶版本,我们十几号人却被要求在某公寓里集体加班。那三天基本上是每晚上到2点以后。我比较撑不住,或者可能“假装”撑不住,困了就直接到卧室一头砸下去,等第二天被叫醒。要是凌晨2点以前下班了,那就第二天早上睡到11点多去,直接拿盒饭。反正加班么,调休少几个小时就少几个小时。哥真的累了。L2怎么看我已经不重要了。

接下来回学校的机会不多了。每次回去都像客人一样,心里真不是滋味。此时我才发觉,这么早去实习是多么的傻逼。同学们都在升华感情,我却浪费了人生唯一的大学毕业时期。当初请假的时候,L1和V都问过我,毕设一周之后要不要继续请假几天,我都很敬业地说不用了。我这一生已经没办法找回这样的时光了,只求后人不要犯我同样的错误,如果他确实认为这是个错误的话。直到现在,但凡知道有应届生要提前出来实习,只要搭得上话的,我都劝之好好考虑。

毕业证书搞定之后,我终于可以拿100%的工资了。前面一些月,我用一些血汗钱买了新的主板,CPU,显卡,凑成了一台新主机的主要部分。终于用上了独显,真是享受,虽然月光的季节总是让我时不时地感慨些许。7月份确实没之前那么疯狂了,但是L2还是三天两头下午临下班布置任务。这让我很烦恼,偶尔也问V:什么条件下可以换部门啊?答曰,入职满一年。我接着说,那我不是起码要加班加一年了?因为V之前跟我说公司不推崇加班的,这时候V会面露小尴尬,也跟我说过不好意思之类的。实际状况也是不怎么加班的——这在三月份确实是,在L1的部门现在也是,在其他部门基本上也是是的多。

大约9月份的时候,一进去就开始做、5月中旬彻底完成的那个小玩意儿又要出新版本了,还是L1带。作为那个项目的始作俑者,我又被划归了L1。这让我相当庆幸。可是好景不长,由于一些组织架构上的变动,H要来带那个项目,L1又纯粹带自己的服务端部门了。H是我的一面面试官,面试感觉相当好。后来也有若干次接触,感觉他是所学甚广,无所不通。这自然让同样所知甚杂的我有点佩服。在L2的带领和影响下全员加班的日子里,H也是时有公开抱怨的——嗯,价值观还算相近。加上H对我的知遇之恩吧,我很是欣然地接受了这个安排——虽然我基本上没有拒绝的选择。

终于摆脱L2了,终于不用TMD加班了!哦耶~!我怀着梦幻的期待,是无法按捺的情怀……

[未完待续]

posted @ 2011-01-20 23:36 溪流 阅读(2266) | 评论 (4)编辑 收藏

目录:

2009-2010小结(一)毕业前夕
2009-2010小结(二)初入职场
2009-2010小结(三)加班考验
2009-2010小结(四)抑郁重重
2009-2010小结(五)离职始末

2009年3月2日,是我上班的第一天。不过这时候还不叫正式工作,称为实习。公司方面说因为毕业证没拿到无法签订劳动合同。我要承受的代价就是50%工资。不过对于当时还没有尝过亲手赚钱的滋味的我来说,50%也已经相当丰厚了,至少这个学期吃饭问题可以自己解决了。

上班第一天,考研成绩出来了,我跟预料的一样,没考上:

B1E9144DD8637DFA_163_0

唯一可以得瑟的是,政治果然靠觉悟啊,我比参加了好些时日培训班的同学们也少不了几分;其余的不堪入目,就不多说了,丢脸,还复习过三天呢,没复习过还可以说说。

我们公司虽小,但两岸三地有好些分公司呢,再加上米国总部的 CxO 们,活生生一个跨国公司。在跨国公司的好处非常明显,常用书面英语不熟练都不可能。在真实的 Email 里头彪英文的感觉有时候还是蛮爽的。比较有意思的是,跟台北的同事们在MSN交流的时候,他们有时候说英文,我就纳闷了,你丫不是会说中文吗?于是回个中文,还是简体的。人家继续英文,我就继续淡定的国语伺候。当然,为了寻欢作乐,经常情况也会反过来。感觉米国的同事以及台北的同事用英文的时候,基本上不太考虑太多小语法的,看多了以后,自己写邮件的时候也就大胆了,差不多意思了就敢乱发了,还CC一大票人。现在一个多月没写了,有时候突然想写句英文,明显憋不出来了。

跨国公司的HR姐姐很亲切,暂时称为V姐吧。特别是打电话的时候,从电话那头听她的声音真是一种享受。不过在电话这头听她给别人打电话,就不是那么唯美了,稍微显得嗲了点。难道经过电路传输,语音会得到稀释吗?从2008年末开始接触,一直到现在,V姐一直给人无微不至的感觉。都说HR不可信,可是可信不可信又有什么重要呢?我有一个观点,特别想跟怀疑论者、阴谋论者分享,不管别人自认为是否对你好,只要那个人所做的事在你看来都对你好的,或者至少不坏,那么TA就是对你好的,即便人家的本意是想对你使坏。何谓好?何为坏?都是主观感受而已,我们生活在一个充满着主观意味的世界里,我们需要凭我们的感知能力去观察世界。于是,存在即被感知,是直观的和易于理解的,所谓的客观,才是人们杜撰出来的概念。从这个意义上来说,如果我们相信世界是美好的,世界就会比预期的美好得多。所以我认为我向来的不以最坏的恶意来揣摩他人的理念是行得通的。呃,扯远了。(批判我可以,不希望看到有人以“孩子,你还年轻”的态度来批判。)

我在跨国公司的第一任主管是L1姐,一个年轻的妈妈,在她身上洋溢着母爱。有时候晚上加班,听她给没几岁的孩子解释不能回去的理由,以及那种认真劲儿、那种语气,就能感知到她是沉浸在怎样的幸福中。不过L1在工作上的态度还是蛮严肃认真的。在上班的第二个星期,我就参与了实际项目,而且是个新小项目。这种机会蛮难得的。在L1的指导和要求下,我觉得我那时起才开始有了一点点设计理念。为了得到她的肯定,我会尽量将代码组织得漂亮点。之后不久我又被安排同时参与另一个项目的维护工作。看过现有代码,才感觉到大家写的代码原来是这样的糟糕。不太客气地说,80%以上的代码是没有经过好好组织的,90%以上的代码的书写是不工整的,以至于当时在手的那个新项目看上去爽多了。大概那时侯起,我觉察到老项目与新项目的截然不同的面貌,心里带上了一些疑问:是否老项目一开始也是很漂亮的呢?是否漂亮的新项目过些时日也会变得面目不堪呢?

L1一直带给我的很大帮助是,当我面对复杂的逻辑不知道如何下手时,她总是能够迅速给出一套简单、明了的方案,简单到当时的我也能立刻理解。从她的或详或简的解释中,能得到很多关于软件设计上的种种“规则”,这些规则,我所见过的好些多年开发者也不一定能够总结出来,不一定能够很好地遵守,甚至不一定觉察到。

有件不幸的事,L1安排我做项目的安装程序,也就是最后一道工序。当时我求知欲很强,多做点事最好,欣然应允。到后来才发现,发布版本的日子通常会加班,加班的时候我是最没事做的,经常是打酱油到10点,然后干几分钟活,最后帮忙测试。最夸张的一次是,被告知要修一个很紧要的bug,周六晚上赶去通宵加班,修bug本身不是我的事情,我是去等他们修好后打个包而已。所以一开始是没事干的,最多帮忙调试,看看什么问题。后来困了,就趴在桌子上睡觉,直到被叫起,已经第二天5、6点了,被告知修好了,然后打包,测试走人。虽然我大半个晚上在睡觉,人家在干活,可是通宵的味道并不好受。我也不喜欢没事做的加班。

说起加班,不得不再说几句。到目前为止,我都认为L1是跨国公司里对项目进度控制的最好的一个主管,时间观念很强。在我忘记或者即将忘记时间的时候,总是能得到善意提醒。所以她带的项目极少加班,需求也被整理的有条不紊,每周都知道要干什么,每天都直到要干什么,即使加班,也知道因何事而加,知道做完什么以后就能离开。

虽然属于实习,但是3月份是我这份工作中过的最开心的一段日子,也是成长蛮快的一段日子。在这里要衷心的谢谢L1在那段时期以及日后的很多日子里给予的指点、帮助,以及关心。

[未完待续]

posted @ 2011-01-17 23:29 溪流 阅读(2293) | 评论 (16)编辑 收藏

目录:

C++ 下 Function 对象的实现(上)
C++ 下 Function 对象的实现(下)

上篇中,我们实现了一个支持 R () 型函数的 Function。补充说明一下,在我们对成员函数的支持中,我们是这样定义的:

template <typename R, typename T>
class MemberFunction0 : public FunctionBase0<R>
{

private:
    R (T::*m_pMemFun)();
    T *m_pObj;
};

Loki 特意在著作中提醒我们,这里的 T 最好不要是函数类型,改为函数指针类型,如此该类的支持范围将扩大。如下:

template <typename R, typename P, typename T>
class MemberFunction0 : public FunctionBase0<R>
{
public:
    R Invoke()
    {
        return (m_pObj->*m_pMemFun)();
    }

public:
    MemberFunction0(P pObj, R (T::*pMemFun)())
        : m_pObj(pObj), m_pMemFun(pMemFun)
    {

    }

private:
    R (T::*m_pMemFun)();
    P m_pObj;
};

于是,P 和 T 的关系不那么紧密了,P 不一定非要 T* 不可,也可以是诸如 SmartPtr<T> 之类的玩意儿。原本只支持传入一个对象和该对象的成员函数的,现在变成传入一个具有指针概念的东东和一个成员函数,只要这个“指针”使用运算符 –> 去调用那个成员函数合乎语法即可。

接下来,我们来扩展这个 Function,以支持拥有数目在给定上限内的任意参数的函数。

我们先来手工写一下,看看如何支持带一个参数的函数。首先定义一个虚基类:

template <typename R, typename T0>
class FunctionBase1
{
public:
    virtual R Invoke(T0) = 0;
    virtual ~FunctionBase1() {}
};

实现两个版本,分别支持非成员函数和成员函数:

template <typename R, typename T0, typename T>
class Function1 : public FunctionBase1<R, T0>
{
public:
    R Invoke(T0 v0)
    {
        return m_Fun(v0);
    }

public:
    Function1(const T &fun)
        : m_Fun(fun)
    {

    }

private:
    T m_Fun;
};

template <typename R, typename P, typename T, typename T0>
class MemberFunction1 : public FunctionBase1<R, T0>
{
public:
    R Invoke(T0 v0)
    {
        return (m_pObj->*m_pMemFun)(v0);
    }

public:
    MemberFunction1(P pObj, R (T::*pMemFun)(T0))
        : m_pObj(pObj), m_pMemFun(pMemFun)
    {

    }

private:
    R (T::*m_pMemFun)(T0);
    P m_pObj;
};

增加一个函数引用萃取的偏特化版本:

template <typename RetType, typename T0>
struct FunctionTraits<RetType (T0)>
{
    typedef RetType (&ParamType)(T0);
};

增加一个 Function 类的偏特化版本:

template <typename R, typename T0>
class Function<R (T0)>
{
public:
    template <typename T>
    Function(const T &fun)
        : m_pFunBase(new Function1<R, T0, typename FunctionTraits<T>::ParamType>(fun))
    {
       
    }

    template <typename P, typename T>
    Function(P pObj, R (T::*pMemFun)(T0))
        : m_pFunBase(new MemberFunction1<R, P, T, T0>(pObj, pMemFun))
    {

    }

    ~Function()
    {
        delete m_pFunBase;
    }

    R operator ()(T0 v0)
    {
        return m_pFunBase->Invoke(v0);
    }

private:
    FunctionBase1<R, T0> *m_pFunBase;
};

现在,我们可以跑一下测试代码了:

Function<int (int)> f1(&intfun1);
Function<int (int)> f1_(intfun1);
Function<int (int)> f2(intfunctor1);
Function<int (int)> f3(&test, &Test::intmem1);

f1(1);
f1_(1);
f2(2);
f3(3);

当然,void 函数也是支持的。

观察上面的这些代码,和我们在上一篇中的代码高度一致,不同的是那些模版参数、偏特化参数、函数调用参数等地方。

假如有这么一组宏:
TYPENAME_DECLARE(n) 被定义为 typename T0, typename T1, …, typename Tn
TYPENAME_LIST(n) 被定义为 T0, T1, …, Tn
TYPENAME_VARIABLE(n) 被定义为 T0 v0, T1 v1, …, Tn vn
VARIABLE_LIST(n) 被定义为 v0, v1, …, vn

那么我们可以使用一个 n 就写出支持所有具有参数的函数的 Function 了。我们抛弃掉上面的 1 系列的所有类,仅保持上篇留下来的代码,然后利用上面 4 个宏将所有数字尾巴去掉,于是代码变成:

template <typename R, TYPENAME_DECLARE(n)>
class FunctionBase_##n
{
public:
    virtual R Invoke(TYPENAME_LIST(n)) = 0;
    virtual ~FunctionBase_##n() {}
};


template <typename R, TYPENAME_DECLARE(n), typename T>
class Function_##n : public FunctionBase_##n<R, TYPENAME_LIST(n)>
{
public:
    R Invoke(TYPENAME_VARIABLE(n))
    {
        return m_Fun(VARIABLE_LIST(n));
    }

public:
    Function_##n(const T &fun)
        : m_Fun(fun)
    {

    }

private:
    T m_Fun;
};

template <typename R, typename P, typename T, TYPENAME_DECLARE(n)>
class MemberFunction_##n : public FunctionBase_##n<R, TYPENAME_LIST(n)>
{
public:
    R Invoke(TYPENAME_VARIABLE(n))
    {
        return (m_pObj->*m_pMemFun)(VARIABLE_LIST(n));
    }

public:
    MemberFunction_##n(P pObj, R (T::*pMemFun)(TYPENAME_LIST(n)))
        : m_pObj(pObj), m_pMemFun(pMemFun)
    {

    }

private:
    R (T::*m_pMemFun)(TYPENAME_LIST(n));
    P m_pObj;
};

template <typename RetType, TYPENAME_DECLARE(n)>
struct FunctionTraits<RetType (TYPENAME_LIST(n))>
{
    typedef RetType (&ParamType)(TYPENAME_LIST(n));
};

template <typename R, TYPENAME_DECLARE(n)>
class Function<R (TYPENAME_LIST(n))>
{
public:
    template <typename T>
    Function(const T &fun)
        : m_pFunBase(new Function_##n<R, TYPENAME_LIST(n), typename FunctionTraits<T>::ParamType>(fun))
    {
       
    }

    template <typename P, typename T>
    Function(P pObj, R (T::*pMemFun)(TYPENAME_LIST(n)))
        : m_pFunBase(new MemberFunction_##n<R, P, T, TYPENAME_LIST(n)>(pObj, pMemFun))
    {

    }

    ~Function()
    {
        delete m_pFunBase;
    }

    R operator ()(TYPENAME_VARIABLE(n))
    {
        return m_pFunBase->Invoke(VARIABLE_LIST(n));
    }

private:
    FunctionBase_##n<R, TYPENAME_LIST(n)> *m_pFunBase;
};

当然上面这样子的代码是没法跑的咯。如果我们将整段代码定义为一个宏 BODY(n),然后用类似刚才四个宏的方式定义宏 FUNCTION_IMPLEMENT(n),使得它的含义为 BODY(0), BODY(1), …, BODY(n),所有工作就都完成了。最后只需要丢下一句 FUNCTION_IMPLEMENT(20),就可以支持 0 到 21 个参数了。

最后归结为,如何使用宏搞出“T0, T1, …, Tn” 的形式。

暴力点,我们可以这样:

#define T_0 T0
#define T_1 T_0, T1
#define T_2 T_1, T2
#define T_3 T_2, T3
#define T_4 T_3, T4
#define T_5 T_4, T5
#define T_6 T_5, T6
#define T_7 T_6, T7
#define T_8 T_7, T8
#define T_9 T_8, T9

#define T(n) T_##n

这样子,对于上面四个宏可以,但是对于最后的 X(n),人工代码量还是太大了。嗯?X(n)?对,这个 X,必须在 _1、_2、_3 系列宏里面占据一个参数地位,这样才有那么一点点扩展性。考虑换成这样:

#define REP_0(macro, n) macro(0)
#define REP_1(macro, n) REP_0(macro, n), macro(1)
#define REP_2(macro, n) REP_1(macro, n), macro(2)
#define REP_3(macro, n) REP_2(macro, n), macro(3)
#define REP_4(macro, n) REP_3(macro, n), macro(4)
#define REP_5(macro, n) REP_4(macro, n), macro(5)
#define REP_6(macro, n) REP_5(macro, n), macro(6)
#define REP_7(macro, n) REP_6(macro, n), macro(7)
#define REP_8(macro, n) REP_7(macro, n), macro(8)
#define REP_9(macro, n) REP_8(macro, n), macro(9)

#define REP(macro, n)   REP_##n(macro, n)

然后:

#define TYPENAME_LIST_PATTERN(n)    T##n
#define TYPENAME_LIST(n)            REP(TYPENAME_LIST_PATTERN, n)

这个 TYPENAME_LIST 就是符合上文要求的宏。接下来如法炮制其余三个:

#define TYPENAME_DECLARE_PATTERN(n)     typename T##n
#define TYPENAME_DECLARE(n)             REP(TYPENAME_DECLARE_PATTERN, n)

#define TYPENAME_VARIABLE_PATTERN(n)    T##n v##n
#define TYPENAME_VARIABLE(n)            REP(TYPENAME_VARIABLE_PATTERN, n)

#define VARIABLE_LIST_PATTERN(n)        v##n
#define VARIABLE_LIST(n)                REP(VARIABLE_LIST_PATTERN, n)

最后,我们在 #define FUNCTION_IMPLEMENT(n)  REP(BODY, n) 中还存在一点点问题。因为 BODY 中会含有 TYPENAME_DECLARE 之类的宏的使用,而 TYPENAME_DECLARE 正是使用 REP 定义的。这涉及到宏的递归展开,C++预处理器的规则是,遇到这样的情况就停止展开。比如,我们 定义 BODY(n) 为 TYPENAME_DECLARE(n),于是 FUNCTION_IMPLEMENT(2) 会被展成:

REP(TYPENAME_DECLARE_PATTERN, 0), REP(TYPENAME_DECLARE_PATTERN, 1), REP(TYPENAME_DECLARE_PATTERN, 2)

上面的 REP 不会被继续展开了。

为此,一个不太聪明的办法就是,再定义一组 REP2。嗯,是个办法,就这么办吧。另外我们刚才的 REP 系列没有将分隔符作为参数,默认使用逗号,而最后一不的 FUNCTION_IMPLEMENT 的重复中是不能用逗号的。考虑提取出来作为参数。最后我们的所需要的宏系统是:

#define NIL
#define COMMA ,

#define REP_0(macro, splitter, n) macro(0)
#define REP_1(macro, splitter, n) REP_0(macro, splitter, n) splitter macro(1)
#define REP_2(macro, splitter, n) REP_1(macro, splitter, n) splitter macro(2)
#define REP_3(macro, splitter, n) REP_2(macro, splitter, n) splitter macro(3)
#define REP_4(macro, splitter, n) REP_3(macro, splitter, n) splitter macro(4)
#define REP_5(macro, splitter, n) REP_4(macro, splitter, n) splitter macro(5)
#define REP_6(macro, splitter, n) REP_5(macro, splitter, n) splitter macro(6)
#define REP_7(macro, splitter, n) REP_6(macro, splitter, n) splitter macro(7)
#define REP_8(macro, splitter, n) REP_7(macro, splitter, n) splitter macro(8)
#define REP_9(macro, splitter, n) REP_8(macro, splitter, n) splitter macro(9)

#define REP(macro, splitter, n)   REP_##n(macro, splitter, n)

#define REP2_0(macro, splitter, n) macro(0)
#define REP2_1(macro, splitter, n) REP2_0(macro, splitter, n) splitter macro(1)
#define REP2_2(macro, splitter, n) REP2_1(macro, splitter, n) splitter macro(2)
#define REP2_3(macro, splitter, n) REP2_2(macro, splitter, n) splitter macro(3)
#define REP2_4(macro, splitter, n) REP2_3(macro, splitter, n) splitter macro(4)
#define REP2_5(macro, splitter, n) REP2_4(macro, splitter, n) splitter macro(5)
#define REP2_6(macro, splitter, n) REP2_5(macro, splitter, n) splitter macro(6)
#define REP2_7(macro, splitter, n) REP2_6(macro, splitter, n) splitter macro(7)
#define REP2_8(macro, splitter, n) REP2_7(macro, splitter, n) splitter macro(8)
#define REP2_9(macro, splitter, n) REP2_8(macro, splitter, n) splitter macro(9)

#define REP2(macro, splitter, n)   REP2_##n(macro, splitter, n)

#define TYPENAME_DECLARE_PATTERN(n)     typename T##n
#define TYPENAME_DECLARE(n)             REP(TYPENAME_DECLARE_PATTERN, COMMA, n)

#define TYPENAME_LIST_PATTERN(n)        T##n
#define TYPENAME_LIST(n)                REP(TYPENAME_LIST_PATTERN, COMMA, n)

#define TYPENAME_VARIABLE_PATTERN(n)    T##n v##n
#define TYPENAME_VARIABLE(n)            REP(TYPENAME_VARIABLE_PATTERN, COMMA, n)

#define VARIABLE_LIST_PATTERN(n)        v##n
#define VARIABLE_LIST(n)                REP(VARIABLE_LIST_PATTERN, COMMA, n)

#define FUNCTION_IMPLEMENT(n)  REP2(BODY, NIL, n)

最后,定义一下 FUNCTION_IMPLEMENT(5),就可以支持到 6 个参数了。为了支持更多参数,把上面的 REP 以及 REP2 系列多定义一点,比如到 50,那么 FUNCTION_IMPLEMENT 的括号中就可以填 50 以内的任意数了。考虑到宏展开对编译速度的影响,以及实际应用中函数参数的个数,定为 20 左右比较合适。

到这里,我们的Function已经实现了预期目标。接下来我本来想说说 TypeList 的。可是现在发现没有 TypeList,Function 跑的通;有了 TypeList,Function 也不能写的漂亮多少,虽说那些重复部分有一定的减少。Loki 的 Functor 的参数类型是一个返回值类型加上一个 TypeList,是由用户直接传入 TypeList 的,不用由散的类型组合出一个TypeList(但还是要从TypeList中萃取各个参数类型),因此用在他那里看上去美妙一点点。当然,Loki 也在 Functor 外头包了一层 Function,以支持函数签名作为模版参数的使用方式。有一点不算改观的改观是,用了 TypeList 以后,就不会再有 FunctionBase_1, FunctionBase_2 这样的玩意儿了,取而代之的是一个统一的 FunctionBase 外加许多偏特化版本,Function* 和 MemberFunction* 可以分别统一为一个,但是每一个里头都需要实现 N 个 Invoke。加上篇幅关系,我想这里就不说这个 TypeList 了。

代码清单太长了,就不贴了,有意者自然能凑起来。我目前在 xlLib 中的最终实现见 xlFunction.h

关于宏,我不知道可以怎样改进。BOOST_PP_REPEAT 貌似可以调用自身?不知道如何实现的,求指教。另外@vczh貌似说“实现了一门可以自己递归自己和内置列表处理的另一个宏”,求分享呀求分享。

2010-01-18 补充:将最外层 Function 的构造函数中的 const T & 直接改为 T,并且抛弃 FunctionTraits,函数实体类型将在传递过程中直接退化为函数指针,这样就能特化出正确的 FunctionHandler。同时带来另一点影响:如果传入 Functor,字面上将多一次拷贝动作。抛开这一点微小的性能来讲,这比刚才的 FunctionTraints 要好得多了。

posted @ 2011-01-17 21:59 溪流 阅读(4158) | 评论 (5)编辑 收藏

目录:

C++ 下 Function 对象的实现(上)
C++ 下 Function 对象的实现(下)

起因在上一篇已经说过了。现在让我们直接进入主题。本文的目标是,让以下代码能顺利跑起来:

int intfun0()
{
    return 1;
}

struct _intfunctor0
{
    int operator()()
    {
        return 2;
    }

} intfunctor0;

struct Test
{
    int intmem0()
    {
        return 3;
    }

} test;

int main()
{
    Function<int ()> f1(&intfun0);
    Function<int ()> f1_(intfun0);
    Function<int ()> f2(intfunctor0);
    Function<int ()> f3(&test, &Test::intmem0);

    f1();
    f1_();
    f2();
    f3();

    return 0;
}

除了上述例子中显示的,还要支持有返回值的函数和没返回值的函数,以及有0个、1个、2个、……、MAX 个参数的函数,参数类型无限制。最后实现的 Function 对象仅仅可以执行就好。(至于是否可拷贝、是否可判断相等 等问题,都是小事,本文暂不考虑。)最后,Bind 概念也不在本文讨论范围之内。

对于这个问题,我们一开始考虑的可能是怎样统一三种不同形式。有两个选择,第一,使用 C++ 的多态机制,最后统一到基类指针的类型;第二,允许类内部有冗余变量以及必要的 Flag,用于判断是哪种形式的函数,要如何执行。这样看起来,第一种方案比第二种爽一点。于是,最初想到的实现有可能是这样的:

先定义一个虚基类:

template <typename R>
class FunctionBase0
{
public:
    virtual R Invoke() = 0;
    virtual ~FunctionBase0() {}
};

然后实现一个普通函数/仿函数的版本:

template <typename R, typename T>
class Function0 : public FunctionBase0<R>
{
public:
    R Invoke()
    {
        return m_Fun();
    }

public:
    Function0(const T &fun)
        : m_Fun(fun)
    {

    }

private:
    T m_Fun;
};

这里需要说明的是,如果是普通函数,T会被特化成 R() 或者 R (&)() 或者 R(*)(),取决于使用的时候传入 fun 还是传入 &fun。所以不必另外实现针对 R(*)() 的版本。Loki (姑且就以作品名称乎 Loki 的作者吧,他那个真名实在是太长)在他的书中称之为“做一个,送一个”。不过对于他书中所说的,我有一个疑惑。Loki 说传入 fun,模版参数 T 会被特化成 R (&)(),于是一切顺利。可是我在操作过程中发现 T 一直被特化成 R (),于是上述 class 中的 m_Fun 被认为是成员函数而不是成员变量。不知道是为什么,有知道者请不吝指教哈。因为以上原因,本文中我一直用 &fun 的形式对待普通函数。

再实现一个成员函数的版本:

template <typename R, typename T>
class MemberFunction0 : public FunctionBase0<R>
{
public:
    R Invoke()
    {
        return (m_pObj->*m_pMemFun)();
    }

public:
    MemberFunction0(T *pObj, R (T::*pMemFun)())
        : m_pObj(pObj), m_pMemFun(pMemFun)
    {

    }

private:
    R (T::*m_pMemFun)();
    T *m_pObj;
};

最后是一个包装类。如果你可以接受 Function<int> 表示 int(), Function<int, int> 表示 int (int),…,那么这里没有多少技巧可言。boost 的那个 function 使用的是函数签名作为模版参数,即 Function<int()>,Function<int (int)> 等形式。如果不太研究语法,可能会像我一样,一开始会对尖括号里的 int (int) 之类的玩意儿不太熟悉,觉得很牛逼。可是了解了以后,不过是个函数类型而已,没什么大不了的。Loki 的 Functor 的使用方式是 Functor<int, TYPELIST_0()>,Functor<int, TYPELIST_1(int)>。其中第一个模版参数始终是返回值,第二个模版参数是参数类型列表,Loki 使用了他创造的玩意儿 TypeList 使得所有函数参数只占一个坑,这在等下的支持多参数的扩展中能够带来一些美观。我比较喜欢 boost 的使用方式,让使用者直接以语言规定的形式填入函数签名,而不是一些额外的约定(“第一个模版参数表示返回值”,“第二个到最后的模版参数表示参数”,“第二个模版参数以 TypeList 形式表示函数参数”等)。

为了达到这个目标,我们要玩一些偏特化技巧。关于偏特化,我一直以来的肤浅认识都是错误的。我原以为,对于模版类:

template <typename T0, typename T1>
class Foo;

我如果特化其中一个参数 T1:

template <typename T0>
class Foo<T0, int>
{

}

我以为只有这样才叫偏特化,以为偏特化的过程总是减少模版参数的。而实际上,只要用某个/些类型占据原始模版参数的位置,就可以了。比如,对于上述 Foo,我可以特化一个 class<T0, std::map<U0, U1>>,消去一个 T1,而新增 U0、U1:

template <typename T0, typename U0, typename U1>
class Foo<T0, std::map<U0, U1>>
{

}

原来 T1 的位置被 std::map<U0, U1> 占据了,这也是偏特化。当然最后的模版参数数量也可以不变,如:

template <typename T0, typename U>
class Foo<T0, std::vector<U>>
{

}

以及

template <typename T0, typename U>
class Foo<T0, U*>
{

}

其中后者是实现类型萃取的主要方式。只要特化以后,这个类依然带有至少一个模版参数,就是偏特化。如果最后产生了 template<> 的形式,那就是完全特化。

回到我们刚才的主题,我们要提供给用户的是这样一个类:

template <typename Signature>
class Function;

其中参数 Signature 会被实际的函数类型所特化。但是我们只知道整体的一个 Signature 并没有用,我们必须知道被分解开来的返回值类型、参数类型。于是,引入一个偏特化版本:

template <typename R>
class Function<R ()>

这里使用 R () 特化原始的 Signature,引入一个新的参数 R。于是返回值类型 R 就被萃取出来了。实现如下:

template <typename R>
class Function<R ()>
{
public:
    template <typename T>
    Function(const T &fun)
        : m_pFunBase(new Function0<R, T>(fun))
    {
       
    }

    template <typename T>
    Function(T *pObj, R (T::*pMemFun)())
        : m_pFunBase(new MemberFunction0<R, T>(pObj, pMemFun))
    {

    }

    ~Function()
    {
        delete m_pFunBase;
    }

    R operator ()()
    {
        return m_pFunBase->Invoke();
    }

private:
    FunctionBase0<R> *m_pFunBase;
};

如果对上面说的“普通函数的使用方式必须是函数指针而不是函数本身”耿耿于怀,可以再引入一个的构造函数:

typedef R (FunctionType)();

Function(const FunctionType &fun)
    : m_pFunBase(new Function0<R, FunctionType &>(fun))
{

}

这里 FunctionType 是 R(&)() 类型,强制使用它来特化 Function0 中的 T。该构造函数在重载决议中会取得优先权从而使普通函数本身的传入成为可能。不过,以函数本身形式传入的普通函数会丧失一些特性,比如 Function<int()> 只能接受 int() 类型的普通函数而不能接受 char () 型的普通函数,因为这种情况下不会走我们刚才新定义的构造函数。

还有一种做法,就是针对全局函数,强制特化出模版参数为其引用类型的类。定义如下元函数:

template <typename Signature>
struct FunctionTraits
{
    typedef Signature ParamType;
};
   
template <typename RetType>
struct FunctionTraits<RetType ()>
{
    typedef RetType (&ParamType)();
};

然后构造函数改为:

    template <typename T>
    Function(const T &fun)
        : m_pFunBase(new Function0<R, typename FunctionTraits<T>::ParamType>(fun))
    {
       
    }

用以上方法,所有的特性都不会丢失。

到这儿,我们的 Function 已经可以小试牛刀了:

Function<int ()> f1(&intfun0);

Function<int ()> f1_(intfun0);
Function<int ()> f2(intfunctor0);
Function<int ()> f3(&test, &Test::intmem0);

f1();
f1_();
f2();
f3();

上面这段代码已经能够正常运行了。

来,继续做一个,送一个。下面的代码居然也能跑(voidfun0、voidfunctor0、Test::voidmem0类似int版本定义):

Function<void ()> f4(&voidfun0);
Function<void ()> f4_(voidfun0);
Function<void ()> f5(voidfunctor0);
Function<void ()> f6(&test, &Test::voidmem0);

f4();
f4_();
f5();
f6();

这说明了,在类里面写一个返回值为该类型的函数,并在里面写下 return XXX; 然后以 void 为模版参数传入该模版类,是符合语法的。验证一下:

template <typename T>
class Foo
{
public:
    T Bar()
    {
        printf("%s invoked\n", __FUNCTION__);
        return T();
    }
};

int main()
{
    Foo<void> f1;
    f1.Bar();

    Foo<int> f2;
    int i = f2.Bar();

    return 0;
}

运行结果:

Foo<void>::Bar invoked
Foo<int>::Bar invoked

到此为止,我们已经实现了 0 个参数的函数支持,也即 R () 类型的所有函数的支持。接下来还要实现对具有 1 个、2 个、3 个直至任意有限个参数的函数支持。也许您也发现了,接下来的工作可以是体力活,我们可以照葫芦画瓢,搞出一堆 FunctionBaseN、FunctionN、MemberFunctionN,并在最后的 Function 中再实现 N 个偏特化版本。是,不错,大致上原理就是这样。限于篇幅,我想暂时写到这里,下篇将继续谈谈宏、TypeList,以及怎样少花点力气实现其余 N 个版本。最终达到的效果是,只要改一个宏定义,就可以提高参数上限。

在本文所涉及的内容中,我比较纠结的是,可否在不用多态机制的情况下达到比较优雅的形式统一?

欢迎讨论。

posted @ 2011-01-16 22:17 溪流 阅读(7295) | 评论 (55)编辑 收藏

事情的缘起是,耐不住寂寞,准备开始造GUI的轮子。

GUI框架,要做的事情我想大概是这么几步:

  1. 实现回调函数的成员化。
  2. 实现方便程度可接受的消息映射。
  3. 确定上述核心部件的使用方式。
  4. 制造大量的控件。

前三步要走的比较小心,第四步是体力劳动。

第一步,Windows下可参考的是MFC方式、WTL方式,以及利用Window相关属性中的某些空位。前不久刚初步看过WTL的机制,虽然当时没写GUI框架的打算,不过也有点技术准备的意思了。现学现用吧。这里一个可以预见的问题是64位兼容,现在没有测试环境,先不管。

接下来看第二步了,所要做的事情就是把 WndProc 下的 一堆 case 有效地组织起来,或者换个写法。之前还真不知道 MFC/WTL 的 BEGIN_MSG_MAP。以为很高深的,想不到就是拼装成一个大的 WndProc。先抄了,做成一个可运行的版本。但是,这方面会直接决定以后的大部分使用方式,单单抄一下意义不大。后来去 @OwnWaterloo 曾推荐过的 @cexer 的博客上逛了几圈,第一圈看了一些描述性文字,第二圈大概看了下技术,第三圈是挖坟,那个传说中的 cppblog 第一高楼啊。。其中有一个使用方式很新颖,嗯……是那个不需要手动写映射代码,直接实现消息处理函数的方式。不过我后来觉得还是不要这种样子了,凭我个人的直觉,如果我写下这样的处理函数,我大概会因为不知道何时注册了这个函数而找不到调用来源而感到郁闷。在Windows回调机制的影响下,我可能会很抱有偏见地认为,只有直接来自WndProc的调用,才算是来源明确的,不需要继续追踪的——当然,这是建立在我不熟悉这个框架的基础上的。框架必然需要隐藏调用来源,以及其他一些细节,但是在这一步,我觉得稍微有点早。

刚才说到的都是静态绑定。现在我有点倾向于动态绑定。从使用方便程度上来看,动态绑定更具灵活性。从性能上,动态绑定下,消息到处理函数的查找过程可以更快,静态绑定只能遍历。当然,未必将“添加处理函数”这样的接口提供给最终用户,但是这个操作对于整个控件体系的形成应该蛮有帮助的吧。比如MFC下一个控件类使用Message Map做了一些事情,继承类就无法直接继承这个动作,于是可能需要做两套处理函数调用机制,一套是给内部继承用的,一套是给用户的。如果在最开始的基类保存一个消息映射,每个消息对应一族处理函数,每个继承类都可以添加处理函数,但不删除父类已添加的函数,这样就可以在一套Message Map机制下获得父类的行为。以上,不知道考虑得对不对,欢迎讨论。

其中,父类保存子类给出的可调用体并正确执行是个问题。折腾了一些时间,都没有成功。我比较纠结,想知道除了用function之类的玩意儿外还有没有其他简单可行的办法。后来去@zblc的群上问,@vczh也说需要一套function机制。看来是逃不开这个问题了。嗯……想起来大约两个月前一个同事从codeproject找来了一个GUI框架看,看到几行整整齐齐的 AddMsgHandler(WM_CREATE, XXX(this, &MyWindow::OnCreate));,叹不已。我当时打趣说,这很简单的,无非是搞了个 function 而已,哥哥两天就能搞定。于是他们叫我两天搞定。我鼓捣了10分钟,搞不定,只好丢一句,真的很简单的,类似boost::function,你去看一下就知道了,哥哥要干活了。

既然现在还是绕不开这个问题,那还是搞一下了,搞好以后就权且当做给他们交作业吧。我会另写一篇文章说说function的事情,这里先略过。现在开始假设这个设施已经造好了。那么,窗口类中大概可以这么定义相关类型:

typedef Function<bool (WPARAM, LPARAM)> MsgHandler;
typedef List<MsgHandler> MsgHandlerList;
typedef Map<UINT, MsgHandlerList> MsgMap;

然后再定义一个变量:

MsgMap  m_MsgMap;

它用于保存消息映射。最终的回调函数可以写成:

LRESULT WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    bool bHandled = false;

    MsgMap::Iterator itMsgMap = m_MsgMap.Find(uMsg);

    if (itMsgMap != m_MsgMap.End())
    {
        for (MsgHandlerList::Iterator it = itMsgMap->Value.Begin();
             !bHandled && it != itMsgMap->Value.End(); ++it)
        {
            bHandled = (*it)(wParam, lParam);
        }
    }

    return bHandled ? TRUE : DefWindowProc(m_hWnd, uMsg, wParam, lParam);
}

最后给个添加消息映射的接口:

void AppendMsgHandler(UINT uMsg, MsgHandler pMsgHandler)
{
    m_MsgMap[uMsg].PushBack(pMsgHandler);
}

到目前为止,我们的窗口类大致上可以写成这样:

#include <Windows.h>
#include <tchar.h>
#include "../GUIFramework/xlWindowBase.h"

class Window : public xl::WindowBase
{
public:
    Window()
    {
        AppendMsgHandler(WM_ERASEBKGND, MsgHandler(this, &Window::OnEraseBackground));
        AppendMsgHandler(WM_PAINT,      MsgHandler(this, &Window::OnPaint));
        AppendMsgHandler(WM_LBUTTONUP,  MsgHandler(this, &Window::OnLButtonUp));
        AppendMsgHandler(WM_RBUTTONUP,  MsgHandler(this, &Window::OnRButtonUp));
        AppendMsgHandler(WM_DESTROY,    MsgHandler(this, &Window::OnDestroy));
    }

protected:
    bool OnEraseBackground(WPARAM wParam, LPARAM lParam)
    {
        return false;
    }

    bool OnPaint(WPARAM wParam, LPARAM lParam)
    {
        PAINTSTRUCT ps = {};
        BeginPaint(m_hWnd, &ps);

        RECT rect = { 200, 200, 400, 400 };
        DrawText(ps.hdc, _T("Hello, world!"), -1, &rect, DT_CENTER | DT_VCENTER);

        EndPaint(m_hWnd, &ps);
        return false;
    }

    bool OnLButtonUp(WPARAM wParam, LPARAM lParam)
    {
        MessageBox(m_hWnd, _T("LButtonUp"), _T("Message"), MB_OK | MB_ICONINFORMATION);
        return false;
    }

    bool OnRButtonUp(WPARAM wParam, LPARAM lParam)
    {
        MessageBox(m_hWnd, _T("RButtonUp"), _T("Message"), MB_OK | MB_ICONINFORMATION);
        return false;
    }

    bool OnDestroy(WPARAM wParam, LPARAM lParam)
    {
        PostQuitMessage(0);
        return false;
    }
};

在最基础的 WindowBase 里,搞成这样大概差不是很多了。暂时先看第三步。到目前为止,我所听说过的 GUI 框架都是真正的框架,似乎没有“GUI 库”。为什么一定要以继承某个基类的方式来使用呢?如果像下面这样使用呢?

class Window
{
private:
    xl::WindowBase m_WindowBase;

public:
    Window()
    {
        m_WindowBase.AppendMsgHandler(WM_ERASEBKGND, MsgHandler(this, &Window::OnEraseBackground));
        m_WindowBase.AppendMsgHandler(WM_PAINT,      MsgHandler(this, &Window::OnPaint));
        m_WindowBase.AppendMsgHandler(WM_LBUTTONUP,  MsgHandler(this, &Window::OnLButtonUp));
        m_WindowBase.AppendMsgHandler(WM_RBUTTONUP,  MsgHandler(this, &Window::OnRButtonUp));
        m_WindowBase.AppendMsgHandler(WM_DESTROY,    MsgHandler(this, &Window::OnDestroy));
    }
};

这个问题,不知道各位有没有什么思考?

还有一个问题是,接下去要不要将 WPARAM 和 LPARAM 的含义彻底解析掉,搞成一系列 PaintParam、EraseBackgroundParam、LButtonUpParam、RButtonUpParam,DestroyParam,让使用的时候与原始消息参数彻底隔离呢?

最后一步,虽说是体力活,但这跟最终的应用场合密切相关,需要提供怎么样的功能是一件需要考量的事。

目前走在第二步,所以下面的两个问题思考得不多。求经验,求意见。

posted @ 2011-01-16 20:05 溪流 阅读(4189) | 评论 (11)编辑 收藏
仅列出标题
共18页: First 6 7 8 9 10 11 12 13 14 Last