C++11

本文根据《C++11/14高级编程、Boost程序库探秘第三版》总结了一些C++11中的特性。

第一章C++11新增语法

左值与右值

左值、右值、失效值、广义左值、纯右值

  • 左值:左值是一个可以用来存储数据的变量,具有世界的内存地址表达式结束后依然存在,如string s = “abc”,其中s是一个左值
  • 右值:它是一个临时的变量,它在表达式结束时生命期终止,不能存放数据,如string s = "abc"中,"abc"就是一个右值,它在此语句结束后就不存在了;右值不能使用&,如int x = 1; int * p = &(x++)中不能使用&进行取地址,因为x++返回的是临时变量。
  • 右值引用:因为右值是即将消失的,通过右值引用来对右值添加一个“临时的名字”,延长生命周期,消除昂贵的拷贝代价,右值引用使用T&&。如int&& p1 = x++,可以对x++返回的临时值进行右值引用。
  • 在C++11标准头文件<utility>定义了函数std::move(T&& t)来将一个对象转换为右值引用。它是一个模板函数。
  • 转移语义:因为右值对象是临时的,通过将右值对象进行转移来优化代码。在对对象采用std::move操作后,将对象变为右值引用,就可以对该对象进行转移了。因此可以对类moveable添加转移构造函数moveable(moveable&& other)和转移赋值函数,这两个函数对应于拷贝构造函数(moveabel(moveable& other))和赋值运算符(operator=),以转移构造函数和拷贝构造函数为例(这里是个人理解,书上没有明说):
    moveable m1;
    moveable m2(m1);
    这里调用拷贝构造函数,执行完后具有两个对象m1和m2,m1和m2对象属性相同;
    moveable m1;
    moveable m3(std::move(m1));
    这里调用转移构造函数,执行完后m1转移到了m3上,而原来m1将不知道是啥,m3的属性就是m1原来的属性,但是m1的属性就不知道了。我觉得移动构造函数的好处在于当传入的是右值时,充分利用了原来的右值,而不用再次进行拷贝而产生新的临时对象。
    拷贝构造函数和转移构造函数我觉得在构造对象的效率上是一样的,因为中间都不存在拷贝对象的过程,只不过一个使用的是左引用,一个使用右引用。移动构造函数可以利用右值对对象进行构造,如一个函数返回一个临时对象,并使用这个临时对象去构造另一个对象,则此时就必须使用转移构造函数,因为拷贝构造函数采用的是左引用。
  • 完美转发:标准头文件<utility>中函数std::forward();可以将实参原封不动的传递给另一个函数;(我感觉forward函数的作用在于告知被调函数实参的是左引用还是右引用,特别实在被掉函数具有左引用和右引用两种重载类型时。感觉forward价值不大,因为根据引用折叠规则,实参的类型也会被保留下来)
  • 引用折叠:在move()和forward()中使用了,是指当形参是实参类型不一样时如何处理;当形参是右值引用时,会保持实参的类型不变,即当传入左值时,在函数内会当做左值引用使用,当传入右值时,会当做右值引用使用。
    如函数foo(T&& v)
    T x;
    foo(x)和foo(std::move(x))两种调用,前面传入的是左值,实际当做左值引用,而后面传入的是右值,实际当做右值引用。
    问题:区分参数是左值引用还是右值引用有什么用,生命期都是一样的,函数内用法也一样(我感觉在用法和作用上区分右引用和左引用是没有用了,右引用的提出是为了充分利用临时变量这种右值来减少数据拷贝,但是右引用和左引用在使用上我暂时找不出差别)
自动类型推导
auto

自动类型推导,对于一些编译器知道的类型让编译器去推导;如auto s = “abc”,编译器知道"abc"的类型是string(也是因此编译器才会在编译时判断程序员的类型定义对错)。但有几点需要注意:

  • auto只能用于类型推导,不能用于变量定义,如auto x;这样的语句就不行
  • auto总是推导出的是指类型(非引用)
  • auto允许使用"const/volatile/&/*"等修饰,从而得到新的类型,如auto& s = "abc"得到的是string&;
  • auto&&总是推导出引用类型。
  • 在C++14中auto可以使用在返回值中,如auto fun(int x){return x*x;}
decltype

它是一个C++关键字,用法与sizeof相似,返回参数的,它是类型在编译期间由编译器计算返回类型。如int x = 0; decltype(x) d = x;则此时d的类型为int型。decltype不仅可以用在赋值语句中,还可以用在变量声明、类型定义、函数参数列表、模板参数列表等任意的地方。decltype用法与auto类似,有如下注意:
int x = 0;

  • decltype(e)的形式获取的是表达式计算结果的值类型;如decltype(x) d1;d1的类型为int
  • decltype((e))的形式获取的是表达式计算结果的引用类型,类似auto&&的效果;如decltype((x)) d2;d2的类型为int&;
面向过程编程
空指针

C++11中定义了nullptr来表示空指针,它是一个关键字,C语言中使用宏定义#define NULL 0来表示空指针,实际是一个整数,在某些时候会造成二义性。
nullptr并不是指针,它是一个强类型,与int或void*等不同,它是一个nullptr_t类型的编译期常量对象实例,不是指针,只是行为像指针。nullptr_t的定义为:typedef decltype(nullptr) nullptr_t,其中nullptr是一个关键字;因此可以这样自己定义一个空指针nullptr_t nil;而asert(nil==nullptr)为真。

初始化

C++11中统一使用"{}"进行初始化变量,称为“列表初始化”,例如:
int x{0};(原来使用int x = 0;)
string s{“abc”};
这种方式也可以初始化数组和容器
int a[] = {1,2,3};(这不就是常用的方式吗)
vector<int> v = {1,2,3};
也可以直接使用“{…}”作为值返回,类型会自动推导;
set<int> get_set(){ return {1,2,3};}
实际上花括号形式的语法会生成一个类型为std::initializer_list的对象。

新式for循环

遍历C++內建数组或容器(此种容器必须具有begin()和end()成员函数)
int a[] = {1,2,3,4,5};
for(auto x:a){ cout << x << endl; }使用auto自动推导x的类型,x与容器a之间使用":"分隔。
这种for循环实际就是使用容器的迭代器来遍历容器,不过是编译器去帮组完成迭代器的展开。

新式函数声明

将函数返回值的类型推后表示,形式如下:
auto func(…)->type{…};这里的auto并不具有自动类型推导的功能,只是用来占位,而"->type"才真正用来表示返回值类型,这里type可以是任意类型,包括decltype,如auto func(int x) -> decltype(x){return x*x};
这种新式函数声明用法怪异,但是在泛型编程中,返回值的可能类型需要有实际的参数来确定,所以有必要将返回值类型的声明“延后”。这种用法如在lambda表达式中使用。

面向对象编程
default

C++11中增加了对默认构造函数、析构函数、拷贝构造函数等缺省特殊成员函数的操作,允许显示的声明默认构造函数等。用法与纯虚函数类似,在函数后面使用=default。

delete

增加delete的功能,用于禁用某些函数,通常是类的构造函数和拷贝构造函数,以阻止对象的拷贝,但也可以用于普通函数,禁用某些形式的重载,用法是在函数后面使用=delete。

override

在虚函数重载中使用,用于表示derived类中某个函数是重载自base类的虚函数(这样做的原因在于让阅读者更加明白哪些函数是继承自base类,哪些函数是derived类自己的,避免因函数原型写错导致的将base类虚函数覆盖的情况),用法如下:
void f() override{};
override在函数名后,并且函数f必须是virtual的且函数声明也必须和base类中声明一致。override不是关键字,除在成员函数后有特殊含义外,在其他地方可以作为函数名或变量名,但最好不要这样做。

final

final用于控制类或者虚函数,同样final也不是关键字,仅在类声明中有特殊含义。

  • 在类名后面使用,显示地禁止类被继承。
  • 在虚函数后面使用,显示的禁止该函数在子类中再被重载。
    final可以和override混用,更好的标记类的继承体系和虚函数。
成员变量初始化

C++11中放松了对类成员初始化的要求,允许成员在声明时初始化。如:
class demo{
public :
int x = 0;
vector <int> v{1,2,3};
}

  • 这种方式对于静态成员变量不适用,仍然要在类定义之外初始化(因为需要分配实际且唯一的存储空间)
  • 这种赋值初始化的形式不能使用auto来推导变量类型。(为什么?我认为这种初始化方式是在构造函数调用前先调用了初始化语句,或者说由编译器将初始化语句写入了构造函数,应该与auto没关系的啊!)
委托构造

在构造函数中大多会出现一些重复的操作,解决办法就是另写一个函数(init)进行这些重复操作,然后再让构造函数调用该init函数。C++11引入了委托构造函数,不用专门写一个特殊的初始化函数,而是可以直接调用本类的其他构造函数。如下demo类的构造函数:
demo():demo(0,0){}
demo(int a):demo(a,0){}
demo(int a, int b){…}
委托构造函数作用在于简化代码。

泛型编程
类型别名

C++11扩展了关键字using的能力,可以完成与typedef相同的工作。如using int64 = long与typedef long int64作用相同,都是新定义类型int64。但using能力又超越了typedef,它可以结合template关键字为模板类声明“部分特化”的别名。如:
template<typename T>
using int_map = std::map<int,T>;部分特化map模板,固定key为int。

编译期常量

const限定的变量为运行时常量,C++11新增关键字constexpr,它修饰的表达式或函数具有编译期的常量性。如constexpr int kk = 1024;
constexpr long giga(){ return 100010001000; }
constexpr让编译期的整数计算成为了可能。

静态断言

C语言提供的assert断言是一个宏,在运行时验证某些条件是否成立。C++11新增关键字static_assert,它是编译期断言,用于在编译期验证某些条件的正确性,如果static_assert判断为false,则会在编译期报错(message),static_assert(condition,message),用法与assert类似。

可变参数模板

C++11为更好的支持模板元编程,引入了可变参数模板,与可变参数函数一样,使用"…"来声明不确定数量的参数。形式如下:
template<typename … T> class some_class{};这是模板类的声明,模板函数的声明与之类似。

  • 使用sizeof…(T)得到具体参数的数量。
  • 直接在类型名后使用"…"解开参数包,如:
    template<tpename … T>
    class variadic_class{ using type = x<T…>; };
  • 若具有函数:
    template<typename … Args>
    int print(const char* format, const Args& … args)
    {return printf(format,args…);//解包参数并转发}
    其中Args可以看做是参数类型的包,args可以看做是参数类型所对应的变量的包。
函数式编程

函数式编程将计算过程视为数学函数的组合运算,可以把函数当做输入或输出,主要思想是把运算过程尽量写成一系列嵌套的函数调用。

lambda表达式

lambda表达式实际上是函数对象的一种强化和扩展,可以直接定义“匿名”的函数对象。定义形式如:[] (params) {},与一般的函数定义比较像,"[]"称为lambda表达式引出操作符,圆括号中为参数,花括号中为函数体。lambda表达式的返回值会自动推导,也可以使用新的返回值后置语法,如[] (params) -> return_type {}。

  • lambda表达式不同于函数对象有类型,它的类型无法直接写出,所以通常使用auto来推导。如
    auto f1 = [] (int x) { return x*x;};
  • lambda表达式的行为类似于函数对象,可以用operator()来调用,也可用于各种标准算法如for_each();如下调用:
cout << f1(5) << endl;//结果打印25;
vector<int> v = {1,2,3,4,5};
for_each(v.begin(), v,end(), [] (int x) {cout << x << ",";});
捕获外部变量

lambda表达式与普通函数或函数对象最大的不同之处在于可以“捕获”表达式外部作用域的变量并在表达式内部使用。lambda表达式的完整声明语法是:

[capture](params)mutable->type{...}

其中[capture]是捕获列表,可以捕获多个捕获项,使用都好分隔,规则如下:

  • [ ]:无捕获,函数体内不能访问任何外部变量
  • [=]:以值(拷贝)的方式捕获所有外部变量,可以访问但不能修改
  • [&]:以引用方式范文所有外部变量,可以访问也可以修改(需注意无效引用,因为外部变量可能被释放了)
  • [var]:以值(拷贝)的方式方位外部变量var,可以访问但不能修改
  • [&var]:以引用的方式方位外部变量var,可以访问也可以修改
  • [this]:捕获this指针,可以访问类的成员变量和成员函数
  • [=,&var]:引用捕获变量var,其他外部变量使用值捕获
  • [&,var]:值捕获变量var,其他外部变量引用捕获
    lambda表达式可以使用mutable关键字修饰,表示对于使用值捕获方式的变量可以修改,但是修改只在lambda函数内部有效,不影响外部变量,因为它是使用拷贝方式捕获如lambda表达式的。
类型转换

C++11允许将无捕获的lambda表达式(也就是[]中为空,这样的lambda表达式没有内部状态,相当于“纯函数”)转换为一个签名相同的函数指针,这条规定实现了lambda表达式与C接口的互操作性。类型转换例如下:

auto f = [](){};//无捕获的lambda表达式
typedef void (*func)();//函数指针类型
func p1 = f;//隐式转换为函数指针
func p2 = [](){cout << endl;};//直接赋值给函数指针
泛型的lambda表达式

C++14中为lambda表达式增加了泛型的功能(相当于模板化的函数对象)。泛型的lambda表达式语法上并没有使用template关键字和"<>",而是使用auto来声明参数类型,虽说语法不同,但目标参数推导规则是相同的。

auto f = [](auto x){...};
并发编程

C++11主要以库的方式为并发编程提供支持,包括<atomic>,<mutex>,<thread>等。
C++11中还引入了关键字thread_local,用法与static、extern一样,用于修饰变量,表示该变量为线程本地存储,即每个线程都有自己完全独立的变量的拷贝。如thread_local int x = 0;

面向安全编程
  • 引入新关键字noexcept表明函数不会跑出任何异常。void func() noexcept{}
  • 内联命名空间,在namespace关键字前加上关键字inline,如inline namespace temp{...},表明该命名空间temp是内联的,外部无须加上空间名字就可直接访问空间内变量、函数等。
  • 强类型枚举,在enum后面加上class/struct声明为强类型枚举,它里面的枚举值不能隐式转换为整数,且必须使用类型名限定访问枚举值(类似于访问静态成员一样),此外还允许在枚举声明后使用char 、int等知识枚举使用的整数类型,让程序员更好的控制空间占用,enum class color : char{}; auto x = color::red;
  • C++11中提出了属性的概念,使用方括号[[attribute]]的形式,属性用于标记编译特征,指示编译器做某些程度的优化。C++11中有两个属性noreturn和carries_dependency。C++14增加了属性deprecated,该属性用于明确地标记被废弃的变量、函数或类。[[deprecated int x = 0;//声明变量x已经被废弃
C++11其他特性
  • 预定义了宏"__cplusplus",这是一个整型常数,利用它程序能够辨别当前编译器使用的标准的版本。1. 未定义,表示为C编译器。2. 199711L,C++98/03。3. 201103L,C++11。4. 201402L,C++14。
  • C++11定义了超长整型long long,它至少有64位,long long也可以使用unsigned修饰,C++11中也新增加了LL/ll或者ULL/uLL/ull来说明整数的类型是long long。auto a = 52428LL
  • 原始字符串,以大写R开头,形式为R"(...)",圆括号中就是原始字符串,它会保留所有字符的原始样子,括号中是什么样的,字符串就是什么样的,好处在于即使字符中有特殊符号如“"”和“\”等,都不需要进行转义。它又一种扩展,允许在圆括号两边最多加上16个字符,成为delimiter,可以更好的标记字符串。auto b = R"***(BioSHock Infinity)***";,圆括号两边delimiter必须相同,且不能使用“@”、“$”、“\”等特殊字符。
  • 在代码auto x = 0x100L;中,0x是一个前缀,表明字面值100是16进制,L是后缀,表明是long类型。C++11增加了用户自定义字面值的工,允许用户增加自定义的后缀,定义函数来对字面值进行运算,决定字面值的时间类型,从而简化代码。自定义字面值需要重载新的操作符“ " " ”,形式如下:
return_type operator"" _suffix(unsigned long long);
return_type operator"" _suffix(long double);
return_type operator"" _suffix(const char*, size_t);

函数声明中的_suffix就是自定义的后缀,当编译器发现字面值使用了该后缀时,实际转换为函数调用,函数名字必须是下划线开头。参数不是任意的,只能使用C++11标准中固定的集中形式,常用的有unsigned long long、long double、const char*等。而函数返回值可以是任意类型,如int,char甚至是自定义类型。如下例自定义字面值用法:

long operator"" _kb(unsigned long long v)//定义后缀“_kb”
{ return v*1024;}

auto x = 2_kb;//使用后缀“_kb”
assert(x == 2*1024);//实际值为2048

C++14中增加了“h/min/s/ms”等新的字面值后缀,可以直接在代码中书写实际单位或者标准字符串。

auto t1 = 2min;//2分钟
auto t2 = 30s;//30秒,整数后面s表示秒,注意与下一行区别
auto s = "std string type"s;//字符串后面的s表示标准字符串,不是秒
杂项
  • 在C++98里模板参数列表中不能出现“>>”这样连续两个右尖括号,否则编译器会把它解释为右移运算符,多个尖括号之间必须加空格。C++11中规定在模板声明中遇到“>>”会游戏解释为模板参数列表的结束标记。
  • C++11中模板类和模板函数的模板参数列表中可以使用默认参数,而C++98中模板函数是不允许的。
  • 增强的联合体,C++11中union内的成员不仅可以是POD(平台旧数据,可以和C兼容的数据类型)类型,而且可以是具有构造/析构函数的自定义类型(但不能有虚函数和引用成员),还可以拥有成员函数,使它更接近于class。
  • C++14增加了新的字面值前缀,可以用“0b/0B”来直接书写二进制数字。
  • 数字分位符,C++14允许使用单引号“ ’ ”来给很长的数字分组,以增加可读性。

第二章 模板元编程简介

模板元编程本质上是泛型编程的一个子集,它是一种“函数式编程”,并且已经证明是图灵完备的,可以“计算”任何东西。模板元编程运行在编译期,它把编译器变成了元程序的解释器。

语法元素

它遵循C++语法规则,但是它的操作对象不是普通的变量,因此不能使用运行时的C++关键字(如if、else、for等),可以使用的语法元素相当有限,最常用的包括如下几种:

  • enum、static:用来定义编译期的整数常量
  • typedef、using(C++11/14):最重要的元编程关键字,用于定义元数据
  • template:模板元编程的“起点”,用于定义元函数
  • “::”:域运算符,用于解析类型作用域获取计算结果(元数据)
    以上这些语法元素稍后的小节中也会讲解、使用。
元数据

元编程可操作的数据称为“元数据”,它是不可变的,不能就地修改,最常见的元数据是整数和C++的类型(type),整数作为数据本身就包含一定的信息;type作为数据也可以包含一些信息,如一些自定义的type,或者说在一个类中具有某种type可以表明它具有某种属性等(这在type_traits中常使用),类型一般再元函数中用于类型计算,如输入一个类型,输出另一个类型。
使用typedef和using(using是在C++11中新加的功能)关键字可以任意定义(声明)元数据,很想运行时的变量定义,如下

typedef int meta_data1;//元数据meta_data1
using meta_data1 = int;//元数据meta_data1
元函数

元函数功能和形式和运行时函数类似,用于操作处理元数据。元函数实际上表现为C++的一个类或者模板类,编写元函数就像编写一个普通的运行时函数,通常有如下形式:

template<typename arg1, typename arg2, ...>//元函数参数列表
struct meta_function
{
	typedef some-define type;//元函数返回的元数据
	static int const value = some-int;//元函数返回的整数
};//使用分号结束元函数的定义

元函数也可以有重载(模板特化/偏特化)等;

元函数转发

元函数的转发相当于运行时的函数转发调用,主要使用public继承实现。通过继承将父类的模板参数传递给子类,同时子类会自动获得父类的::type定义,同时也就完成了元函数的返回。

第三章 类型特征萃取

模板元工具type_traits实现类型特性萃取功能,它在C++11/14中以头文件,<type_traits>中,type_traits位于命名空间boost中;

#include <boost/type_traits.hpp>
using namespace boost;

type_traits库提供一组特征(traits)类—即元函数,可以在编译期确定类型(元数据)是否具有某些特征,或者为类型增添或移除const、volatile等修饰词。
根据返回类型type_traits库中元函数可以分为两类:

  • 检查元数据属性的值元函数:以::value返回一个bool值或一个整数。
  • 操作元数据的标准元函数:对元数据进行计算,以::type返回一个新的元数据。
    type_traits库中以is_和has_开头的元函数均属于值元函数,其他则属于标准元函数,但少数元函数例外。
    根据元函数实现的功能可分为以下7类:
  • 检查元数据的类别
  • 检查元数据的属性
  • 检查元函数之间的关系
  • 检查操作符重载
  • 转换元数据
  • 解析函数元数据
  • 用指定的对齐方式组合类型
    书中只介绍了上述中前6类元函数的使用,没有源码!

第四章 实用工具

本章介绍一些功能简单、但实现原理涉及C++语言深层次概念细节的Boost组件。

compressed_pair

此功能与std::pair类似,但是针对空类(空类至少会占用一个字节的内存)进行了特别优化以压缩pair的大小。在使用上compressed_pair与std::pair略有不同。

  • std::pair使用first和second引用内部对象,而compressed_pair使用了继承来实现,“空类”不是以成员变量的形式存在,所以compressed_pair不能直接访问成员,只能通过first()、second()函数来访问。
  • compressed_pair没有辅助函数和比较操作,需要自己定义,如定义操作如下。此定义只是简单的比较操作,并没有考虑空类。真正的比较功能使用元编程实现,针对不同情况下的空类进行偏特化。
template<typename T1, typename T2>
bool operator==(const compressed_pair<T1,T2>& l, const compressed_pair<T1,T2>& r)
{ return l.first() == r.first && l.second() == r.second; }
  • compressed_pair的真正实现是模板类compressed_pair_imp,针对多种空类是否存在的情况定义了6(3*2,3个模板参数每个参数都有两种情况)个偏特化的compressed_pair_imp实现,声明如下。如前面所述,一般对象在偏特化中以成员变量实现,空类采用继承实现。
template<class T1, class T2, int Version>
class compressed_pair_imp;
checked_delete

checked_delete作用与delete一样,用于删除对象,checked_array_delete与delete[]一样用于删除数组。不同之处在于checked_delete是带检查的删除,当删除对象不完整时(在delete时只有类声明而没有类定义,如类定义在delete调用后面),编译报错(当使用delete时,只会出现警报,甚至有的编译器不会警报)。
共有函数形式和类形式两种版本。

  • 函数形式:checked_delete和checked_array_delete,声明为:
template<class T> void checked_delete(T* p);
template<class T> void checked_array_delete(T* p);
用法为:
auto p1 = new int(10);
check_delete(p1);//模板函数可以自动推导模板类型,所以和普通函数调用没有区别,
  • 函数对象形式:checked_deleter和checked_array_deleter,声明为:
template<class T>
struct checked_deleter
{
	typedef void result_type;
	typedef T* argument_type;
	void operator()(T* x) const;
};
checked_array_deleter声明类似,用法为
auto p1 = new demo_class;
checked_deleter<demo_class>()(p1);//函数对象不能自动推导模板参数,因此使用时需指明待删除的对象类型。第一个()作用在于构造出一个临时checked_deleter对象,第二个(p1)为operator()的调用。

实现原理简单,代码如下,利用sizeof()参数编译错误,当T不完整时,typedef一个大小为-1的数组。

template<class T> inline void checked_delet(T* x)
{
	typedef char type_must_be_complete[sizeof(T)?1:-1];
	(void) sizeof(type_must_be_complete);
	delete x;
}

ckecked_delete由于直接管理内存,容易产生难以察觉的内存错误,一般使用智能指针,如unique_ptr和shared_ptr它们在内部调用了checked_delete。checked_delete相对于delete并没有效率上的损失。

addressof

addressof是对C++取地址操作(&)的增强,作用一样。由于C++允许操作operator&,因此有时对对象使用&进行取地址不一定能得到该对象的地址(重载operator&后只有程序员知道返回的是啥),但addressof总能获取对象的真实地址,它已经被收入C++11标准(头文件<memory>)。用法简单:string s; assert(&s == addressof(s));//断言,&与addressof此时效果一样
实现原理核心代码为reinterpret_cast<T*> (&const_cast<char&>(reinterpret_cast<const volatile char &>(v)));,仅仅是使用了复杂的转型操作。
addressof的运行效率没有原始的operator&高,有一点效率损失。建议不要重载operator&

base_from_member

为解决使用派生类成员变量初始化基类成员变量所产生的问题而构造(在初始化时基类构造函数先调用,此时派生类成员变量还未初始化,从而会产生问题)。
实现原理:将派生类中用于初始化基类的成员变量移到base_from_member类中,由派生类基础base_from_member类和基类,base_from_member先继承,因此该类的成员变量先于基类初始化。

  • base_from_member使用private继承
  • 当需要使用多个派生类成员变量初始化多个基类时,可以继承多个base_from_member,base_from_member是一个模板函数,第二个参数为一个ID,用于标识区分多个不同的类。但此种情况最好自己定义一个类似base_from_member的类。
conversion库

标准C++中有const_cast、static_cast、dynamic_cast、reinterpret_cast四种显示类型转换,conversion库重点关注dynamic_cast类型转换(用于多态类型转换,即基类到派生类的转换,或派生类到基类的转换等),多态类型转换分为两类:

  1. 向下转型:从基类到派生类转型;
  2. 交叉转型:从一个基类到另一个基类转型;
  • dynamic_cast同时支持这两种转型,在使用上并不区分,容易造成混淆,因此conversion库提供了polymorphic_downcast和polymorphic_cast以区分向下转型和交叉转型(这两个是模板函数)。
  • dynamic_cast转型指针失败后会返回空指针(nullptr),而引用转型失败后抛出异常bad_cast。conversion库中提供了polymorphic_downcast和polymorphic_cast对指针和引用的转型的重载形式。polymorphic_downcast和polymorphic_cast对指针和引用实现上不同,但都较简单,并且不论是指针还是引用转型,失败后都统一抛出bad_cast异常。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值