分类 技术志 下的文章

C++咬文嚼字-'Evil cast'

Cast也被称为"Explicit Type Conversion",即显式类型转换,在传统C中强制转型(cast)只有一种语法形式(T)e。Bjarne Stroustrup在'The Design and Evolution of C++'(以后称作D&E)一书的14.3小节开始就说了'无论是从语法还是从语义上, Cast都是C++里最难看的特征之一',所以他要为cast提供A New Cast Notation.

旧式的cast有诸多问题,在D&E一书中有描述,这里摘录一些:
首先记法(T)e单一,语义上容易引起错误,使人们很难理解代码的真实意图,几乎每种类型组合都有某种合法的解释:
如:const X* px = new X(); 
    py = (Y*)px;   //这里很难明确是去掉const,还是取得基类访问权或者是强制转型为其他unrelated类型的指针

其次(T)e记法上过渡使用'()',这使得在代码里难辨别是否为cast,并且很难用grep这样的搜索工具快速检索定位。

Bjarne Stroustrup引入几种cast operators实际上是对传统cast的一个细致分类,而且提供的这几种cast operators的功能的总和和传统cast功能一致,这也为C++程序员完全不使用传统cast提供了充分的理由。Scott Meyers在其'More effective c++'的item2中就明确说'Prefer C++ style casts'。新的cast operators使用了长名字和类似于模版的使用语法,也便于工具检索。

C++提供四种新式cast operator:
static_cast<T>(e)
dynamic_cast<T>(e)
reinterpret_cast<T>(e)
const_cast<T>(e)

我将这些C++ style cast支持常见的cast都融入到了下面这个例子中,慢慢体会吧:

#include <iostream>

class Base {
        public:
                int     _x;
                virtual int func() {;}
};

class Derived : public Base {
        public:
                int     _y;
};

void print(char *str) {
        std::cout << str << std::endl;
}

class T;
class S;

void test_reinterpret_cast(T *pt) {
        S *ps = reinterpret_cast<S*>(pt);
}

int main() {
        void    *buf    = 0;

        //for static_cast
        double  d       = 2007.02;
        int     i       = 0;
        char    c       = 'A';
        i = static_cast<int>(d);        //ok, static_cast支持non-pointer类型的conversion
        std::cout << i << std::endl;

        i = static_cast<int>(c);        //ok, static_cast支持non-pointer类型的conversion
 std::cout << i << std::endl;

        int     *p      = 0;
        buf = operator new(100);
        p = static_cast<int*>(buf);     //ok, static_cast支持从void*到任意指针类型
        (*p) = 2008;
        std::cout << *p << std::endl;

        Base    *pb     = new Derived();
        Derived *pd     = static_cast<Derived*>(pb); //ok, static_cast利用静态类型信息完成类层次间的转型
        pd->_y          = 2009;
        pd->Base::_x    = 2010;

        std::cout << pb->_x << std::endl;
        std::cout << pd->_x << std::endl;
        std::cout << pd->_y << std::endl;

        pb      = new Base();
        pd      = static_cast<Derived*>(pb); //ok, but pd is trivial and it is very dangerous to use it.
        //pd->_y = 2011; //dangerous, may cause crash

        //for const_cast
        const int       k = 2012;
        //int           m = const_cast<int>(k); //error: const_cast不支持non-pointer类型的conversion

        const char* str = "hello";
        //print(str);   //error: invalid conversion from `const char*' to `char*'
        print(const_cast<char*>(str));

        //for dynamic_cast ,
        //Base class should be a polymorphic class, otherwise the dynamic_cast operator will prevent you from doing cast

        pb      = new Derived();
        pd     = dynamic_cast<Derived*>(pb);
        if (pd) {
  std::cout <<"downcast ok\n"; //we will see this
        } else {
                std::cout <<"downcast error\n";
        }

        pb      = new Base();
        pd     = dynamic_cast<Derived*>(pb);
        if (pd) {
                std::cout <<"downcast ok\n";
        } else {
                std::cout <<"downcast error\n"; // we will see this. dynamic_cast会在运行时利用运行时类型信息判断动态类型到底
是不是一个真实的Derived Object.
        }

        //for reinterpret_cast
        char    ch      = 'A';
        char    *pch    = &ch;
        //int   h       = reinterpret_cast<int>(ch); // reinterpret_cast不支持这种non-pointer conversion
 &nb
sp;      int   h       = reinterpret_cast<int>(pch); //ok, reinterpret_cast支持这种将指针值转换成整数的conversion
        std::cout << h << std::endl;
        int     *ph     = reinterpret_cast<int*>(h); //ok, reinterpret_cast支持这种将任意整型数转换成地址的操作
        //std::cout << *ph << std::endl; //may cause crash

        //see test_reinterpret_cast function的定义, reinterpret_cast can
        //converts any pointer type to any other pointer type, even of unrelated classes.
        //and reinterpret_cast不需要知道类型的定义, 正如test_reinterpret_cast涉及到的
        //M和T,在这里仅仅是两个forward declaration

}

Cast毕竟还是cast,依旧是很多Evil的源头,尽管C++做了细化和改善还是尽量少用的好,尽量减少cast的操作。总体来说Bjarne Stroustrup以及C++委员会的工作还是很有意义的。减轻罪恶(evil)就是造福,不是吗?^_^

C++咬文嚼字-'0 or NULL'

C程序员和C++程序员在声明空指针时做法常常是不相同的。
C程序员常常如下做:
int *ptr = NULL;

C++程序员则是听从Bjarne Stroustrup或者其他C++大师的教诲,坚定地如下做:
int *ptr = 0;

也许没有谁对谁错之分,也许只是习惯不同罢了,毕竟C语言是老大哥,诞生的早;而在早期C编程时人们也许不习惯在程序里使用0这样的magic number,转而使用了#define NULL ((void*)0)来统一进行空指针的声明或者赋值。

在'Effective C++'中明确提出避免使用使用macro的issue,广大C++信徒自然也就将NULL抛掷脑后,并逐渐形成习惯,用0给指针赋值以意会这是个空指针的方式就流传了下来。

还是那句话没有谁对谁错,在'The C++ Programming Language Special Edition'中Bjarne Stroustrup在5.1.1小节用了不到200个words来说明了关于'0'或NULL的问题,这段叙述也是堪称经典,我们可以来回顾一下:

Zero(0) is an int. Because of standard conversions, 0 can be used as a constant of any integral, floating-point, pointer, or pointer-to-member type. The type of zero will be determined by context. Zero(0) will typically (but not necessarily) be represented by the bit patternall-zerosof the appropriate size.No object is allocated with the address 0 . Consequently, 0 acts as a pointer literal, indicating that a pointer doesn’t refer to an object.

0是一个整型数,通过标准的转型操作,0可以被用作各种数据类型常量,这些数据类型包括整型、浮点型、指针型或者指向类成员的指针类型。这时这个常量0的类型需要通过上下文才能判断出来。0通常(但不是必要的)用特定大小的全二进制0的bit串表示。没有object会被分配到0地址上,0只是字面值,其含义是这个指针变量没有指向(参考到)任何object。

举例:
int i = 0; //整型  
long l = 0; //整型  
float f = 0; //浮点型
double  d = 0; //浮点型

int *p = 0; //整型指针
double  *dp = 0; //浮点指针

class T {
 public :
                int func(int a){…};
};

T *pT = 0; //用户自定义类型指针
int (T::*PTR)(int) = 0; //指向类成员的指针类型

Bjarne Stroustrup继续说明了C与C++程序员习惯上的差异并给出了自己的建议:
In C, it has been popular to define a macro NULL to represent the zero pointer. Because of C++’s tighter type checking, the use of plain 0, rather than any suggested NULL macro, leads to fewer problems. If you feel you must define NULL, use const int NULL = 0;
The const qualifier prevents accidental redefinition  the NULL and ensures that NULL can be used where a constant is required.

在C中,定义一个macro NULL代表空指针是很流行常见的做法。由于C++编译器更严格的类型检查,使用0比使用NULL macro给你带来的麻烦更少。如果你一定要用NULL,那么建议作如下定义: const int NULL = 0; 这行定义会阻止意外的重定义NULL,而且会保证NULL在一个需要常量的场合被使用。

由此看来,在C++中0的灵活性和适应性更强一些,至于到底用哪个还是个见仁见智的问题,谁也不能强迫谁^_^。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats