C++对象模型——构造,解构,拷贝语意学(第五章)

第5章 构造,解构,拷贝语意学 (Semantics of Construction, Destruction, and Copy)

考虑下面这个abstract base class 声明:

class Abstract_base {
public:
	virtual ~Abstract_base() = 0;
	virtual void interface() const = 0;
	virtual const char * mumble() const { return _mumble; }
protected:
	char *_mumble;
};

有什么问题?虽然这个 class 被设计为一个抽象的base class(其中有pure virtual function,使得Abstract_base不可能拥有实体),但它仍然需要一个明确的构造函数以初始化其data member _mumble.如果没有这个初始化操作,其derived class
的局部性对象_mumble将无法决定初值
,例如:

class Concrete_derived : public Abstract_base {
public:
	Concrete_derived();
};
void foo() {
	// Abstract_base::_mumble未被初始化
	Concrete_derived trouble;
}

可能说是:也许Abstract_base的设计者试图让其每一个derived class 提供_mumble的初值.然而如果这样,derived class 的唯一要求就是Abstract_base必须提供一个带有唯一参数的 protected constructor:

Abstract_base::Abstract_base(char *mumble_val = 0) : _mumble(mumble_value)
{}

一般而言,class 的data member应该初始化,并且只在constructor中或是在 class 的其他member functions中指定初值,其他任何操作都将破坏封装性质,使 class 的维护和修改更加困难.

当然也可能是说设计者的错误并不在于未提供一个 explicit constructor,而是不应该在抽象的base class 中声明data member.这是比较强而有力的论点(把interface和implementation分离),但它并不是行遍天下皆有理,因为将"被共享的数据"抽取出来放在base class 中,毕竟是一种正当的设计.

纯虚拟函数的存在 (Presence of Pure Virtual Function)

    C++可以定义和调用(invoke)一个pure virtual function;不过它只能被静态地调用(invoke statically),不能经由虚拟机制调用.例如,可以合法写下这段代码:

// OK:定义pure virtual function,但只能被静态地调用(invoked statically)
inline void Abstract_base::interface() const {
	function
	// ...
}
inline void Concrete_derived::interface() const {
	// OK:静态调用(static invocation)
	Abstract_base::interface();
	function
	// ...
}

要不要这样做,全由 class 设计者决定.唯一的例外就是pure virtual destructor:class 设计者一定得定义它.为什么?因为每一个derived class destructor会被编译器加以扩展,以静态调用的方式调用其"每一个virtual base class"以及"上一层base
class"的destructor.因此,只要缺乏任何一个base class desstructors的定义,就会导致链接失败.

难道对一个pure virtual destructor的调用操作,不应该在"编译器扩展derived class的destructor"时压抑下来吗?不!class 设计者可能已经真的定义了一个pure virtual destructor(就像上一个例子中定义了Abstract_base::interface()那样).这样的设计是以C++语言的一个保证为前提:继承体系中每一个 class object的destructor都会被调用,所以编译器不能够压抑这个调用操作.

    一个比较好的替代方案就是,不要把 virtual destructor声明为pure.

虚拟规格的存在 (Presence of a Virtual Specification)

如果决定把Abstract_base::mumble()设计为一个 virtual function,那将是一个糟糕的选择,因为其函数定义内容并不与类型有关,因而几乎不会被后继的derived class 改写.此外,由于它的non-virtual 函数实体是一个 inline 函数,如果常常被调用的话,效率上的负担实在不低.

然而,编译器难道不能经由分析,知道该函数只有一个实体存在于 class 层次体系中?果真如此的话,难道它不能够把调用操作转换为一个静态调用操作(static invocation),以允许调用操作的 inline expansion?如果 class 层次体系陆续被加入新的 class,带有这个函数的新实体,又当如何?是的,新的 class 会破坏优化.该函数现在必须被重新编译(或是产生第二个--也就是多态--实体,编译器将通过流程分析哪一个实体要被调用).不过,函数可以以二进制形式存在与一个library中.

一般而言,把所有的成员函数都声明为 virtual function,然后再靠编译器的优化操作把非必要的 virtual invocation去除,并不是好的设计观念.

虚拟规格中的 const 的存在

决定一个 virtual function是否需要 const,似乎是件琐碎的事情.但当真的面对一个abstract base class 时,却不容易做决定.做这件事情,意味着得假设subclass实体可能被无穷次数地使用.不把函数声明为 const,意味着该函数能够获得一个 const reference或 const pointer.

重新考虑 class 的声明

由前面的讨论可知,重新定义Abstract_base如下,才是比较适当的一种假设:

class Abstract_base {
public:
	virtual ~Abstract_base();						// 不再是pure
	virtual void interface() = 0;
	const char *mumble() const { return _mumble; }	// 不再是virtual
protected:
	Abstract_base(char *pc = 0);					// 新增一个带有唯一参数的constructor
	char *_mumble;
};

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-14 11:45:50

C++对象模型——构造,解构,拷贝语意学(第五章)的相关文章

C++对象模型——解构语意学(第五章)

第6章    执行期语意学 (Runtime Semantics) 想象一下有下面这个简单的式子: if (yy == xx.getValue()) // ... 其中xx和yy定义为: X xx; Y yy; class Y的定义为: class Y { public: Y(); ~Y(); bool operator==(const Y &) const; }; class X定义为: class X { public: X(); ~X(); operator Y() const; // c

《Linux4.0设备驱动开发详解》笔记--第十五章:Linux I2C核心、总线与设备驱动

15.1 Linux I2C体系结构 I2C核心 I2C核心提供了I2C总线驱动和设备驱动的注册.注销的方法,I2C通信(Algorithm)方法上层的与具体适配器无关代码以及探测设备.检测设备地址的上层代码等 I2C总线驱动 是对I2C体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部 总线驱动包含I2C适配器数据结构i2c_adapter.I2C适配器的Algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数 I2C设备驱动 它是对I

构造、解构、拷贝语意学

一 "无继承"情况下的对象构造 考虑下面程序片段: 1 2 3 4 5 6 7 8 9 10 11 Point glocal; //全局内存配置 Point foobar() {    Point local;//局部栈内存配置    Point *heap=new Point;//heap内存配置    *heap=local;        delete heap;    return local; } 1 把Point类写成c程序,c++标准说这是一种所谓的Plain OI Da

深度探索C++对象模型 第五章 构造、析构、拷贝语意学

1. const 成员函数需要吗? 尽量不要,如果存在继承,则无法预支子类是否有可能改变data member 2. pure virtual constructor 可以实现类的隐藏吗(包含data member)?   这样子类无法调用base 的构造函数对数据初始化,所以可以用protected来实现构造函数,可以实现子类调用: 3. 如果class中存在virtual function,则编译器会再构造函数中对vptr进行初始化(在base构造函数调用之后,而代码实现之前) 4.拷贝构造

C++对象模型——对象的构造和解构(第六章)

6.1    对象的构造和解构 (Object Construction and Destruction) 一般而言,constructor和destructor的插入如预期所示: { Point point; // point.Point::Point() 一般而言会被插入在这里 ... // point.Point:;~Point() 一般而言会被插入在这里 } 如果一个区段(以{}括起来的区域)或函数中有一个以上的离开点,情况会稍微混乱一点.Destructor必须被放在每一个离开点(当时

解构控制反转(IoC)和依赖注入(DI)

1.控制反转 控制反转(Inversion of Control,IoC),简言之就是代码的控制器交由系统控制,而不是在代码内部,通过IoC,消除组件或者模块间的直接依赖,使得软件系统的开发更具柔性和扩展性.控制反转的典型应用体现在框架系统的设计上,是框架系统的基本特征,不管是.NET Framework抑或是Java Framework都是建立在控制反转的思想基础之上. 控制反转很多时候被看做是依赖倒置原则的一个同义词,其概念产生的背景大概来源于框架系统的设计,例如.NET Framework

scala 模式匹配详解 3 模式匹配的核心功能是解构

http://www.artima.com/scalazine/articles/pattern_matching.html这篇文章是odersky谈scala中的模式匹配的一段对话,我做了部分片段翻译(不是连贯的): 模式可以嵌套,就像表达式嵌套,你可以定义深层的模式,通常一个模式看起来就像一个表达式.它基本上就是同一类事情.它看起来像一个复杂的对象树构造表达式,只是漏掉了new关键字.事实上在scala当你构造一个对象,你不需要new关键字然后你可以在一些地方用变量做站位符替代对象树上实际的

ES6 对象解构赋值(浅拷贝 VS 深拷贝)

对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中. 拷贝对象 let aa = { age: 18, name: 'aaa' } let bb = {...aa}; console.log(bb); // {age: 18, name: "aaa"} 合并对象 扩展运算符(...)可以用于合并两个对象 let aa = { age: 18, name: 'aaa' } let bb = { sex: '男' } let cc = {...aa, ...bb

es6学习 -- 解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring). 以前,为变量赋值,只能直接指定值. let a = 1; let b = 2; let c = 3; ES6 允许写成下面这样. let [a, b, c] = [1, 2, 3]; 上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值. 本质上,这种写法属于"模式匹配",只要等号两边的模式相同,左边的变量就会被赋予对应的值.下面是一些使用嵌套数组进行解构的例子. 我认为