枫林在线论坛精华区>>程序设计 |
[38196] 主题: 非议MFC(一)宏和类型定义的困惑 |
作者: little (渺小) | ||
标题: 非议MFC(一)宏和类型定义的困惑[转载] | ||
来自: 61.151.*.* | ||
发贴时间: 2003年02月18日 13:17:07 | ||
长度: 1748字 | ||
有感于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 //... |
||
========== * * * * * ==========
|
作者: little (渺小) | ||
标题: 非议MFC(二)逻辑上的不完备 | ||
来自: 61.151.*.* | ||
发贴时间: 2003年02月18日 13:18:00 | ||
长度: 3384字 | ||
说明:程序片断仅包括理解所必需的代码,其余省略。
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:// BOOL IsRectEmpty() const; BOOL IsRectNull() const; void SetRectEmpty(); file:// CRect(int l, int t, int r, int b); CRect(POINT topLeft, POINT bottomRight); CRect(POINT point, SIZE size); file:// void SetRect(int x1, int y1, int x2, int y2); void SetRect(POINT topLeft, POINT bottomRight); }; 为什么有SwapLeftRight(),却不提供对应的SwapTopBottom()? 同理,为什么不提供SetRectNull()呢? 既然三种方法都可以构造CRect对象,期望SetRect(POINT point, SIZE s ize)不是很合理吗? 补上这些缺失的函数不过是举手之劳,“不因善小而不为”这句话不应该 只挂在嘴上! 2.前后不一致 file://in <AFXWIN.H> class CPoint : public tagPOINT { CRect operator+(const RECT* lpRect) const; file:// }; typedef const RECT* LPCRECT; class CRect : public tagRECT { CRect operator+(LPCRECT lpRect) const; file:// void operator+=(LPCRECT lpRect); file:// void operator&=(const RECT& rect); file :// }; 由于LPCRECT的类型定义放在中间,的形参采取了形式不同但意义相同的声 明方式。 是相似的运算符重载,却使用了不同的形参传递方式。 每个人可以有自己的代码风格,但在同一个文件中,或者至少在同一个类 中,总应该使用统一的风格吧! 3.妨碍语法完整性 file://in <AFXWIN.H> class CRect : public tagRECT { void operator=(const RECT& srcRect); }; 众所周知,在C和C++中,任何一个表达式的本身都是有值的,例如:a=10 0就是一个表达式,它的值是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+p nt偏偏就不行。直觉上,加法应该满足交换率,但是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相加了。 要行都行,要不行都不行,不要歧视! |
||
========== * * * * * ==========
|
作者: little (渺小) | ||
标题: 非议MFC(三)库代码的质量问题 | ||
来自: 61.151.*.* | ||
发贴时间: 2003年02月18日 13:20:18 | ||
长度: 3212字 | ||
说明:程序片断仅包括理解所必需的代码,其余省略。
每个人的代码都不可能完全排除质量隐患,但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:// _AFXWIN_INLINE CPoint& CRect::BottomRight() { return *((CPoint*)this+1); } file:// TopLeft()通过返回CPoint &同时提供Set和Get功能,并且,返回CPo int &比返回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()等只有先调用N ormalizeRect()才能确保获得正确结果。但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 &,这样做还可以支持链式赋值。同理op erator+=()也应该返回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* pS rcDC, 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 *。 请参考上一篇《非议MFC(二)逻辑上的不完备》 |
||
========== * * * * * ==========
|
返回 |