🥒
type
status
date
slug
summary
tags
category
icon
password
Property
目录
C++ 是可以多重继承的(Multiple inheritance),意味着一个派生类可以多个直接基类。多重派生类(multiply derived class)继承其所有的父类的属性。尽管在概念上很简单,但是在细节上涉及到多个直接基类的时候会在设计层面和实现层面遇到许多问题。
多重继承
多重继承的派生列表会包含多个基类:
每个基类都有一个可选的访问说明符,如果省略的话就提供默认的说明符,对于
class
是private
,对于struct
是public
。与单一继承一样,派生列表中的类必须是已经定义的,并且不能是final
的。语言并没有限制具体可以在派生列表中包含多少个类。每个基类只能在派生列表中出现一次。多重派生类从每个基类中继承状态,一个派生类对象将包含所有基类的子对象。
Panda
类中包含了Bear
和Endangered
子对象,以及它自身定义的成员。🥒
type
status
date
slug
summary
tags
category
icon
password
Property
容器与继承
当使用容器存放继承体系中的对象时,通常必须采用间接存储的方式。
因为不允许在容器中保存不同类型的元素,所以不能把具有继承体系关系的多种类型的对象直接存放在容器当中。
当派生类对象被赋值给基类对象时,其中的派生类部分将被“切掉”,因此容器和存在继承关系的类型无法兼容。
在容器中放置(智能)指针而非对象
当希望在容器中存放具有继承关系的对线时,实际上存放的是基类的指针(更好的选择是智能指针),这些指针所指对象的动态类型可能是基类类型,也可能是派生类类型:
basket
存放着shared_ptr
,解引用basket.back()
的返回值以获得运行net_price
的对象,通过在net_price
的调用中使用->
以达到这个目的。实际调用的net_price
版本依赖于指针所指对象的动态类型。可以将一个派生类的普通指针转换成基类指针,也能把一个派生类的普通指针转换成基类的智能指针。继承和组合
🥒
type
status
date
slug
summary
tags
category
icon
password
Property
目录
C++
提供了运行时类型识别,使得在运行时可以进行类型识别,这个在某些场景很有用途。运行时类型识别(Runtime type identification, RTTI)通过两个操作符提供:
typeid
操作符返回给定表达式的类型
dynamic_cast
操作符将一个基类指针或者引用转为派生类的指针或引用
在有些时候想通过基类指针或引用去调用派生类的函数是不可能的,原因在于无法定义这个虚函数。如果不能使用虚函数,可以使用 RTTI 操作符。另一方面,使用这些操作符将比使用虚成员函数更加易错:程序员必须知道对象的真实类型,并且必须检查转型是否成功。
RTTI 应该被限制使用在有限的范围内,更好的方式是定义虚函数而不是直接管理这些类型。
🥒
type
status
date
slug
summary
tags
category
icon
password
Property
目录
设计一个类,不能被拷贝
拷贝只会出现在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98
:将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可- 设置成私有:如果只声明没有设置成
private
,用户自己如果在类外定义了,就不能禁止拷贝了
- 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了
C++11
:C++11
拓展delete
的用法,delete
除了释放new
申请的资源外,如果在默认成员函数后加上=delete
,即表示让编译器删除掉该默认成员函数。🍒
type
status
date
slug
summary
tags
category
icon
password
Property
目录
异常(exception)是指程序运行时的反常行为,这些行为超出了函数正常功能的范围。当程序的某一部分检测到一个它无法处理的问题时,需要使用 异常处理。
异常处理机制为程序中异常检测和异常处理这两部分的协作提供支持,包括
throw
表达式、try
语句块和异常类。- 异常检测部分使用
throw
表达式表示它遇到了无法处理的问题(throw
引发了异常)
- 异常处理部分使用
try
语句块处理异常。try
语句块以关键字try
开始,并以一个或多个catch
子句结束。try
语句块中代码抛出的异常通常会被某个catch
子句处理,catch
子句也被称作异常处理代码
try
语句块中代码抛出的异常通常会被某个catch
子句处理,catch
子句也被称作异常处理代码
throw表达式
🥬
type
status
date
slug
summary
tags
category
icon
password
Property
目录
为每个类型都定义相同的操作是十分繁琐的,而且需要知道所有操作的类型,更合理的方式是定义函数模板,然后在使用时提供类型:
对每种希望比较的类型,重复定义完全一样的函数体, 是非常烦琐且容易出错的。而且,需要确定可能要
compare
的所有类型,如果希望能在用户提供的类型上使用此函数, 这种策略可能就失效了。相比于为每个类型定义一个新的函数,可以定义函数模板(function template)。
模板定义从关键字
template
开始,后面跟着模板参数列表(在模板定义中,模板参数列表不能是空的)——放在尖括号中的逗号分隔的一个或多个模板参数。🥬
type
status
date
slug
summary
tags
category
icon
password
Property
目录
类模板(class template)是合成类的蓝本。与函数模板不同的是编译器不能推断出类模板的模板参数的类型。相反,使用类模板时必须提供额外的信息,这些信息将放在类模板名后的尖括号中。这些额外信息是模板实参列表(template arguments list),用于替换模板参数列表(template parameters list)。
比如
Stack
,如果定它是int
,那么它就是存整型的栈:如果想改成存
double
类型的栈呢?需要改变栈的数据类型,直接改typedef
那里也可以:这依然治标不治本,虽然看起来就像是支持泛型一样,它最大的问题是不能同时存储两个类型,你就算是改也没法解决:
🥬
type
status
date
slug
summary
tags
category
icon
password
Property
默认情况下编译器使用调用实参来决定函数模板的模板参数。这个过程称为模板实参推断(template argument deduction),在推断过程中编译器使用调用的实参类型来选择哪个生成的函数版本是最合适的。
类型转换和模板类型参数
与常规函数一样,传递给函数的模板的实参被用于初始化函数的参数。类型是模板类型参数的函数参数有特殊的初始化规则。只有非常有限的几个转换是自动运用于这种实参的。相比于转换实参,编译器会生成一个新的实例。
与之前的描述一样,不论是参数还是实参中的顶层
const
都会被忽略。在函数模板中会执行的有限转换分别是:const
转换:如果一个函数参数是const
的引用或指针,可以传递一个非const
对象的引用或指针;
- 数组或函数至指针的转换:如果函数参数不是引用类型,那么指针转换将被运用于数组或函数类型。数组实参将被转为指向其首元素的指针,同样,函数实参将被自动转为指向函数类型的指针;
其它任何类型的转换(算术转换、子类到基类的转换、用户定义转换)都不会执行:
在 fobj 调用中,数组的类型不一样是无所谓的。两个数组都转为了指针,fobj 中的模板参数类型是
int*
,而调用 fref 却是非法的,当参数是引用时,数组不能自动转为指针,此时 a 和 b 的类型是不匹配的,因而调用是错误。🥬
type
status
date
slug
summary
tags
category
icon
password
Property
函数模板可以被别的模板或非模板函数重载。与往常一样,同名的函数必须在参数的数目或类型上有所差异。由于函数模板的出现导致的函数匹配的差异将表现在以下几个方面:
- 一个调用的候选函数(candidate function)包括所有模板实参推断成功的函数模板实例;
- 候选函数模板实例讲总是可行函数(viable function),这是由于模板实参推断会排除所有不可行的模板;
- 与往常一样,可行函数事按照需要进行的转型进行排序的,对于函数模板来说这种转型是非常有限的;
- 与往常一样,如果一个函数比其它函数提供了更优的匹配,此函数将被选中。然而,如果有好几个函数提供了一样好的匹配,那么:
为了正确定义重载的函数模板需要对类型之间的关系以及模板函数的有限转型有一个良好的理解;
编写写重载模板
如下两个函数是重载的模板,如:
🥬
type
status
date
slug
summary
tags
category
icon
password
Property
可变参数模板(variadic template)是新加的一种函数模板或类模板,这种模板可以接收可变数量的模板参数。这种可变参数被称为参数包(parameter pack)。语言允许两种参数包:模板参数包(template parameter pack)表示 0 或多个模板参数,以及函数参数包(function parameter pack)表示 0 个或多个函数参数。
语言使用省略号(ellipsis)来表示模板或函数参数包。对于模板参数列表,
class...
或 typename...
表示接下的参数表示一系列 0 个或多个类型;省略号后的名字表示其参数包的任何类型的名字。在函数参数列表中,如果一个参数的类型是一个模板参数包,那么它就是一个函数参数包。如:将 foo 声明为一个可变函数,其中一个类型参数是 T,以及一个模板参数包是 Args,这个参数包表示 0 个或多个额外的类型参数。函数参数列表则有一个参数,其类型是
const T&
,以及一个函数参数包 rest,这个参数包表示 0 个或多个函数参数。与之前一样,编译器从函数实参中推断模板参数类型。对于可变模板,编译器还会推断参数包中的数量,如:
编译器将会实例化 4 个不同的 foo 实例:
🥬
type
status
date
slug
summary
tags
category
icon
password
Property
一个模板并不总是能满足可以所有使其实例化的模板实参的要求。在某些情况下,通用的模板定义还可能是错误的:通用的定义可能无法编译或者做错误的事情。在某些情况下,可以利用特定类型的知识来写出更加有针对性且高效的代码(至少比从模板中实例化更加高效)。当不想或者不能使用模板版本时,可以定义一个类或函数模板的特例化版本。如:
以上两个函数,(2) 函数只能对数组或字符串字面量进行调用,如果传入了字符指针,那么总是 (1) 函数被调用。
为了处理字符指针的清醒,需要给 (1) 模板定义特例(template specialization)。特例是模板的另外一个定义,其中一个或多个目标那参数具有特定的类型。
定义函数模板特例
当定义函数模板特例时,需要给原模板中所有的模板参数提供实参。为了表示特例化一个模板,需要使用关键字
template
后跟随一个空的尖括号<>
,空的尖括号表示给原模板中的所有模板参数都提供了实参: