zoukankan      html  css  js  c++  java
  • 在C++中用模版实现property

      前几天为了查找一个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

    作者:小时了了
    原创文章,欢迎转载,但请以链接形式注明本文地址.
  • 相关阅读:
    Django Form组件的扩展
    Python TCP与UDP的区别
    Python三次握手和四次挥手
    网络基础之网络协议
    Python 类方法、实例方法、静态方法的使用与及实例
    python深浅拷贝
    2021牛客寒假算法基础集训营1 题解
    01 Trie 专题
    MOTS:多目标跟踪和分割论文翻译
    牛客巅峰赛S2第6场题解
  • 原文地址:https://www.cnblogs.com/xrunning/p/2243826.html
Copyright © 2011-2022 走看看