以文本方式查看主题

-  计算机科学论坛  (http://bbs.xml.org.cn/index.asp)
--  『 C/C++编程思想 』  (http://bbs.xml.org.cn/list.asp?boardid=61)
----  非议MFC[转帖]  (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=52153)


--  作者:一分之千
--  发布时间:9/4/2007 8:38:00 AM

--  非议MFC[转帖]

非议MFC(一)宏和类型定义的困惑  

作者:cphj 时间:2001-10-06 09:58 出处:互联网 责编:My FAQ  

              摘要:非议MFC(一)宏和类型定义的困惑


                          非议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
//...




--  作者:一分之千
--  发布时间:9/4/2007 8:38:00 AM

--  
非议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相加了。
要行都行,要不行都不行,不要歧视!


--  作者:一分之千
--  发布时间:9/4/2007 8:39:00 AM

--  
非议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 *。


--  作者:gjymylover
--  发布时间:9/10/2007 9:21:00 PM

--  
未作深入研究 不看发表评论 XD
W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
2,373.047ms