Trap of c++
前段时间春节假期,好好休息了一下,并且趁机复习了几本C++的书,包括 More Exceptional C++、Imperfect C++、Morden C++ Design 等等,这次看得比较快,大概3、5个晚上的时间就看完了,尽管外头的鞭炮声不时打扰,不过还是经常会有“想拍大腿的感觉”。正如 Herb Sutter 说的[sutter2001]那样:“第一遍看掌握基本知识,然后再看以学习细节,这样才能成为某一领域的专家”(含义,非原文)。
是否成为了专家自己还不能定论,但是我一直认为——c++越学越复杂。知道c++越多,就会越认为它复杂,所以其实我很怀疑绝大多数我收到的简历上面——“精通c++”这句话的水分 ;) 精通谈何容易啊。拿下面这个例子来说吧,从感情上说,我们几乎可以认为c++就是在这里面设置了一个陷阱。
- // 可以通过编译
- void a()
- {
- int a = 100;
- const short &b = a;
- a = 0;
- std::cout << "a:" << a << ",b:" << b << std::endl;
- }
这段程序的输出结果很违反我们的直觉——a:0,b:100,问题的原因在于 const short &b 实际上指向的是一个临时对象,并非是 a 。我用的编译器(msvc8)在缺省警告级别(W3)下对此毫无怨言,它默默的为我生成了一个临时对象并且做好其他的‘伪装’工作,警告级别设置为 W4 的时候它才提示这个转换可能会导致‘丢失数据’。
“编译器怎么能这么干呢!?”我也有过这样的疑问。但是想想也对,既然声明了 b 是个 const refrence,就相当于我告诉了编译器我不打算改动 b 的内容,那么即使编译器给了我一个临时对象而非原始对象 a ,也没问题呀!站在编译器的角度来看,这确实没问题。而且我用的编译器确实会用‘错误’阻止我写下面这样的东西:
- // 这是无法通过编译的
- void a()
- {
- int a = 100;
- short &b = a;
- a = 0;
- std::cout << "a:" << a << ",b:" << b << std::endl;
- }
也就是说编译器会阻止我对非 const refrence 做这种不合理的转型,它不会在这种情况下给我生成个临时对象。即使用下面这样的 static_cast 也不行。
short &b = static_cast<short&>( a );
那些讨厌 c++ “奇技淫巧”的 c 程序员,会提供下面这样的方案:
- // 可以通过编译
- void a()
- {
- int a = 100;
- short &b = *(( short* )( &a ));
- a = 11;
- std::cout << "a:" << a << ",b:" << b << std::endl;
- }
这次编译器不会抱怨了,而且它输出了 c 程序员想要的结果—— a:11,b:11 ,结果看上去非常好!但是代码只要稍微改动一下,事情就变得离谱了:
- // 可以通过编译,但是结果有问题
- void a()
- {
- int a = 100;
- short &b = *(( short* )( &a ));
- a = 32768;
- std::cout << "a:" << a << ",b:" << b << std::endl;
- }
这段代码会输出——a:32768,b:-32768 ,这差得很远,对吧?再改,a = 65537 的时候会输出——a:65537,b:1 。至此,我似乎可以得出结论:这种 c 转型方案很坏、很危险。可能这种方法只适合那些“知道他们自己在做些什么的 hacker ”,c 程序员。但是问题还没有结束,即使用纯 c++ 方法,问题也还存在。
- // 可以通过编译,使用纯 c++ 的方法
- void a()
- {
- int a = 100;
- const short &b = a;
- // c++ 提供了 const_cast
- short &c = const_cast<short&>(b);
- c = 1;
- std::cout << "a:" << a << ",b:" << b << ",c:" << c <<std::endl;
- }
在这个例子里面,b 是 a 的 refrence,c 和 b 是同一个东西,这段代码的输出结果很容易就被期望为:a:1,b:1,c:1,当然这不可能,实际上是:a:100,b:1,c:1 。根据这个输出结果,我们还能得出关于这种临时对象的另外一个结论——这种临时对象,也可以通过 refrence 而改变它的值,这个临时对象一定处于可写的内存区域。
就这样一个隐式(implicit)转换问题,就可以带来如此多的麻烦,我们很难做出“c++ 不复杂”这样的判断。c++ 里面违反直觉的例子数不胜数,例如 new int[0] 合法,但是 int array[0] 就非法,我们很容易就会认为编译器是在找茬打架。不过,强大的东西怎么可能不复杂?否则它早就消灭一切其他的编程语言了。
Post a Comment