向左右向右走 —— 小时了了的技术博客

关注C++开发技术、架构设计、软件项目管理、软件产品管理等

posts - 19,comments - 55,trackbacks - 0

  前几天为了查找一个BUG花了几乎一整天的时间,然后——就像江湖传说的一样,只改了一行代码就搞定了。至于原因仅仅是因为一个变量未初始化,一秒钟的疏忽却花了一天的代价来弥补,就像那个蚂蚁和大象的段子:风流了一夜,挖一辈子的坑。

  这类BUG通常是不稳定重现的测试未必测的出来,测出来了也未必容易定位,若是不小心留到了用户那里可能真的就成了一辈子的坑,永远都解决不掉了。这样的事我想命苦的C++程序员们初入江湖的时候可能都遇到过那么一两回。——声明一下,以前我也搞出过这样的BUG,但从那之后没再犯第二次错误,这次的代码是别人写的。

  俗话说人是靠不住的,只有靠制度才有保障,哪怕我们再小心也总有马虎大意的时候。所以今天我们想讨论的就是怎样以“制度”的方式一劳永逸的解决这个问题。方案其实挺简单,就是把成员变量封装到一个独立的类里,在构造函数里做初始化,然后用这个类的对象替代普通的成员变量。

  这里我们一步到位直接给出一个模版实现:

	template<typename T, T _v>
	class Property
	{
	public:
		Property(void) : m_value(_v)
		{
		}

		operator T(void) const					
		{ 
			return m_value; 
		}

		T operator=(T v_)	
		{ 
			m_value = v_;
			return m_value;
		}

	private:
		T	m_value; 
	};

  为什么取名叫Property我们后面再说,先看使用方式:

	class Rectangle
	{
	public:
		int GetWidth(void) 	const	{ return m_width; }
		int GetHeight(void) 	const	{ return m_height; }
		void SetWidth(int width)	{ m_width = width; }
		void SetHeight(int height)	{ m_height = height; }

	private:
		Property<int, 0> m_width;
		Property<int, 0> m_height;
	};

  一个小小的模版不仅解决了可能忘记初始化成员变量的问题,而且买一送一,直接在声明变量的时候就可以指定初始化值,避免初始化代码和变量声明代码两地分居,很强大吧。

  从小就有人教导我们说成员变量要声明成private的,然后写一组get/set函数来做读写操作。上面的Rectangle类中我们就是这么做的,但是事实上我们已经用Property模版对真正的成员变量的访问封装过一层了,再封装一层get/set函数显得有些多余,完全可以简化一下,简化版本的Rectangle如下:

	class Rectangle
	{
	public:
		Property<int, 0> width;
		Property<int, 0> height;
	};
  使用起来也更方便直观:
	Rectangle rect;		
	rect.width = 320;
	rect.height = 240;

	int width = rect.width;
	int height = rect.height;

  

  如果你用C++之余还使用过C#之类的语言那么你一定对其中的property(属性)印象深刻,并且叹息C++中为什么没有这样的好东西。不过C++有一个优点就是几乎什么东西都可以自己做,我们的Property模版就相当于为C++添加了一个简化版的“属性”,当然我们还需要继续重载大于小于等操作符进一步完善它。

  美中不足的是我们虽然通过Property封装了对成员变量的读写操作,但没有实现对读写操作的监控,实际上设计get/set函数特别是set函数最重要的目的就在于此。我这个人比较喜欢花哨的语法糖,所以下面我们就用C++中最摩登的方法实现对成员变量写操作的监控,方案是给Property增加一个function用于事件回调。代码如下:

	template<typename T, T _v>
	class Property
	{
	public:
		Property(void) : m_value(_v)
		{
		}

		operator T(void) const					
		{ 
			return m_value; 
		}

		T operator=(T v_)	
		{ 
			if (m_value != v_)
			{
				m_value = v_;
				if (OnChanged != nullptr)
				{
					OnChanged();
				}
			}
			return m_value;
		}

	public:
		typedef std::function<void(void)>	_Event;
		_Event	OnChanged;

	private:
		T		m_value; 
	};

  使用时只要给OnChanged成员赋值就可以了。下面的代码中使用了lambda表达式赋值,可以在lambda表达式中进行事件处理:

	rect.width.OnChanged = []{ /* do something */ };

  更理想的方案是实现事件多播,也就是为OnChanged绑定多个function对象,这样就可以像C#中的事件那样使用:

	rect.width.OnChanged += []{ /* do something */ };
	rect.width.OnChanged += []{ /* do another thing */ };

 

  由于C++的function本身不支持多播,所以必须自己实现。简单的方法是使用一个vector存储function对象,不过使用起来有点麻烦,更好的方法是自己实现一个支持多播的Event类,这个实现起来有点难度我还没尝试过,如果你做过类似的东西或者有更好的实现多播的方法可以交流一下。

  关于function和lambda表达式的使用可以参考我之前的两篇文章:《使用function改进设计》和《在VS2010中使用auto关键字和lambda表达式》。

 

本文地址:http://www.cnblogs.com/xrunning/archive/2011/11/10/2243826.html

posted on 2011-11-11 00:38 小时了了 阅读(...) 评论(...) 编辑 收藏