- 内容提要
- 前言
- 第 1 章 预备知识
- 第 2 章 开始学习 C++
- 第 3 章 处理数据
- 第 4 章 复合类型
- 第 5 章 循环和关系表达式
- 第 6 章 分支语句和逻辑运算符
- 第 7 章 函数——C++的编程模块
- 第 8 章 函数探幽
- 第 9 章 内存模型和名称空间
- 第 10 章 对象和类
- 第 11 章 使用类
- 第 12 章 类和动态内存分配
- 第 13 章 类继承
- 第 14 章 C++中的代码重用
- 第 15 章 友元、异常和其他
- 第 16 章 string 类和标准模板库
- 第 17 章 输入、输出和文件
- 第 18 章 探讨 C++新标准
- 附录 A 计数系统
- 附录 B C++保留字
- 附录 C ASCII 字符集
- 附录 D 运算符优先级
- 附录 E 其他运算符
- 附录 F 模板类 string
- 附录 G 标准模板库方法和函数
- 附录 H 精选读物和网上资源
- 附录 I 转换为 ISO 标准 C++
- 附录 J 复习题答案
18.3 新的类功能
除本章前面提到的显式转换运算符和类内成员初始化外,C++11 还新增了其他几个类功能。
18.3.1 特殊的成员函数
在原有 4 个特殊成员函数(默认构造函数、复制构造函数、复制赋值运算符和析构函数)的基础上,C++11 新增了两个:移动构造函数和移动赋值运算符。这些成员函数是编译器在各种情况下自动提供的。
前面说过,在没有提供任何参数的情况下,将调用默认构造函数。如果您没有给类定义任何构造函数,编译器将提供一个默认构造函数。这种版本的默认构造函数被称为默认的默认构造函数。对于使用内置类型的成员,默认的默认构造函数不对其进行初始化;对于属于类对象的成员,则调用其默认构造函数。
另外,如果您没有提供复制构造函数,而代码又需要使用它,编译器将提供一个默认的复制构造函数;如果您没有提供移动构造函数,而代码又需要使用它,编译器将提供一个默认的移动构造函数。假定类名为 Someclass,这两个默认的构造函数的原型如下:
在类似的情况下,编译器将提供默认的复制运算符和默认的移动运算符,它们的原型如下:
最后,如果您没有提供析构函数,编译器将提供一个。
对于前面描述的情况,有一些例外。如果您提供了析构函数、复制构造函数或复制赋值运算符,编译器将不会自动提供移动构造函数和移动赋值运算符;如果您提供了移动构造函数或移动赋值运算符,编译器将不会自动提供复制构造函数和复制赋值运算符。
另外,默认的移动构造函数和移动赋值运算符的工作方式与复制版本类似:执行逐成员初始化并复制内置类型。如果成员是类对象,将使用相应类的构造函数和赋值运算符,就像参数为右值一样。如果定义了移动构造函数和移动赋值运算符,这将调用它们;否则将调用复制构造函数和复制赋值运算符。
18.3.2 默认的方法和禁用的方法
C++11 让您能够更好地控制要使用的方法。假定您要使用某个默认的函数,而这个函数由于某种原因不会自动创建。例如,您提供了移动构造函数,因此编译器不会自动创建默认的构造函数、复制构造函数和复制赋值构造函数。在这些情况下,您可使用关键字 default 显式地声明这些方法的默认版本:
编译器将创建在您没有提供移动构造函数的情况下将自动提供的构造函数。
另一方面,关键字 delete 可用于禁止编译器使用特定方法。例如,要禁止复制对象,可禁用复制构造函数和复制赋值运算符:
第 12 章说过,要禁止复制,可将复制构造函数和赋值运算符放在类定义的 private 部分,但使用 delete 也能达到这个目的,且更不容易犯错、更容易理解。
如果在启用移动方法的同时禁用复制方法,结果将如何呢?前面说过,移动操作使用的右值引用只能关联到右值表达式,这意味着:
关键字 default 只能用于 6 个特殊成员函数,但 delete 可用于任何成员函数。delete 的一种可能用法是禁止特定的转换。例如,假设 Someclass 类有一个接受 double 参数的方法:
再假设有如下代码:
int 值 5 将被提升为 5.0,进而执行方法 redo( )。
现在假设将 Someclass 类的定义改成了下面这样:
在这种情况下,方法调用 sc.redo(5) 与原型 redo(int) 匹配。编译器检测到这一点以及 redo(int) 被禁用后,将这种调用视为编译错误。这说明了禁用函数的重要一点:它们只用于查找匹配函数,使用它们将导致编译错误。
18.3.3 委托构造函数
如果给类提供了多个构造函数,您可能重复编写相同的代码。也就是说,有些构造函数可能需要包含其他构造函数中已有的代码。为让编码工作更简单、更可靠,C++11 允许您在一个构造函数的定义中使用另一个构造函数。这被称为委托,因为构造函数暂时将创建对象的工作委托给另一个构造函数。委托使用成员初始化列表语法的变种:
例如,上述默认构造函数使用第一个构造函数初始化数据成员并执行其函数体,然后再执行自己的函数体。
18.3.4 继承构造函数
为进一步简化编码工作,C++11 提供了一种让派生类能够继承基类构造函数的机制。C++98 提供了一种让名称空间中函数可用的语法:
这让函数 fn 的所有重载版本都可用。也可使用这种方法让基类的所有非特殊成员函数对派生类可用。例如,请看下面的代码:
C2 中的 using 声明让 C2 对象可使用 C1 的三个 fn( ) 方法,但将选择 C2 而不是 C1 定义的方法 fn(double)。
C++11 将这种方法用于构造函数。这让派生类继承基类的所有构造函数(默认构造函数、复制构造函数和移动构造函数除外),但不会使用与派生类构造函数的特征标匹配的构造函数:
由于没有构造函数 DR(int, double),因此创建 DR 对象 o3 时,将使用继承而来的 BS(int, double)。请注意,继承的基类构造函数只初始化基类成员;如果还要初始化派生类成员,则应使用成员列表初始化语法:
18.3.5 管理虚方法:override 和 final
虚方法对实现多态类层次结构很重要,让基类引用或指针能够根据指向的对象类型调用相应的方法,但虚方法也带来了一些编程陷阱。例如,假设基类声明了一个虚方法,而您决定在派生类中提供不同的版本,这将覆盖旧版本。但正如第 13 章讨论的,如果特征标不匹配,将隐藏而不是覆盖旧版本:
由于类 Bingo 定义的是 f(char * ch) 而不是 f(char ch),将对 Bingo 对象隐藏 f(char ch),这导致程序不能使用类似于下面的代码:
在 C++11 中,可使用虚说明符 override 指出您要覆盖一个虚函数:将其放在参数列表后面。如果声明与基类方法不匹配,编译器将视为错误。因此,下面的 Bingo::f( ) 版本将生成一条编译错误消息:
例如,在 Microsoft Visual C++ 2010 中,出现的错误消息如下:
说明符 final 解决了另一个问题。您可能想禁止派生类覆盖特定的虚方法,为此可在参数列表后面加上 final。例如,下面的代码禁止 Action 的派生类重新定义函数 f( ):
说明符 override 和 final 并非关键字,而是具有特殊含义的标识符。这意味着编译器根据上下文确定它们是否有特殊含义;在其他上下文中,可将它们用作常规标识符,如变量名或枚举。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论