• static_cast, dynamic_cast与reinterpret_cast的区别


    在C++中,static_cast, dynamic_castreinterpret_cast都可用于类型转换,它们在具体使用时有什么区别?此外,更为重要的是,为什么不推荐使用强制类型转换?

    1. static_cast

    static_cast是静态类型转换,“静态”一词是指在程序编译期间完成类型的转换,这应该是平时使用最多的类型转换。例如,将一个浮点数转化为整数,就可以使用static_cast:

    float a = 10.5;
    int b = static_cast<int>(a);

    2. dynamic_cast

    dynamic_cast是动态类型转换,“动态”一词是指在程序运行期间完成类型的转换,如果转换失败且转换的目标类型是指针,则返回一个空指针;如果转换失败且转换的目标类型是引用,则会抛出std::bad_cast异常。

    动态类型转换与C++的多态有关,常用于基类与子类指针或引用的转换,且基类中至少要有一个虚函数。例如:

    class Base { 
        virtual void f();
    };
    
    class Derived : public Base {
        void f() override;
    };
    
    
    Derived* ptr = new Derived();
    Base* ptrBase = dynamic_cast(ptr);

    当然,这个例子并没有很好地展示出dynamic_cast的功能,文章最后有一个例子展示了需要使用dynamic_cast的情形。

    3. reinterpret_cast

    reinterpret的意思是“重新解释”,它不会改变任何底层的数据,而是告诉编译器应该把当前数据当作哪种类型。例如,有一个指向整数的指针,你可以使用reinterpret_cast将其转化为一个指向浮点数的指针:

    int a = 10;
    float* ptrB = reinterpret_cast<float*>(&a);

    转换完成后,没有任何数据被改变,只是ptrB之后会被编译器当作一个指向浮点数的指针,这种感觉有点像C语言中的union. 可以看出,这种转换更偏向底层,使用时一定要小心。

    当然,既然存在这一转换,就一定有它的用处。例如,在OpenGL中,可以将一个指针传递到窗口:

    class App {
        ...
    };
    
    App* app1 = new App();
    glfwSetWindowUserPointer(app1);

    函数glfwSetWindowUserPointer的参数是void*类型的指针,可以通过函数glfwGetWindowUserPointer获取这个指针,获取到的指针类型也是void*,但我们已经知道它必然是一个App*类型的指针,此时可以使用reinterpret_cast将其转换回来:

    void* ptr = glfwGetWindowUserPointer();
    App* app1 = reinterpret_cast(ptr);

    4. 强制类型转换

    C语言中,可以通过(T)xx转换为类型T,C++中也支持这种写法,这种写法被称为强制类型转换。它有什么问题呢?请看下面这个例子:

     

    我们首先定义类Human

    class Human {
    protected:
        int mAge;  // 年龄
    
    public:
        virtual void say() {
            std::cout << "I'm a human.\n";
        }
    };

    这个类表示人类,它有一个成员mAge,表示人类的年龄;还有一个虚函数say, 此函数会输出一句话。

     

    接下来定义类Man, 这个类继承自Human类:

    class Man : public Human {
    public:
        Man(int age) {
            mAge = age;
        }
    
        void say() override {
            std::cout << "I'm a man.\n";
        }
    
        void howOld() {
            std::cout << "I'm " << mAge << " now.\n";
        }
    };

    这个类表示男人。其中,虚函数say被重载,此外还有一个成员函数howOld, 输出当前的年龄。

     

    最后定义类Woman, 同样继承自Human类:

    class Woman : public Human {
    public:
        Woman(int age) {
            mAge = age;
        }
    
        void say() override {
            std::cout << "I'm a woman.\n";
        }
    };

    这个类表示女人。它只重载了虚函数say, 并没有提供howOld方法(因为女性的年龄不会轻易告诉别人)

     

    我们在main函数中创建一个指向Woman对象的指针,并尝试通过指针访问howOld方法:

    int main() {
        auto pWoman = new Woman(32);
        pWoman->howOld();
    }

    这段代码应当会报错,因为Woman类没有howOld方法。以下是博主使用g++编译时输出的报错信息:

    error: 'class Woman' has no member named 'howOld'

     

    但如果我们将指针pWoman强制转换Man*类型的指针呢?代码如下:

    int main() {
        auto pWoman = new Woman(32);
        ((Man*)pWoman)->howOld();
    }

    令人诧异的是,这段代码可以正常运行,输出如下:

    I'm 32 now.

    使用强制类型转换后,竟然输出了女性的年龄,这真的是太糟糕了!

     

    我们不妨使用static_cast转换试试:

    int main() {
        auto pWoman = new Woman(32);
        (static_cast(pWoman))->howOld();
    }

    这段代码会在编译时报错:

    error: invalid 'static_cast' from type 'Woman*' to type 'Man*'

     

    再使用dynamic_cast试试:

    int main() {
        auto pWoman = new Woman(32);
        (dynamic_cast(pWoman))->howOld();
    }

    代码可以编译成功,但运行时会出错,因为类型转换失败,dynamic_cast会返回一个空指针。对返回值加以判断,程序就可以正常运行了:

    int main() {
        auto pWoman = new Woman(32);
        auto pMan = dynamic_cast(pWoman);
        if (pMan) {
            pMan->howOld();
        }
        else {
            std::cout << "convert failed.\n";
        }
    }

    运行后,程序输出如下:

    convert failed.

     

    最后使用reinterpret_cast试试:

    int main() {
        auto pWoman = new Woman(32);
        (reinterpret_cast(pWoman))->howOld();
    }

    编译后,程序输出如下:

    I'm 32 now.

     

    从这个例子中,可以看出不同类型转换方式的区别:强制类型转换会尝试不同类型的转换,有时会有潜在的bug. 将类型转换细分为不同种类,可以减少代码出错的可能。

    最后,展示一个使用dynamic_cast的例子:

    void howOldAreYou(Human* pHuman) {
        Man* pMan = dynamic_cast(pHuman);
        if (pMan) {
            pMan->howOld();
        }
        else {
            std::cout << "My age is a secret.\n";
        }
    }
    
    
    int main() {
        Man* p1 = new Man(23);
        Woman* p2 = new Woman(35);
        howOldAreYou(p1);
        howOldAreYou(p2);
    }

    程序输出为:

    I'm 23 now.
    My age is a secret.
  • 相关阅读:
    H5/CSS 笔试面试考题(101-110)
    Policy-Based Method RL
    需求管理-架构真题(三十四)
    IDEA 2019 Springboot 3.1.3 运行异常
    Leetcode第306场周赛(附数位DP总结)
    用DIV+CSS技术设计的体育篮球主题 校园体育网页与实现制作(web前端网页制作课作业)
    【Spring注解必知必会】深度解析@Component注解实现原理
    10.19作业
    关于Mac配置逆向工程
    既能够用ffmpeg命令做RTSP流转RTMP流,又可以像调用avcodec/avfilter库一样逻辑编程
  • 原文地址:https://www.cnblogs.com/overxus/p/17991592