《深度探索C++对象模型》阅读笔记 第五章 构造、解构、拷贝语意学

  • const成员变量

const 成员变量的用法和普通 const 变量的用法相似

  • const成员函数

const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值

  • const对象

一旦将对象定义为常对象之后,不管是哪种形式,该对象就只能访问被 const 修饰的成员了(包括 const 成员变量和 const 成员函数),因为非 const 成员可能会修改对象的数据(编译器也会这样假设),C++禁止这样做。

1. 几点类设计原则

1. 即使是一个抽象基类,如果它有非静态数据成员,也应该给它提供一个带参数的构造函数,来初始化它的数据成员。或许你可以通过其派生类来初始化它的数据成员(假如nostatic data member为publish或protected),但这样做的后果则是破坏了数据的封装性,使类的维护和修改更加困难。由此引申,类的data member应当被初始化,且只在其构造函数或其member function中初始化。

2.不要将析构函数设计为纯虚的这不是一个好的设计。将析构函数设计为纯虚函数意味着,即使纯虚函数在语法上允许我们只声明而不定义纯虚函数,但还是必须实现该纯虚析构函数,否则它所有的继承类都将遇到链接错误。一个不能派生继承类的抽象类有什么存在的意义?必须定义纯虚析构函数,而不能仅仅声明它的原因在于:每一个继承类的析构函数会被编译器加以扩展,以静态调用方式其每一个基类的析构函数(假如有的话,不论是显示的还是编译器合成的),所以只要任何一个基类的析构函数缺乏定义,就会导致链接失败。矛盾就在这里,纯虚函数的语法,允许只声明而不定义纯虚析构函数,而编译器则死脑筋的看到一个其基类的
析构函数声明,则去调用它的实体,而不管它有没有被定义。

3.真的必要的时候才使用虚函数,不要滥用虚函数。虚函数意味着不
小的成本,编译很可能给你的类带来膨胀效应:

  • 每一个对象要多负担一个word的vptr。
  • 给每一个构造函数(不论是显示的还是编译器合成的),插入一些代
    码来初始化vptr,这些代码必须被放在所有基类构造函数的调用之后,
    但需在任意用户代码之前。没有构造函数则需要合成,并插入代码。
  • 合成一个拷贝构造函数和一个复制操作符(如果没有的话),并插入
    对vptr的初始化代码,有的话也需要插入vptr的初始化代码。
  • 意味着,如果具有bitwise语意,将不再具有,然后是变大的对象、没
    有那么高效的构造函数,没有那么高效的复制控制。

4.不能决定一个虚函数是否需要 const ,那么就不要它。

**5.决不在构造函数或析构函数中使用虚函数机制。**在构造函数中,每次调用虚函数会被决议为当前构造函数所对应类的虚函数实体,虚函数机制并不起作用。当一个base类的构造函数含有对虚函数vf()的调用,当其派生类derived的构造函数调用基类base的构造函数的时候,其中调用的虚函数vf()是base中的实体,而不是derived中的实体。这是由vptr初始化的位置决定的——在所有基类构造函数调用之后,在程序员供应的代码或是成员初始化队列之前。因构造函数的调用顺序是:有根源到末端,由内而外,所以对象的构造过程可以看成是,从构建一个最基础的对象开始,一步步构建成一个目标对象。析构函数则有着与构造相反的顺序,因此在构造或析构函数中使用虚函数机制,往往不是程序员的意图。若要在构造函数或析构函数中调用虚函数,应当直接以静态方式调用,而不要通过虚函数机制。

2. POD数据类型

Plain Old Data(简旧数据类型) : 能直接以其二进制形式与 C 库交互的数据类型,比如可以直接使用memmove、memcopy等C函数进行复制。

class Point {
    float x,y,z;
};

概念上来讲,对于一段这样的C++代码,编译器会为之合成一个默认构造函数、拷贝构造函数、析构函数、赋值操作符。然而实际上编译器会分析这段代码,并给Point贴上Plain OId Data标签。编译器在此后对于Point的处理与在C中完全一样,也就是说上述的函数都不会被合成。可见概念上应当由编译器合成的函数,并不一定会合成,编译器只有在必要的时候才会合成它们。由此一来,原本在观念上应该调用这些函数的地方实质上不会调用,而是用其它的方法来完成上面的功能,比方复制操作会用bitwise copy(位拷贝)。

3.类的显式列表初始化

struct N{
    int x,y,z;
};
//或者
class N{
public:
    int x,y,z;
};
int main(){
    N a={1,2,3};
	return 0;
}

限制:1)只有成员都是public,且没有构造函数时才能使用,2)只能指定常量。

4.虚拟继承的构造函数

image.png

根据c++ 语法,Point 的初始化应有most-derived class来施行。也就是说当Vertex3d为most-derived class的时候,应当由它的构造函数来调用Point的构造函数初始化Point子对象,Vertex3d的子对象的构造函数对于Point的调用则应当抑制。如果没有抑制会怎么样?当我们定义Vertex3d cv;时,Vertex3d的构造函数中调用Point的构造函数、而随之调用它的子对象,Point3d和Vertex的构造函数中也调用了Point的构造函数。先不说,对于同一个子对象进行三次初始化是否有效率,更重要的是,这将不可避免的带来错误。由Vertex3d指定的子对象Point的值,会被覆盖掉。

编译器通常使用一个条件变量来表示是否为most-derived class,各构造函数根据这个条件变量来决定是否调用虚基类的构造函数,因此通过控制这个条件变量,就可以抑制非most-derived class调用虚基类的构造函数。当然也有其它的方法来做同样的事。

constructor的执行流程

  1. 在derived class constructor,"所有 virtual base classes"及"上一层“base class”的constructors会被调用。
  2. 上述完成之后,对象的vptr(s)被初始化,指向相关的 virtual table(s)。
  3. 如果有member initialization list 的话,将在 constructor 体内扩展开来。这必须在vptr被设定之后才进行,以免有一个virtual member function 被调用。
  4. 最后,执行程序员所提供的码

对象复制语意学

设计一个类,并考虑到要以一个对象指定给另一个对象时,有三种选择:

  1. 什么都不做,采用编译器提供默认行为(bitwise copy或者由编译器合成一个)。
  2. 自己提供一个赋值运算符操作。
  3. 明确拒绝将一个对象指定给另一个对象。(设置为private不定义,或者=delete)

对于第三点,只要将赋值操作符声明为private,且不定义它就可以了。对于第二点,只有在第一点的行为不安全或不正确,或你特别想往其中插入点东西的时候。

编译器的默认行为:如果没有特殊情况,用bitwise copy(展现bitwise copy语意),否则合成赋值运算符。

不使用bitwise copy的四种特殊情况:

  1. 类中含有成员类对象,并且此类对象含有默认构造函数;
    这种情况下,如果没有显示的定义构造函数,那么需要一次构造类中定义的所有成员,当构造成员类对象(member class object)的时候,需要调用此成员类的默认构造函数,所以这时候需要编译器构造出默认的构造函数,来调用成员类的默认构造函数
  2. 类的基类中至少有一个含有默认的构造函数;
    如果没有显式的定义构造函数,同样编译器构造派生类的时候,必然需要调用基类的构造函数,所以需要编译器在派生类中构造出默认的构造函数。
  3. 类中含有虚函数(virtual function);
  4. 类中含有虚基类(virtual base class);
    由于虚拟机制的原因,这两种情况下,需要编译器来完成虚函数表(vbtl)的初始化和虚表指针(vptr)的初始化,所以如果没有显式的定义构造函数,需要编译器构造默认的构造函数。(本身虚拟机制就是从编译器角度来实现的)

对象析构语意学

如果class没有定义 destructor,那么只有在class内带的member object(或是class 自己的base class)拥有destructor的情况下,编译器才会自动合成出一个来。否则,destructor会被视为不需要,也就不需被合成(当然更不需要被调用)。

析构的顺序正好与构造相反:

  1. 本身的析构函数被执行。
  2. 如果member class object拥有析构函数,则以声明的相反顺序调用member object 的析构函数。
  3. 如果对象内含vptr,则重设vptr 指向适当的基类的虚函数表。
  4. 如上一层非虚基类有析构函数,则以声明相反的顺序被调用。
  5. 如果当前类是 most-derived class,那么以构造的相反顺序调用虚基类的析构函数。

析构函数实现策略:维护两份析构函数实例

对于完整对象实例:总是设定好vptr,并调用虚基类析构函数。
基类子对象实例:除非析构函数中调用了虚函数,否则不会设定vptr。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值