C++ 20类型转换指南:使用场景与最佳实践

news/2024/7/7 20:02:07

C++ 20类型转换指南:使用场景与最佳实践

类型转换 (Casts)

C++ 提供了五种特定的类型转换:const_cast<>()static_cast<>()reinterpret_cast<>()dynamic_cast<>() 和 C++20 引入的 std::bit_cast<>()

请注意,旧的 C 风格类型转换(如 (int)myFloat)在 C++ 中仍然有效,并在现有代码库中广泛使用。C 风格的类型转换涵盖了所有四种 C++ 类型转换,因此它们更容易出错,因为您试图实现的目的并不总是显而易见的,可能会得到意外的结果。我强烈建议您在新代码中只使用 C++ 风格的类型转换,因为它们更安全,且在代码中更加突出。

虚拟基类

模糊基类出现在多个父类共有一个共同的父类时。推荐的解决方案是确保共享的父类自身不具有任何功能。这样,其方法永远不会被调用,从而避免了歧义问题。C++ 还有另一种机制,称为虚拟基类,用于解决您希望共享的父类具有自己功能的情况。

如果共享的父类是一个虚拟基类,则不会有任何歧义。以下代码在 Animal 基类中添加了一个 sleep() 方法,并修改了 DogBird 类,使它们作为虚拟基类从 Animal 继承。如果不使用虚拟基类,对 DogBird 对象的 sleep() 调用将是模糊的,并会生成编译器错误,因为 DogBird 将具有两个 Animal 子对象,一个来自 Dog,一个来自 Bird。然而,当 Animal 被虚拟继承时,DogBird 只有一个 Animal 类的子对象,因此调用 sleep() 不会有歧义。

class Animal {
public:
    virtual void eat() = 0;
    virtual void sleep() { cout << "zzzzz...." << endl; }
};

class Dog : public virtual Animal {
public:
    virtual void bark() { cout << "Woof!" << endl; }
    void eat() override { cout << "The dog ate." << endl; }
};

class Bird : public virtual Animal {
public:
    virtual void chirp() { cout << "Chirp!" << endl; }
    void eat() override { cout << "The bird ate." << endl; }
};

class DogBird : public Dog, public Bird {
public:
    void eat() override { Dog::eat(); }
};

int main() {
    DogBird myConfusedAnimal;
    myConfusedAnimal.sleep(); // 因为虚拟基类而不模糊
}

注意:虚拟基类是避免类层次结构中歧义的好方法。唯一的缺点是许多 C++ 程序员不熟悉这个概念。

类型转换 (Casts)

C++ 提供了五种特定的类型转换:const_cast<>()static_cast<>()reinterpret_cast<>()dynamic_cast<>() 和 C++20 引入的 std::bit_cast<>()。第一种在第 1 章中讨论过。第 1 章还介绍了用于某些基本类型之间转换的 static_cast<>(),但在继承的上下文中还有更多内容。现在您已经熟悉编写自己的类并理解类继承,是时候更仔细地看看这些类型转换了。

请注意,旧的 C 风格类型转换(如 (int)myFloat)在 C++ 中仍然有效,并在现有代码库中广泛使用。C 风格的类型转换涵盖了所有四种 C++ 类型转换,因此它们更容易出错,因为您试图实现的目的并不总是显而易见的,可能会得到意外的结果。我强烈建议您在新代码中只使用 C++ 风格的类型转换,因为它们更安全,且在代码中更加突出。

static_cast()

使用场景
  • static_cast() 用于执行语言直接支持的显式转换。例如,将 int 转换为 double 以避免整数除法:

    int i { 3 };
    int j { 4 };
    double result { static_cast<double>(i) / j };
    
  • static_cast() 也可用于执行因用户定义的构造函数或转换例程而允许的显式转换。例如,如果类 A 有一个接受 B 对象的构造函数,则可以使用 static_cast() 将 B 对象转换为 A 对象。

在继承中的应用
  • static_cast() 可用于继承层次结构中的向下转型:

    class Base { /* ... */ };
    class Derived : public Base { /* ... */ };
    
    Base* b { nullptr };
    Derived* d { new Derived{} };
    b = d; // 向上转型,不需要转换。
    d = static_cast<Derived*>(b); // 向下转型,需要转换。
    
注意事项
  • static_cast() 不执行运行时类型检查。可以将任何 Base 指针转换为 Derived 指针,即使 Base 实际上不是 Derived
  • static_cast() 不是万能的,它不能将一种类型的指针转换为另一种完全无关的类型的指针,也不能在没有转换构造函数的情况下直接将一种类型的对象转换为另一种类型的对象。

reinterpret_cast()

使用场景
  • reinterpret_cast()static_cast() 更强大但同时也更不安全。它用于执行 C++ 类型规则技术上不允许的某些转换。
  • 可以用来将一种类型的引用转换为另一种类型的引用,即使类型之间完全无关。
  • 常用于将指针类型转换为任何其他指针类型,包括将任何类型的指针转换为 void*
注意事项
  • 使用 reinterpret_cast() 要格外小心,因为它允许你在不执行任何类型检查的情况下进行转换。
  • 可以用 reinterpret_cast() 将指针转换为足够大以容纳它的整型类型,反之亦然。但是,尝试将 64 位指针转换为 32 位整数会导致编译错误。

std::bit_cast()

特点
  • std::bit_cast() 是 C++20 中引入的,定义在 <bit> 头文件中。
  • 它是标准库中唯一的类型转换,其他类型转换是 C++ 语言本身的一部分。
  • bit_cast() 类似于 reinterpret_cast(),但它创建一个给定目标类型的新对象,并将源对象的位复制到这个新对象中。它有效地将源对象的位解释为目标对象的位。
  • bit_cast() 要求源对象和目标对象的大小相同且都是平凡可复制的。
示例
float asFloat { 1.23f };
auto asUint { bit_cast<unsigned int>(asFloat) };
if (bit_cast<float>(asUint) == asFloat) {
    cout << "Roundtrip success." << endl;
}
应用场景
  • bit_cast() 的一个用例是用于平凡可复制类型的二进制 I/O。例如,可以将这些类型的单个字节写入文件,读取文件时,可以使用 bit_cast() 正确解释从文件读取的字节。

    平凡可复制类型通常具有以下特征:

    1. 无自定义析构函数:类型没有自定义的析构函数。
    2. 无自定义或虚拟构造函数:类型没有自定义的构造函数,也没有虚拟构造函数。
    3. 无虚函数和虚基类:类型不包含虚函数,并且不从虚基类继承。
    4. 可简单拷贝其状态:类型的所有成员可以通过简单的内存拷贝来复制,没有需要特殊处理的成员(如指针或动态分配的资源)。

dynamic_cast()

特点
  • dynamic_cast() 在继承层次结构中提供了运行时类型检查。
  • 它可以用于转换指针或引用。
  • 如果转换没有意义,dynamic_cast() 将返回空指针(对于指针版本)或抛出 std::bad_cast 异常(对于引用版本)。
示例
Base* b;
Derived* d { new Derived{} };
b = d;
d = dynamic_cast<Derived*>(b);

Base base;
Derived derived;
Base& br { base };
try {
    Derived& dr { dynamic_cast<Derived&>(br) };
} catch (const bad_cast&) {
    cout << "Bad cast!" << endl;
}
与其他类型转换的区别
  • static_cast()reinterpret_cast() 不同,dynamic_cast() 执行运行时(动态)类型检查,而后者即使转换错误也会执行转换。
  • 为了使用 dynamic_cast(),你的类必须至少有一个虚拟方法。如果类没有虚拟表(vtable),尝试使用 dynamic_cast() 将导致编译错误。

C++ 类型转换总结

情境推荐的转换方法说明
移除 const 属性const_cast()用于移除对象的 const 属性
语言直接支持的显式转换static_cast()例如,从 int 转换到 doublebool
用户定义的构造函数或转换支持的显式转换static_cast()用于用户定义的转换
一个类的对象转换为另一个(无关)类的对象bit_cast()用于无关类之间的对象转换
同一继承层次中的类的指针对象转换dynamic_cast() (推荐) 或 static_cast()用于继承层次中的指针对象转换
同一继承层次中的类的引用对象转换dynamic_cast() (推荐) 或 static_cast()用于继承层次中的引用对象转换
不相关类型的指针转换reinterpret_cast()用于完全不相关的指针类型之间的转换
不相关类型的引用转换reinterpret_cast()用于完全不相关的引用类型之间的转换
函数指针之间的转换reinterpret_cast()用于函数指针之间的转换

注意事项

  • 使用 const_cast() 应谨慎,因为它改变了对象的 const 性质。
  • static_cast() 是最常用的转换类型,适用于许多标准和用户定义的转换。
  • bit_cast() 用于位级别的类型转换,要求源和目标类型大小相同且都是平凡可复制的。
  • dynamic_cast() 在继承层次中提供运行时类型检查,但要求类至少有一个虚拟方法。
  • reinterpret_cast() 提供更广泛的转换能力,但也带来更高的风险,因为它不执行类型检查。

http://lihuaxi.xjx100.cn/news/1805469.html

相关文章

TableUtilCache:针对CSV表格进行的缓存

TableUtilCache:针对CSV表格进行的缓存 文件结构 首先来看下CSV文件的结构&#xff0c;如下图&#xff1a; 第一行是字段类型&#xff0c;第二行是字段名字&#xff1b;再往下是数据。每个元素之间都是使用逗号分隔。 看一下缓存里面存储所有表数据的字段 如下图&#xff…

记录联系ThinkPad T490扬声器无声音但插耳机有声音的解决办法

型号&#xff1a;联想ThinkPad T490&#xff0c;系统Win10 64位。 现象&#xff1a;扬声器无声音&#xff0c;插耳机有声音。且右下角小喇叭正常&#xff0c;设备管理器中驱动显示一切也都正常&#xff08;无黄色小叹号&#xff09;。 解决办法&#xff1a; 尝试了各种方法&a…

harmonyOS鸿蒙开发工具下载安装以及使用流程

注册账号 进入鸿蒙官方网站&#xff1a;https://www.harmonyos.com/ 推荐使用手机号注册 进行实名认证 下载开发环境 华为集成开发环境IDE DevEco Device Tool下载 | HarmonyOS设备开发 下载开发工具 HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 安装 无脑下一…

理财和银保区别

理财和银保在以下六个方面存在区别&#xff1a; 产品性质&#xff1a;银行理财是银行发行的理财产品&#xff0c;属于金融投资&#xff0c;主要投向债券、票据等固定收益类资产。银保产品是保险公司发行的保险产品&#xff0c;属于保障投资&#xff0c;除了固定收益类资产外&am…

Spring学习学习

Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spring学习学习Spri…

Linux--网络概念

1.什么是网络 1.1 如何看待计算机 我们知道&#xff0c;对于计算机来说&#xff0c;计算机是遵循冯诺依曼体系结构的&#xff08;即把数据从外设移动到内存&#xff0c;再从内存到CPU进行计算&#xff0c;然后返回内存&#xff0c;重新读写到外设中&#xff09;。这是一台计算机…

从键盘输入5个学生的信息(姓名、学号、成绩), 存入一个结构体数组中,计算平均分,并按成绩 高低排序并输出.

代码如下 #include<stdio.h> #include<string.h> #include<stdlib.h> /* 1.练习结构体数组排序   从键盘输入5个学生的信息&#xff08;姓名、学号、成绩&#xff09;,存入一个结构体数组中&#xff0c;计算平均分&#xff0c;并按成绩高低排序并输出. */…

风火编程--playwright爬虫

playwright爬虫基本用法 等待加载 page.wait_for_load_state(‘networkidle’) text page.content() 点击 demo with sync_playwright() as pw:browser pw.chromium.launch(headlessTrue)context browser.new_context()page context.new_page()page.goto(url)page.wait…