C++对象模型学习笔记5 构造/析构/拷贝 语意学

本文探讨C++中抽象类的实现细节,包括纯虚函数的作用、构造与析构过程中的vptr初始化、对象复制与赋值操作的语义学,以及虚拟继承的复杂性。通过具体代码示例,分析了C++类层次结构中不同构造阶段的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

abstract class 探讨

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

有若干的问题:

  1. {P192} 它需要有一个显式的构造函数来初始化对应的成员_mumble, 当然我们可以在派生类中初始化,但如果是这样,可以在基类里提供一个protected的唯一参数构造函数。
  2. {P193} 纯虚函数的存在。C++新手发现,纯虚函数竟然可以定义和调用(被静态地调用)。但要不要这么做全凭设计者自己的意愿。但有一个例外:pure virtual desctrutor: 设计者必须定义它。为什么?因为每一个派生类的析构函数会被编译器加以扩张,以静态方式调用每个virtual base class以及上一层base class的析构函数。因此,主要缺少任何一个base class dtor的定义,会导致链接失败。
  3. {P194} mumble几乎跟派生类的类型无关,并无virtual必要。
  4. {P195} const不应该乱加,virtual function是否需要const需要认真考虑。
    考虑后的代码类似于:
class Abstract_base {
public:
	virtual ~Abstract_base(); // 去掉pure
	virtual void interface() = 0;	// 去掉const
	const char* mumble() const {return _mumble;} // 去掉virt
protected:
	Abstract_base(char* pc=0); // 增加protected构造
	char* _mumble;
};

无继承下的对象构造

  • {P197} 在C中,global被视为一个临时性的定义,放到了bss中。而它在C++中相对不那么重要,C++的所有全局对象都被以“初始化过的数据”来对待。
为继承做准备
  • 并非有ctor的类就必须要合成dtor,主要还是按需,是否满足语义。比如一个class定义了一个virt函数,需要有ctor来初始化vptr,但如果没有特殊需求,它可以没有dtor.

继承体系下的对象构造

{P206} ctor可能内含大量的隐藏码,因为编译器会扩充每一个ctor,扩充程度视class T的继承体系而定。一般而言编译器所做的扩充操作大约如下:

  1. 记录在member init list中的data members初始化操作会被放进ctor的函数本体,并以members的声明顺序为顺序。
  2. 如果有一个mem没出现在mem init list中,但它有default ctor,被调用。
  3. 在那之前,如果class object 有vptr,他们必须被设置为初值,指向适当的vtable。 (后面会解释)
  4. 在那之前,所有上一层的base class ctor必须被调用,以base class的声明顺序为顺序
  5. 在那之前,所有virt base class ctor必须被调用,从左到右,从最深到最浅。
  • {P209} 一个由程序员提供的copy operator中忘记检查自我指派(赋值)操作是否失败,是新手极易陷入的一项错误。

虚拟继承

代码:

class Vertex :: virtual public Point { ... };
class Vertex3d :: public Point3d, public Vertex { ... };
class PVertex : public Vertex3d { ... };
  • {P211} Vectex的ctor必须调用Point的ctor, 然而,当Point3d和Vertex同为Vertex3d的内部sub obj时,他们对Point ctor的调用一定不可发生;取而代之,最底层的Vertext3d需要将Point初始化。而往后,PVertex(不再是Vertex3d)来负责完成“被共享的Point sub obj”的构造。
Vertex3d* Vertex3d::Vertex3d (Vectex3d *this, bool __most_derived, float x, float y, float z){
	if (__most_derived == false)
		this->Point::Point(x,y);
	
	// 调用上一层base class
	// 设定__most_...为false
	this->Point3d::Point3d(false, x, y, z);
	this->Vertex::Vertex(false, x, y, z);
	
	//设定vptr
	//安插user code
	
	return this;
}
vptr 初始化语意学
	我们定义一个PVertex obj, ctor调用顺序是:
	Point(x,y);
	Point3d(x,y,z);
	Vertex(x,y,z);
	Vertex3d(x,y,z);
	PVertex(x,y,z);
  • {P214} 假设这里面每个class都定义了size(),如果每个构造函数都调用size(),会如何决议?在ctor调用virt函数,其函数实例应该是在此class中有作用的那个。
  • 因为从底层往后构造,所以当构造Vertex时,Vertex3d是还没有的,可以在对应的ctor去静态调用。但问题在于这个size()可能继续调用其他virt函数。
  • 那么虚拟机制需要决定其去向。⇒ 需要设置vptr以保证该被构造好的对象能够正确地调用对应的virt functions.
  • {P216} 通常的执行步骤:
    1. 在derived class ctor中,所有的virt base classes以及上一层base class的ctor都被调用。
    2. 上述完成后,对象的vptr被设置。
    3. 处理mem init list。这个必须在vptr设置后,以免有virt函数被调用。
    4. 程序员自身提供的代码

对象复制语意学

  • {P220} 只有在默认行为所导致的语意不安全或者不正确时,才需要设计一个copy assignment operator (memberwise copy及其潜在陷阱)。否则反而执行慢。
  • {P221} copy ctor不应该让我们以为还一定要提供一个copy assignment operator.
  • copy assignment operator有一个非正交情况,那就是它缺乏一个平行于 mem init list的mem assignment list。调用base class的copy assignment operator示例:
	Point::operator=(p3d);
    // or:
	(*(Point*)this) = p3d;

析构语意学

  • {P231} 如果class没有定义dtor,那么只有在class内含的member object(或自己的base class)拥有dtor时,编译器才会自动合成出一个dtor。

  • {P235} destructor被扩展的方式类似constructors被扩展的方式,只是顺序相反:

  1. destructor的函数本体首先被执行。
  2. 如果class拥有member class objects,而后者拥有destructors,那么它们将以声明的相反顺序而调用。
  3. 如果object内含vptr,现在被重新设定,以指向适当base class之virtual table。
  4. 如果有任何直接的(上一层)nonvirtual base classes拥有destructor,它们会以声明的相反顺序而调用。
  5. 如果有任何virtual base classes拥有destructor,而目前讨论的这个class是最尾端(most-derived),那么它们会以原先构造顺序的相反顺序被调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值