当前位置: 首页 > 图文教程 > 开发语言 > VC++ > VC++ 6.0的小花招

VC++
在类VC的界面实现中加入目录树
软件换肤技术在 BCB 中的实现
利用非模窗口生成MDI介面
报表输出轻松搞定
Windows 中不规则窗体的编程实现
解说Win32的窗口子类化
使用测试优先方法开发用户界面
一个简单的登录对话框的实现
一个简单的日记本程序
从资源中加载皮肤
一个在RichEdit中添加表情图象的类
ActiveSkin 4.3 软件换肤在VC中的实现
一种另类“关于(About)”对话框的动态显示方法
对话框打印预览及打印
关于如何换肤、子类化的解决方案
制作 MSN、QQ 的消息提示窗口
如何对 BCGControlBarPro 进行换肤
定制个性化的对话框窗口类
改变窗口中的光标形状
更新MFC中的视图,跟踪.NET Framework中的事件

VC++ 6.0的小花招


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-08-14   浏览: 65 ::
收藏到网摘: n/a

Visual Studio系列中产品中,Visual Studio 6.0是最经典的一个版本,虽然后来有Visual Studio .NET 2003,以及2005,也确实添加了很多让我觉得激动的特性,但是从使用细节的细腻程度上来看,VS 6.0无疑是最棒的。我们一些同事甚至试图把2005的C++编译器独立的拿到Visual Studio 6.0中来用,也不愿意升级到.NET上来用,可见其魅力。
 
和VS 6.0这个产品的成熟相比,VC++ 6.0的编译器的的确确相对来说有些糟糕,其中最被诟病的是对模板技术支持很不好。下面我想做的一件事情,就是向那些继续留恋VC++ 6.0的朋友,介绍一些小花招,来避开VC++ 6.0的一些编译器缺陷。
 
 

1)for (type var=expression;;) 中变量var的作用域问题。

 
按照C++标准,这里定义的变量var出了for循环应该被销毁。也就是说下面这段代码是有效的:
 
   for (int i = 0; i < 100; ++i)
       func();
   for (int i = 0; i < 100; ++i)
       func2();
 
而下面这段代码应该编译不过:
 
    for (int i = 0; i < 100; ++i)
    {
         if (has_found_it())
         {
             handle_find_result();
             break;
          }
    }
    if (i == 100)
         do_not_found();
 
然而VC++ 6.0对于第一段代码会报变量i重复定义错误,而第二段代码编译通过。
 
为了让VC++ 6.0的for语句看起来符合C++标准,你可以这样做:
 
   #define for if (0); else for
 
你会发现很有趣,这样define一下后,VC++ 6.0的for语句完全符合C++标准了!而且由于编译器的优化,Release版本不会增加任何额外的开销。
 
喜欢“钻牛角尖”的朋友可能会说:嗯,不错的主意。但是——为什么不这样做:
 
   #define for if (1) for
 
嗯?看起来也可以。还是让我们看一个用例:
 
   if (cond)
      for (int i = 0; i < 100; ++i)
          func1();
   else
       func2();
 
进行宏代码展开后,成为:
 
   if (cond)
       if (1)
           for (int i = 0; i < 100; ++i)
               func1();
       else
           func2();
 
这个结果显然不能符合我们的原意。这里func2();语句永远得不到执行机会。
 
 

2)模板参数类型如果不出现在参数列表中,则不能作为返回值类型。

 
由于编译器的缺陷,VC++ 6.0不支持以下这种用法:
 
   template <class T1, class T2>
   T1 func(T2 arg)
   {
       T1 var;
       ... // 处理var过程
       return var;
   }
 
   void test()
   {
       int result1 = func<int>(1);
       double result2 = func<double>(2);
   };
 
很抱歉,这种用法VC++ 6.0不支持。让人恼火的是,VC++ 6.0编译时不会提示错误,但是生成的执行代码却很成问题。
 
究其原因,是因为VC++ 6.0的template技术是在编译器的较高层次做的,真正的编译器核心并不考虑模板。以上面的代码为例,对编译器核心来说,只是有两个重载函数而已:
 
   int func(int arg);
   double func(int arg);
 
如果是普通情况,只是返回值不同的函数,是不能同时存在的,编译器应该认为这是一个错误。但是很在模板情况下,这两个函数被简单认为是同一个函数。因为VC++ 6.0会为每个函数根据它的:
   1)所在的namespace;
   2)所在的类的类名(如果是成员函数);
   3)函数名;
   4)函数调用方式(cdecl、stdcall还是fastcall);
   5)所有参数的类型;
而生成一个唯一标识该函数的函数名。这个过程叫Name Mangling,是所有C++编译器都要进行的工作。而另一个背景是,很多C++编译器生成的目标文件(.obj文件)有一些和模板相关的特殊信息,包括也标识了某个函数是否模板函数。这是因为一个模板函数在多个源文件(.cpp文件)中被调用的话,这个模板函数就会在这些源文件编译生成的目标文件(.obj文件)中都定义(definition)一份。为了支持模板,link程序显然必须知道这个函数是模板函数,从而随意选择一个定义(丢弃其余的定义),而不是报符号重复定义错误。
 
因为函数名、参数列表等完全一致,所以这两个函数Name Mangling后生成的名字是一样的,并且,它们都被标识为这是模板函数。从而,link程序在工作的时候,简单地将其中一个函数定义给抛弃了。
 
那么,如果我们非要提供上述的func函数,怎么办?我们来点花招:
 
template <class T1>
class func
{
private:
    T1 var;
 
public:
    template <class T2>
    func(T2 arg)
    {
       ... // 处理var过程
    }
    operator T1() const
    {
        return var;
    }
};
 
我们再来使用func这个“函数”:
 
   void test()
   {
       int result1 = func<int>(1);
       double result2 = func<double>(2);
   };
 
呵呵,你会发现,它还真象是你期望的正常工作。
 
 

3)仿真VC++提供的关键字__uuidof。

 
这个技巧不是针对VC++ 6.0缺陷的,而是针对VC++扩展语法的。这个技巧的来由,是为了某些希望有一天有可能要脱离Visual C++环境进行开发的人员。为了脱离VC++,你需要谨慎使用它的所有扩展语法。例如本文讨论的__uuidof。我们先来看看一个例子:
 
class __declspec(uuid("B372C9F6-1959-4650-960D-73F20CD479BA")) Class;
struct __declspec(uuid("B372C9F6-1959-4650-960D-73F20CD479BB")) Interface;
 
void test()
{
   CLSID clsid = __uuidof(Class);
   IID iid = __uuidof(Interface);
   ...
}
 
这比起你以前定义uuid的方法简单多了吧?可惜,这样好用的东西,它只在VC++中提供。不过没有关系,我们这里介绍一个技巧,可以让你在几乎所有C++编译器中都可以这样方便的使用__uuidof。这里没有说是所有,是因为我们使用了模板特化技术,可能存在一些比较“古老”的C++编译器,不支持该特性。
 
也许你已经迫不及待了。好,让我们来看看:
 
#include <string>
#include <cassert>
 
inline
STDMETHODIMP_(GUID) GUIDFromString(LPOLESTR lpsz)
{
    HRESULT hr;
    GUID guid;
    if (lpsz[0] == '{')
    {
        hr = CLSIDFromString(lpsz, &guid);
    }
    else
    {
        std::basic_string<OLECHAR> strGuid;
        strGuid.append(1, '{');
        strGuid.append(lpsz);
        strGuid.append(1, '}');
        hr = CLSIDFromString((LPOLESTR)strGuid.c_str(), &guid);
    }
    assert(hr == S_OK);
    return guid;
}
 
template <class Class>
struct _UuidTraits {
};
 
#define _DEFINE_UUID(Class, uuid)                                        \
template <>                                                              \
struct _UuidTraits<Class> {                                              \
    static const GUID& Guid() {                                          \
        static GUID guid = GUIDFromString(L ## uuid);                    \
        return guid;                                                     \
    }                                                                    \
}
 
#define __uuidof(Class)    _UuidTraits<Class>::Guid()
 
#define DEFINE_CLSID(Class, guid)                                        \
    class Class;                                                         \
    _DEFINE_UUID(Class, guid)
 
#define DEFINE_IID(Interface, iid)                                       \
    struct Interface;                                                    \
    _DEFINE_UUID(Interface, iid)
 
 
这样一来,就已经模拟出一个__uuidof关键字。我们可以很方便进行uuid的定义。举例如下:
 
DEFINE_CLSID(Class, "{B372C9F6-1959-4650-960D-73F20CD479BA}");
DEFINE_IID(Interface, "{B372C9F6-1959-4650-960D-73F20CD479BB}");
 
void test()
{
   CLSID clsid = __uuidof(Class);
   IID iid = __uuidof(Interface);
   ...
}
 
在VC++中,为了与其他编译器以相同的方式来进行uuid的定义,我们不直接使用__declspec(uuid),而是也定义DEFINE_CLSID, DEFINE_IID宏:
 
#define DEFINE_CLSID(Class, clsid)           \
    class __declspec(uuid(clsid)) Class
 
#define DEFINE_IID(Interface, iid)           \
    struct __declspec(uuid(iid)) Interface
 
这样一来,我们已经在所有包含VC++在内的支持模板特化技术的编译器中,提供了__uuidof关键字。通过它可以进一步简化你在C++语言中实现COM组件的代价。