分类: 2010 December

封装C++的成员函数调用

ref: <Wrapping C++ Member Function Calls> by Bjarne Stroustrup.

我们知道,一个标准的代码编写时要走很多流程的,正如下面这样:

unzip, strip, touch, finger, grep, mount, fsck, more, yes, fsck, fsck, fsck, umount, sleep

可是有些急性子程序员,打代码打出了快感,不问架构上下文,无视可扩展性,对着终端直接就是fsck, fsck, fsck,留下了一堆烂摊子.后人接手代码则头痛不已,一段代码没有前戏更少了后文.怎么处理呢?

最简单的方法自然是在自己的代码里手动添加前后文,如:

class X;

void DoSomething(X *x)
{
	prefix(); //开工
	x -> f();
	suffix(); //收尾
}

这工作有点无聊,在每一次调用x -> f();时前后都要加上prefix()和suffix().一个很明显的做法是直接在类X的源码里对f()做封装,也就是:

class X
{
//...
	f() {prefix(); /*这里是f()的本职工作*/ ; suffix();}
//...
};

这对类X的客户来说是透明的,可是对类X的实现者就是个考验了,首先是无聊的成对加上prefix()和suffix(),其次是类X的实现者需要很先验地判断哪些函数需要,哪些函数不需要如此这般被封装.当然,责任越大,能力越大,一个明显的好处是类的函数成员被有挑选性的封转,而不是眉毛胡子一把抓了.

下面主要是看看宁杀一千的情况.

在design and evolution of C++一书中,Bjarne Stroustrup老师提到C++在其祖父C with classes年代有一个特性,它允许类成员函数在被调用之前和之后执行一个类实现者自定义的函数(call()和return()).以下面的代码为例:

class X
{
//...
	call() { /*初始化,加锁等等.*/ }
	return() { /*清理工作,解锁等等.*/ }
//...
};

class Y : public X
{
public:
	void f();
};

void DoSomething(Y *y)
{
	y -> f(); //先是X::call() 然后 Y::f() 最后 X::return() (假设Y中未定义自己的call()和return())
}

可惜的是,C++这个特性后来被移除了,未能加入到标准里面..

跳过历史的光明期,我们利用template同样可以实现这个机制.先看看prefix,重载 -> 可以实现它.

template<class T>
class CPrefix
{
private:
	T * m_pPtr;
public:
	CPrefix(T *ptr): m_pPtr(ptr) {}
	T * operator -> ( /* prefix */ return m_pPtr;)
};

X x;
CPrefix<X> oPrefix(&x);

void DoSomething(CPrefix<X> p)
{
	p -> f(); //prefix code; X::f()
}

注意Prefix是如何非侵入地封装类,这在我们无法修改类的实现的时是一个不错的技巧..

接下来看看suffix如何实现,这里我们可以借鉴RAII的想法:

class CSuffix
{
public:
	~CSuffix() { /* suffix code */ }
};

void DoSomething(X *x)
{
	CSuffix oSuffix;
	x -> f();
	//...
	// 隐式调用suffix code
}

利用析构函数做收尾工作有一个很牛逼的特性是即使DoSomething中throw了异常,suffix code依旧会被执行.(btw,所以还是不要用longjmp了,它在unwinding stack时会有一点小问题.提到异常处理就不得不谈RTTI,这个留给以后来说.)
将以上两种方法合二为一,模拟出call()和return()得:

template<class T>
class CWrap
{
private:
	T * m_pPtr;
	CWrap(const CWrap &);
	CWrap & operator = (const CWrap &);
	
	class CCallProxy
	{
	private:
		T * m_pPtr;
		CCallProxy(T *ptr) : m_pPtr(ptr) {}
		
	public:
		~CCallProxy() { m_pPtr -> suffix(); }
		T * operator -> () const { return m_pPtr; }
		friend class CWrap<T>;
	}
	
public:
	Wrap(T &x) : m_pPtr(&x) {}
	CCallProxy operator -> () const { m_pPtr -> prefix(); return CCallProxy(m_pPtr); }
};

测试一下:

class CTest
{
public:
	void test() { std::cout<<"test()\n"; }
	void prefix() { std::cout<<"prefix()\n"; }
	void after() { std::cout<<"after\n"; }
}

int main()
{
    CTest oTest;
    CWrap<CTest> w(oTest);
    w -> test();
}

输出如下:

prefix()
test()
suffix()

不要小看CCallProxy的思想,有些情况下它能发挥出大作用,这加上权限控制,模板参数化神马,因为涉及到类成员函数指针这一C++阴暗面和仿函数等等,以后再聊.

ps. 博客主机越来越慢了,我把它加入了自己翻墙的list,so,能订阅的还是订阅吧.

一篇迟到的小结

实习结束一周了,最近玩的很累,很精简地小结一下..

编程

编程能力上的提高是最为显而易见的收益.我开始注重代码质量,渐渐明白了之前所写的大学生练习似的代码是多么的脆弱,书本的讲解为了简洁去掉了太多需要考虑的关键,称之为toy不无道理.工业上的每一个优雅的接口背后都是丑陋甚至蹩脚的实现,无穷的封装,数不尽的边界判断,精力就这么消耗在细节这个该死的魔鬼里.为了效率,我学会了如何聪明的打log,实现自己的跟踪类,努力掌握classwise在内存上的分配和消亡.最重要的是,我认识到了代码复用的好处,开始整理自己的代码库,相信这是一个不错的开始.

分享

这个不多说..

生活

这个也不多说了,肥了20多斤,相当的悲剧..

小结

如上,无它.