许式伟 ID:xushiweizh
420931次访问,排名110好友3人,关注者38
xushiweizh的文章
原创 125 篇
翻译 0 篇
转载 11 篇
评论 927 篇
许式伟的公告

本博客内容除非特殊说明均属原创,如需转载、引用其中的部分文字,请注意以下几点:

1)请在转载(引用)的内容开始添加本人署名,并提供本博客中相应文章的链接。如你的作品为非电子读物或纯文本,请给出链接的url。

2)请勿用于商业用途。

3)如果愿意,请给我邮件:xushiweizh@gmail.com,让我知道我的东西到哪去了。谢过。

重要链接


订阅

最近评论
yefeng_ok:非常期待C++0x标准的出台。
yefeng_ok:非常期待C++0x标准的出台。
LiYanRui:能不能把 free software 翻译为“自由软件”呢?

cairo 项目主页上很明确地说了:you should think of ``free'' as in ``free speech,'' not as in ``free beer.''
zhangyaoting196:

WWW.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!




ttkk1024:给老大顶一下
文章分类
收藏
相册
DocX预览图
Google vs. 百度
WinX相关
WINX团队
ebasil的专栏(RSS)
VisualFC/WINX专栏(RSS)
任风行(一路奔跑)(RSS)
绅士亦花心之WINX相关(RSS)
许伟群的专栏(RSS)
友情链接
QWL1996的专栏(RSS)
Sting的专栏(RSS)
SunHui的专栏(RSS)
不亦快斋(RSS)
于无声处(RSS)
手机开发论坛
珠穆朗玛(老汉)(RSS)
福&柯实验室(RSS)
存档
软件项目交易
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

原创 VC++ 6.0的小花招收藏

新一篇: C++内存管理变革 | 旧一篇: 人体发病的“红灯”信号

VC++ 6.0的小花招

 
许式伟
2006-2-18
 
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组件的代价。
 
附注:关于本文使用的C++模板的特化技术,详细请参阅C++文法方面的书籍,例如《C++ Primer》。其实这个技巧在C++标准库——STL中有一个专门的名字:traits(萃取),你可以在很多介绍STL的书籍中见到相关的介绍。

发表于 @ 2006年11月16日 21:21:00|评论(loading...)|编辑

新一篇: C++内存管理变革 | 旧一篇: 人体发病的“红灯”信号

评论

#xushiwei 发表于2006-11-16 21:22:00  IP: 219.131.196.*
这是我以前写的,好像网上有转载的了。
因为后面的文章引用到它,我自己再贴一遍。
#DraculaW 发表于2006-11-17 11:09:00  IP: 218.107.240.*
hehe VC6 打了补丁后 上面说的大部分问题都已经被解决了
#corejava 发表于2006-11-29 10:09:00  IP: 218.242.180.*
俺加了Intel C Plus,基本上还是蛮好用的,而且编译效果比VC自带的好很多。就是价格上去不少,,,,都是白花花的银子啊
发表评论  


当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
Csdn Blog version 3.1a
Copyright © 许式伟