枫林在线论坛精华区>>程序设计
[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(二)逻辑上的不完备》


========== * * * * * ==========
返回