非议MFC(一)宏和类型定义的困惑 关键字:C++,MFC,宏,macro,define,typedef 有感于MFC库代码之去简就繁、之故弄玄虚,作下文,聊博一笑。
请看一段常见的代码: //in user.h class CTest { private: int x; public: void SetX(int setX); int GetX() const; operator int *(); operator const int *() const; void * GetSafeHandle() const; }; //in user.cpp #include "user.h" void CTest::SetX(int setX) { x=setX; } //... 我们(MFC的作者)认为这样的代码太浅显,不够深沉,缺少内涵,没有嚼劲。赶快include我们的头文件吧,它可以提高代码的整体形象。我们的口号是:让蓝色关键字在屏幕上消失。 //in minimfc.h #define USER_CLASS class #define BEGIN_CLASS_DECLARATION { #define END_CLASS_DECLARATION }; #define PRIVATE_MEMBER private: #define PROTECTED_MEMBER protected: #define PUBLIC_MEMBER public: #define OPERATOR_OVERLOAD operator #define CONSTANT_MEMBER_FUNTION const #define BEGIN_FUNCTION_DEFINITION { #define END_FUNCTION_DEFINITION } typedef void AFX_RETURN_VOID_FUNCTION; typedef int INT; typedef int * LPINT; typedef const int * LPCINT; typedef void * HOBJ; 看!我们做到了。从此,我们的客户代码将这样写: //in user.h #include "minimfc.h" USER_CLASS CTest BEGIN_CLASS_DECLARATION PRIVATE_MEMBER INT x; PUBLIC_MEMBER AFX_RETURN_VOID_FUNCTION SetX(INT setX); INT GetX() CONSTANT_MEMBER_FUNTION; OPERATOR_OVERLOAD LPINT(); OPERATOR_OVERLOAD LPCINT() CONSTANT_MEMBER_FUNTION; HOBJ GetSafeHandle() CONSTANT_MEMBER_FUNTION; END_CLASS_DECLARATION //in user.cpp #include "user.h" AFX_RETURN_VOID_FUNCTION CTest::SetX(INT setX) BEGIN_FUNCTION_DEFINITION x=setX; END_FUNCTION_DEFINITION //...
非议MFC(二)逻辑上的不完备
作者:cphj 来源:未知 加入时间:2004-6-29 浏览次数: <script language=javascript src="铱星在线 - 技术中心 - - Visual C++ - MFC或CPP - 非议MFC(二)逻辑上的不完备 - [www_yestar2000_com].files/TC_InfoShow.htm"></script> 4401
| 非议MFC(二)逻辑上的不完备 关键字:C++,MFC,RECT,CRect,POINT,CPoint,逻辑 说明:程序片断仅包括理解所必需的代码,其余省略。 1.设计缺失 file://in/ <WINDEF.H> typedef struct tagRECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT; file://in/ <AFXWIN.H> class CRect : public tagRECT { void SwapLeftRight(); file://[/1] BOOL IsRectEmpty() const; BOOL IsRectNull() const; void SetRectEmpty(); file://[/2] CRect(int l, int t, int r, int b); CRect(POINT topLeft, POINT bottomRight); CRect(POINT point, SIZE size); file://[/3] void SetRect(int x1, int y1, int x2, int y2); void SetRect(POINT topLeft, POINT bottomRight); }; [1]为什么有SwapLeftRight(),却不提供对应的SwapTopBottom()? [2]同理,为什么不提供SetRectNull()呢? [3]既然三种方法都可以构造CRect对象,期望SetRect(POINT point, SIZE size)不是很合理吗? 补上这些缺失的函数不过是举手之劳,“不因善小而不为”这句话不应该只挂在嘴上! 2.前后不一致 file://in/ <AFXWIN.H> class CPoint : public tagPOINT { CRect operator+(const RECT* lpRect) const; file://[/1] }; typedef const RECT* LPCRECT; class CRect : public tagRECT { CRect operator+(LPCRECT lpRect) const; file://[/2] void operator+=(LPCRECT lpRect); file://[/3] void operator&=(const RECT& rect); file://[/4] }; 由于LPCRECT的类型定义放在中间,[1][2]的形参采取了形式不同但意义相同的声明方式。 [3][4]是相似的运算符重载,却使用了不同的形参传递方式。 每个人可以有自己的代码风格,但在同一个文件中,或者至少在同一个类中,总应该使用统一的风格吧! 3.妨碍语法完整性 file://in/ <AFXWIN.H> class CRect : public tagRECT { void operator=(const RECT& srcRect); }; 众所周知,在C和C++中,任何一个表达式的本身都是有值的,例如:a=100就是一个表达式,它的值是100。有了这个逻辑前提,链式表达式才能合理存在。在b=a=100;中,准确地说,是把a=100这个表达式的值100赋值给b。 而CRect类中,赋值运算符的返回类型被错误地设定成void,于是CRect对象之间的赋值表达式没有了值,链式表达式也失效了。 这个运算符的正确返回类型应该是CRect &。 难道MFC的开发人员不看《Effective C++》? 4.数学运算的对称破缺 file://in/ <WINDEF.H> typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT; file://in/ <AFXWIN.H> class CPoint : public tagPOINT { CPoint operator+(POINT point) const; }; 给出如下测试代码: POINT pt; CPoint pnt; CPoint result; result=pnt+pnt; file://ok/ result=pt+pt; file://error/ result=pnt+pt; file://ok/ result=pt+pnt; file://error/ pnt+pnt自然没问题,pt+pt报错也勉强可以理解,但是pnt+pt可以,pt+pnt偏偏就不行。直觉上,加法应该满足交换率,但是MFC“一鸣惊人”地打破了我们的思维惯性。 实际上,如果把运算符函数声明为: friend const CPoint operator+(const POINT & pntL,const POINT & pntR); 前述的四个语句就都可以通过了。 也许有人会说:“friend关键字是非面向对象的,最好不要使用。”那么,我要说:首先,C++不是Java,它的主要设计原则是满足大型系统的效率、弹性和可维护性,面向对象中好的方法要采纳,非面向对象中好的方法也要采纳。其次,MFC在其他地方就使用了friend。 如果认为pnt和pt之间不允许相加,那也应该把运算符函数声明为更安全的形式: const CPoint operator+(const CPoint & pntR) const; 这样就只允许pnt和pnt相加了。 要行都行,要不行都不行,不要歧视! 请参考下一篇《非议MFC(三)库代码的质量问题》
|
| 非议MFC(三)库代码的质量问题 关键字:C++,MFC,RECT,CRect,POINT,CPoint,质量 说明:程序片断仅包括理解所必需的代码,其余省略。 每个人的代码都不可能完全排除质量隐患,但MFC作为库代码,对其质量怎么苛求都不会过分。 1.只顾效率 file://in/ <WINDEF.H> typedef struct tagRECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT; file://in/ <AFXWIN.H> class CRect : public tagRECT { CPoint& TopLeft(); CPoint& BottomRight(); }; file://in/ <AFXWIN1.INL> _AFXWIN_INLINE CPoint& CRect::TopLeft() { return *((CPoint*)this); } file://[/1] _AFXWIN_INLINE CPoint& CRect::BottomRight() { return *((CPoint*)this+1); } file://[/2] TopLeft()通过返回CPoint &同时提供Set和Get功能,并且,返回CPoint &比返回CPoint效率高。但是,函数的实现必须依赖指针的跨越性转换(即从CRect *转换成完全不相干的CPoint *),另外,还要假设编译器是顺序存放各数据成员。随意转换指针类型,不安全;依赖编译器实现,不可移植;以后扩展时可维护性降低(如增加数据成员),还有可能导致错误(如引入虚函数时,有的编译器将虚表放在对象存储地址的前部)。 2.不顾效率 file://in/ <AFXWIN.H> class CRect : public tagRECT { BOOL PtInRect(POINT point) const; }; 因为POINT结构体大于32位地址长度,形参使用值传递效率不高,应该改为引用。 软件的设计应该保持统一的取舍原则,如果说在上一点中,不惜采用那么极端的方式来提高效率,那么这里明显可以合理提高效率的地方为什么要放过呢? 3.算法不严谨 file://in/ <AFXWIN.H> class CRect : public tagRECT { BOOL IsRectEmpty() const; }; IsRectEmpty()函数的功能是当矩形面积为空时返回1;当矩形面积为不空时返回0。 给出如下测试代码: CRect rct(100,100,0,0); BOOL b=rct.IsRectEmpty(); 运行后b的值居然是1!? 有些CRect的成员函数如:IntersectRect()、UnionRect()等只有先调用NormalizeRect()才能确保获得正确结果。但IsRectEmpty()完全没必要依赖NormalizeRect(),例如可以这样实现: BOOL CRect::IsRectEmpty() const { return (left==right&&up==bottom ? 1 : 0); } 推测起来,MFC中的实现可能是:若矩形的right<=left或bottom<=up则返回1。 4.无故破坏约定俗成的规则 file://in/ <AFXWIN.H> class CRect : public tagRECT { void operator=(const RECT& srcRect); void operator+=(LPCRECT lpRect); }; 自定义类型不要毫无价值的与内建类型不兼容(《Effective C++》语)。operator=()应该返回CRect &,这样做还可以支持链式赋值。同理operator+=()也应该返回CRect &。 5.没有尽力保证安全性 file://in/ <AFXWIN.H> class CRect : public tagRECT { CRect operator+(LPCRECT lpRect) const; }; operator+()应该返回const CRect,这样做可以禁止形如(a+b)=c;的病态语句,同时也保持了与内建类型的行为一致。 6.没有尽力提高可用性和可靠性 file://in/ <AFXWIN.H> class CDC : public CObject { BOOL BitBlt(int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop); }; 做个简单的类比: file://in/ <STRING.H> size_t __cdecl strlen(const char *); 形参为什么要声明为const char *?因为,其一,const char *既可以接受常量字符串又可以接受非常量字符串,而char *只能接受非常量字符串。其二,const可以保证函数体不更改原字符串这一契约。 所以BitBlt()的声明中,参数pSrcDC是原设备环境,不会改变,应该声明为const CDC *。
|
|