《深入理解C++11:C++ 11新特性解析与应用》——第3章 通用为本,专用为末 3.1 继承构造函数

简介: 本节书摘来自华章计算机《深入理解C++11:C++ 11新特性解析与应用》一书中的第3章,第3.1节,作者 IBM XL编译器中国开发团队,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

第 3 章

通用为本,专用为末

C++11的设计者总是希望从各种方案中抽象出更为通用的方法来构建新的特性。这意味着C++11中的新特性往往具有广泛的可用性,可以与其他已有的,或者新增的语言特性结合起来进行自由的组合,或者提升已有特性的通用性。这与在语言缺陷上“打补丁”的做法有着本质的不同,但也在一定程度上拖慢了C++11标准的制定。不过现在一切都已经尘埃落定了。在本章里读者可以看到这些经过反复斟酌制定的新特性,并体会其“普适”的特性。当然,要对一些形如右值引用、移动语义的复杂新特性做到融会贯通,则需要读者反复揣摩。

3.1 继承构造函数

类别:类作者

C++中的自定义类型—类,是C++面向对象的基石。类具有可派生性,派生类可以自动获得基类的成员变量和接口(虚函数和纯虚函数,这里我们指的都是public派生)。不过基类的非虚函数则无法再被派生类使用了。这条规则对于类中最为特别的构造函数也不例外,如果派生类要使用基类的构造函数,通常需要在构造函数中显式声明。比如下面的例子:

struct A { A(int i) {} };
struct B : A { B(int i): A(i) {} };

B派生于A,B又在构造函数中调用A的构造函数,从而完成构造函数的“传递”。这在C++代码中非常常见。当然,这样的设计有一定的好处,尤其是B中有成员的时候。如代码清单3-1所示的例子。

image

在代码清单3-1中我们看到,派生于结构体A的结构体B拥有一个成员变量d,那么在B的构造函数B(int i)中,我们可以在初始化其基类A的同时初始化成员d。从这个意义上讲,这样的构造函数设计也算是非常合理的。

不过合情合理并不等于合用,有的时候,我们的基类可能拥有数量众多的不同版本的构造函数—这样的情况并不少见,我们在2.7节中就曾经看到过这样的例子。那么倘若基类中有大量的构造函数,而派生类却只有一些成员函数时,那么对于派生类而言,其构造就等同于构造基类。这时候问题就来了,在派生类中我们写的构造函数完完全全就是为了构造基类。那么为了遵从于语法规则,我们还需要写很多的“透传”的构造函数。我们可以看看下面这个例子,如代码清单3-2所示。

image

在代码清单3-2中,我们的基类A有很多的构造函数的版本,而继承于A的派生类B实际上只是添加了一个接口ExtraInterface。那么如果我们在构造B的时候想要拥有A这样多的构造方法的话,就必须一一“透传”各个接口。这无疑是相当不方便的。

事实上,在C++中已经有了一个好用的规则,就是如果派生类要使用基类的成员函数的话,可以通过using声明(using-declaration)来完成。我们可以看看下面这个例子,如代码清单3-3所示。

image
image

在代码清单3-3中,我们的基类Base和派生类Derived声明了同名的函数f,不过在派生类中的版本跟基类有所不同。派生类中的f函数接受int类型为参数,而基类中接受double类型的参数。这里我们使用了using声明,声明派生类Derived也使用基类版本的函数f。这样一来,派生类中实际就拥有了两个f函数的版本。可以看到,我们在main函数中分别定义了Base变量b和Derived变量d,并传入浮点字面常量4.5,结果都会调用到基类的接受double为参数的版本。

在C++11中,这个想法被扩展到了构造函数上。子类可以通过使用using声明来声明继承基类的构造函数。那我们要改造代码清单3-2所示的例子就非常容易了,如代码清单3-4所示。

image

这里我们通过using A::A的声明,把基类中的构造函数悉数继承到派生类B中。这样我们在代码清单3-2中的“透传”构造函数就不再需要了。而且更为精巧的是,C++11标准继承构造函数被设计为跟派生类中的各种类默认函数(默认构造、析构、拷贝构造等)一样,是隐式声明的。这意味着如果一个继承构造函数不被相关代码使用,编译器不会为其产生真正的函数代码。这无疑比“透传”方案总是生成派生类的各种构造函数更加节省目标代码空间。

不过继承构造函数只会初始化基类中成员变量,对于派生类中的成员变量,则无能为力。不过配合我们2.7节中的类成员的初始化表达式,为派生类成员变量设定一个默认值还是没有问题的。

在代码清单3-5中我们就同时使用了继承构造函数和成员变量初始化两个C++11的特性。这样就可以解决一些继承构造函数无法初始化的派生类成员问题。如果这样仍然无法满足需求的话,程序员只能自己来实现一个构造函数,以达到基类和成员变量都能够初始化的目的。

image

有的时候,基类构造函数的参数会有默认值。对于继承构造函数来讲,参数的默认值是不会被继承的。事实上,默认值会导致基类产生多个构造函数的版本,这些函数版本都会被派生类继承。比如代码清单3-6所示的这个例子。

image

可以看到,在代码清单3-6中,我们的基类的构造函数A (int a = 3, double = 2.4)有一个接受两个参数的构造函数,且两个参数均有默认值。那么A到底有多少个可能的构造函数的版本呢?

事实上,B可能从A中继承来的候选继承构造函数有如下一些:

image

相应地,B中的构造函数将会包括以下一些:

image

可以看见,参数默认值会导致多个构造函数版本的产生,因此程序员在使用有参数默认值的构造函数的基类的时候,必须小心。

而有的时候,我们还会遇到继承构造函数“冲突”的情况。这通常发生在派生类拥有多个基类的时候。多个基类中的部分构造函数可能导致派生类中的继承构造函数的函数名、参数(有的时候,我们也称其为函数签名)都相同,那么继承类中的冲突的继承构造函数将导致不合法的派生类代码,如代码清单3-7所示。

image

在代码清单3-7中,A和B的构造函数会导致C中重复定义相同类型的继承构造函数。这种情况下,可以通过显式定义继承类的冲突的构造函数,阻止隐式生成相应的继承构造函数来解决冲突。比如:

struct C: A, B {
    using A::A;
    using B::B;
    C(int){}
};

其中的构造函数C(int)就很好地解决了代码清单3-7中继承构造函数的冲突问题。

另外我们还需要了解的一些规则是,如果基类的构造函数被声明为私有成员函数,或者派生类是从基类中虚继承的,那么就不能够在派生类中声明继承构造函数。此外,如果一旦使用了继承构造函数,编译器就不会再为派生类生成默认构造函数了,那么形如代码清单3-8中这样的情况,程序员就必须注意继承构造函数没有包含一个无参数的版本。在本例中,变量b的定义应该是不能够通过编译的。

image

在我们编写本书的时候,还没有编译器实现了继承构造函数这个特性,所以本节中代码清单3-4至代码清单3-8的例子都仅供读者参考,因为我们并没有实际编译过。但是编译器对继承构造函数的支持应该很快就要完成了,比如g++就计划在4.8版本中提供支持。可能本书出版的时候,读者就已经可以进行实验了。

相关文章
|
12天前
|
存储 C++ 容器
C++STL(标准模板库)处理学习应用案例
【4月更文挑战第8天】使用C++ STL,通过`std:vector`存储整数数组 `{5, 3, 1, 4, 2}`,然后利用`std::sort`进行排序,输出排序后序列:`std:vector<int> numbers; numbers = {5, 3, 1, 4, 2}; std:sort(numbers.begin(), numbers.end()); for (int number : numbers) { std::cout << number << " "; }`
17 2
|
16天前
|
存储 缓存 安全
掌握Go语言:Go语言中的字典魔法,高效数据检索与应用实例解析(18)
掌握Go语言:Go语言中的字典魔法,高效数据检索与应用实例解析(18)
|
22天前
|
存储 C++ 容器
C++入门指南:string类文档详细解析(非常经典,建议收藏)
C++入门指南:string类文档详细解析(非常经典,建议收藏)
31 0
|
23天前
|
安全 算法 C++
【C/C++ 泛型编程 应用篇】C++ 如何通过Type traits处理弱枚举和强枚举
【C/C++ 泛型编程 应用篇】C++ 如何通过Type traits处理弱枚举和强枚举
46 3
|
19天前
|
存储 缓存 算法
Python中collections模块的deque双端队列:深入解析与应用
在Python的`collections`模块中,`deque`(双端队列)是一个线程安全、快速添加和删除元素的双端队列数据类型。它支持从队列的两端添加和弹出元素,提供了比列表更高的效率,特别是在处理大型数据集时。本文将详细解析`deque`的原理、使用方法以及它在各种场景中的应用。
|
21天前
|
安全 Java 数据安全/隐私保护
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
44 1
|
6天前
|
SQL API 数据库
Python中的SQLAlchemy框架:深度解析与实战应用
【4月更文挑战第13天】在Python的众多ORM(对象关系映射)框架中,SQLAlchemy以其功能强大、灵活性和易扩展性脱颖而出,成为许多开发者首选的数据库操作工具。本文将深入探讨SQLAlchemy的核心概念、功能特点以及实战应用,帮助读者更好地理解和使用这一框架。
|
8天前
|
机器学习/深度学习 分布式计算 BI
Flink实时流处理框架原理与应用:面试经验与必备知识点解析
【4月更文挑战第9天】本文详尽探讨了Flink实时流处理框架的原理,包括运行时架构、数据流模型、状态管理和容错机制、资源调度与优化以及与外部系统的集成。此外,还介绍了Flink在实时数据管道、分析、数仓与BI、机器学习等领域的应用实践。同时,文章提供了面试经验与常见问题解析,如Flink与其他系统的对比、实际项目挑战及解决方案,并展望了Flink的未来发展趋势。附带Java DataStream API代码样例,为学习和面试准备提供了实用素材。
27 0
|
12天前
|
程序员 C++
C++语言模板学习应用案例
C++模板实现通用代码,以适应多种数据类型。示例展示了一个计算两数之和的模板函数`add&lt;T&gt;`,可处理整数和浮点数。在`main`函数中,展示了对`add`模板的调用,分别计算整数和浮点数的和,输出结果。
11 2
|
15天前
|
测试技术 API 智能硬件
语言模型在提升智能助手引用解析能力中的创新应用
【4月更文挑战第4天】苹果研究团队推出了ReALM,一种利用大型语言模型解决引用解析的新方法,提升智能助手理解用户意图和上下文的能力。ReALM将引用解析转化为语言建模问题,尤其擅长处理屏幕上的实体,比现有系统提升超5%,性能接近GPT-4但参数更少。其模块化设计易于集成,可在不同场景下扩展。然而,复杂查询处理和依赖上游数据检测器可能影响其准确性和稳定性。
63 6
语言模型在提升智能助手引用解析能力中的创新应用

推荐镜像

更多