C++现代类型系统
类型转换
static_cast
static_cast用于代替传统C风格的类型转换,建议使用static_cast<T>(val)替换C风格的类型转换。
其功能相当于C风格的强制转换
编译时检查,一般用于非多态的转换
没有运行时类型检查来保证转换的安全性
1
2char ch = 'a';
int b = static_cast<int>(ch);相较于C风格的类型转换更加安全
1
2
3const int a = 1;
int* b = (int*)&a; // 编译通过
int* c = static_cast<int*>(&a); // 编译报错“Static_cast from 'const int *' to 'int *' is not allowed”
dynamic_cast
dynamic_cast通常用于类层次间的上行转换和下行转换。
类型萃取
(个人理解)C++的类型萃取特性type traits直译为“类型的特性”,其实现的功能也和提取类型的特性有关。而什么是类型的特性?如对于类型T,const T、T&、T&&都是类型T的“变种”。类型萃取是纯编译期进行的,不涉及对数据的修改。
需要引入<type_traits>头文件,类型萃取主要用于在编译期间获得操作类型相关的信息,一般用于在泛型编程以及在编译时做出决策。以下是一些常用的类型萃取:
std::is_integral<T>:判断类型 T 是否为整数类型。std::is_floating_point<T>:判断类型 T 是否为浮点数类型。std::is_pointer<T>:判断类型 T 是否为指针类型。std::is_reference<T>:判断类型 T 是否为引用类型。std::is_const<T>:判断类型 T 是否为 const 类型。std::is_same<T, U>:判断类型 T 和 U 是否相同。
这些类型萃取可以使用::value取出一个布尔值,当类型符合其条件时为true,否则为false。在函数模板或类模板中,我们可以借助这些布尔值对模板类型进行判断,并根据类型决定不同的执行逻辑。
当然,除了对类型trait的提取,此特性还能够”编辑“类型的trait,从而在进行模板编程时减少重复的代码,或避免错误。
举例:希望定义一个函数模板,并在函数中定义一个T类型的对象。下面的代码展示了不使用类型萃取带来的问题:
1 | template <typename T> |
使用类型萃取可以保证类型T不是一个引用类型,从而实现预期的逻辑,并规避这种错误:
1 | template <typename T> |
以下是一些常用的类型“traits”的编辑:
std::remove_reference<T>std::remove_const<T>- …
经典示例:std::move
std::move的功能是获得一个绑定到左值上的右值引用。
标准库对std::move的定义如下:
1 | template <typename T> |
示例分析
考虑以下代码中的std::move:
1 | string str_2; // Obviously `str_2` is lvalue |
该段代码中实例化的std::move函数定义如下:
1 | string&& move(string&&) {...} |
于是此std::move什么都不做,相当于str_2 = string("str 2");,显而易见。然而,考虑std::move传入左值的情况:
1 | string str_1("str 1"); |
此时实例化的std::move函数定义如下:
1 | // 类型T被推断为string& |
C++的引用折叠规则:
C++不允许直接创建引用的引用,如:
1
2
3
4
5 int a;
int& & b = a; // Error
int&& & b = a; // Error
int&& && b = a;// Error
// ...但是允许间接创建引用的引用,因为基于现有的模板语法,间接创建引用的引用不可避免
1
2
3
4
5
6
7
8 template <typename T>
void func(T&& ref) { ... }
// ^^^ 此处间接创建了int& && ref
void caller() {
int a;
int& b = a;
func<int&>(b);
}
- 于是规定
T& &、T& &&、T&& &被折叠为T&- 规定
T&& &&被折叠为T&&
即通过std::move将string&类型的t通过static_cast转为string&&;
- C++中允许显示地将左值引用转为右值引用
而将转为右值引用的str_1赋值给str_2时会调用string类的移动赋值函数(参数为T&&的operator=重载),区别于拷贝赋值,移动赋值不会对实际的字符串数据进行深拷贝,而只是让str_2中需要拷贝的字段指向str_1中的数据,同时将str_1置空。
值得注意的是,任何cast操作都不会导致operator=重载、拷贝构造函数或移动构造函数的调用,真正调用这些函数的一定是实际的构造或赋值,如:
1 | mytype_t obj_1("str 1"); |
1 | mytype_t obj_1("str 1"); |
1 | mytype_t obj_1("str 1"); |
1 | mytype_t obj_1("str 1"); |
经典示例:以二维方式访问一维数组
1 | int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; |
经典示例:从一个对象推导类型
1 | int arr_2d[3][3]; |