【C++】深度探索C++对象模型之构造、析构、拷贝语意学

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

一、纯虚函数的存在

可以定义和调用一个pure virtual function, 不过只能被静态调用(invoked statitcally),不能经由虚拟机制调用

Abstract_base::interface();
如上述,Abstract_base是一个虚基类。这只取决于类设计者要不要这么做。

但是对于pure virtual destrcutor, 类设计者一定要定义它,因为每个drived class destructor会被编译器加以扩张,然后以静态方式进行调用"每一个virtual base class”以及“上一层base class”的destructor。

所以一般比较好的处理方式,不要把virtual destructor声明为pure。


二、“无继承”情况下的对象构造

有三种对象产生的方式:global内存控制,local内存配置和heap内存配置。


两种初始化方式

Point local1 = {1.0, 1.0, 1.0};

Point local2;
local2._x = 1.0;
local2._y = 1.0;
local2._z = 1.0;
local1对应是explicit initialization list,这比local2声明有效的多。因为当函数的activation record放进程序堆栈时,上述的initialization list中的常量就被放进local内存中了。  并且,explicit initialization list只有当类数据成员声明为public时才有效。


三、为继承做准备

当一个类声明多了virtual function,也就是可以进行操作的动态决议时,会有如下变化:

  • 每个class object多负担一个vptr
  • 所定义的contructor将发生膨胀,被附加一些代码以便将vptr进行初始化
  • 合成一个copy constructor和copy assignment operator,主要是由于vptr的存在,bitwise copy可能会发生问题。
Point foobar()
{
    Point local;
    // ...
    return local;
}
会被转化为如下:
Point foobar( Point &_result )
{
    Point local;
    // ...stuff...
    _result.Point::Point(local); //调用copy constructor
    local.Point::~Point();
    return;
}
如果设计的类中,函数需要以传值方式(by value)传回一个local class object,最好提供一个copy construtor。

四、继承体系下的对象构造

T object;
编译器会扩充每一个constructor,扩充程度视class T的继承体系而定。大致扩充如下:
  • 记录在member initialization list中的data members初始化操作会被放进constructor的函数本体,并以members的声明顺序为顺序。
  • 如果member没有出现在initialization list中,但有一个default constructor,那么default constructor必须背调用。
  • 如果class object有vptr,则必须设定初值指向适当的virtual table。
  • 所有的上层base class constructors必须被调用,以base class的声明顺序为顺序。
  •                  如果base class被列于member initialization list中,那么任何显示指定的参数都需要被传递过去
  •                  如果没有列于上述,则调用defualt constructor或default memberwise copy constructor。
  •                  如果base class是多重继承下的第二或后继的base class,那么this指针需要有所调整
  • 所有的virtual base class constructor必须被调用,从左至右,从深到浅。
  •                  1. 如4(1)
  •                  class的每一个virtual base class subobject的偏移位置必须在执行期可被存取。
  •                  支持最底层的constructor的某种调用机制需要放进来。

如果Point destructor是inline函数,每一个调用操作在调用地点会被扩展开来。

Line b = a;
被写出来时,implicit Line copy constructor会被合成出来,成为一个inline public member
a = b;
被写出来时,implicit copy assignment operator 会被合成出来,成为一个inline public member。

五、虚拟继承

对于虚拟继承,越是往后的derive class,其相应就需要完成对共享基类对象的构造。这是顶端派生类所干的事。同时,在扩展中还需要将标记,使得基类对象只被实例化一份。而后继的类不需要再实例化共享基类。
只有当一个完整的class object被定义出来,virtual base class constructor才能被调用,如果object只是某个完整的object的suboject,它就不会被调用。

一般而言,一个class的constructor中,经由构造中的对象来调用一个virtual function,其函数实例化应该是此class中有作用的那一个。
constructor的调用顺序,从根源到末端, 由内向外,也就是从基类到派生类方向。

在构造过程中,对于每次调用,该如何决议 ==> 需要对每个调用操作以静态方式决议,不要用到虚拟机制。也就是类似
Point3d::size();
同时,如果size中需要调用其他函数实例,这个时候急需要经由virtual机制决定其动态。

那到底什么时候是以静态方式调用,什么时候以虚拟机制调用?
根本解决之道,在执行一个constructor时,必须限制一组virtual function候选名单。换一句话说,就是为了控制一个class中的有所作用的函数,编译系统需要控制住vptr的初始化和设定操作。

vptr的初始化操作应该如何处理?
每一个constructor都一直等待到其base class constructor 执行完毕之后才设定其对象的vptr,那么每次就能够调用到正确的virtual function实例。

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. 执行程序猿的代码。

六、对象复制语意学

只有在默认行为所导致的语意不安全或不正确时,我们才需要设计一个copy assignment operator。
如果不对类提供拷贝复制符,那么编译就和copy constructor的情形一样,不会产生实例。

什么时候不会表现出bitwise copy?
  1. 当一个class内含一个member object,而其class有一个copy assignment operator
  2. 当一个class的base class有一个copy assignment operator
  3. 当一个class声明了任何virtual function(因为不能拷贝右端的vptr)
  4. 当一个class继承自一个virtual base class。
copy assignment 缺乏member assignment list,也就是如下性质是不支持的:
inline Point3d&
Point3d::operator=( const Point3d &p3d ) : Point( p3d ), z ( p3d._z ) {}

一种方法可以保证most derived class完成virtual base class subobject的copy行为,也就是在derived class 的 copy assignment operator函数实例最后,显示调用operator。如:
inline Vertex3d &
Vertex3d::operator=( const Vertex3d &v )
{
    this->Point3d::operator=(v);
    this->Vertex::operator=(v);
    this->Point::operator=(v);
}

七、析构语意学

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

一个destructor被扩展的方式类似constructor,但顺序相反。
  • 如果object内含一个vptr,那么首先重设相关的virtual table 3
  • destructor的函数本体现在被执行,也就是说vptr会在程序员的代码执行前被重设 1
  • 如果class拥有member class objects,而后者拥有destructor,那么它们会以其声明的相反顺序被调用 2
  • 如果有任何直接的nonvirtual base classes 拥有 destructor,它们会以其声明的顺序的相反顺序被调用 4
  • 如果有任何virtual base classes拥有destructor,而目前这讨论的这个class是最尾端(most derived)的class,那么她们会以其相反的构造顺序的相反顺序被调用。 5


参考:

侯捷 深度探索C++对象模型



深入探索C++对象模型-构造、解拷贝语意学 如果把将一个共同变量加到了接口类中,那么这个类也就是纯虚类了,纯虚类需要定义构造函数来完成这个共同变量的初始化。到底要不要把这个公共变量提取出来下放到接口类中,这一点并无一个标准到底怎么做好。纯虚类中,正常来说那些函数我们都无需定义,除了一个函数之外,纯虚函数。由于后续继承的子类在的过程中会层层调用上级的函数,因此缺乏任何一个base class的destructor的定义,就会导致链接失败。因此,定义函数时,不要将其定义为纯虚函数,将其定义为虚函数。 阅读详情

相关推荐

深度探索C++对象模型——5_构造拷贝语义学

深度探索C++对象模型

wogan101的博客 284

深度探索c++对象模型》第二章笔记

有四种情况,会导致“编译器必须为未声明constructor 之 classes合成--个default constructor”.C++ Stardand 把那些合成物称为implicit nontrivial defaultconstructors。

StudyWinter的博客 1752

c++ 类的 构造函数,explicit,初始化列表看这一篇就够了

c++ 类的 构造函数,explicit,初始化列表看这一篇就够了构造函数多个构造函数函数默认参数隐式转换和explicit构造函数初始化列表 构造函数 构造函数:在类中有一种特殊的成员函数,他的名字和类名相同,这个特殊的成员函数会被系统自动调用,这个成员函数叫做构造函数。因为构造函数会被系统自动调用,所以我们可以理解成构造函数的目的就是初始化类对象的数据成员。 构造函数没有返回值。构造函数的特殊之处。 不可以手动调用构造函数,否则编译出错。 正常情况下,构造函数应该被声明为public。因为我们创建一个

u014339447的博客 1285

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

1、 一般而言,class的data member应该被初始化,而且只在constructor中或其它member functions中初始化,其它任何操作都将破坏其封装性质,使其维护和修改更加困难。2、 可以定义并调用一个pure virtualfunction,但它只能被静态调用,不能经由虚拟机制调用。3、 class设计者一定得定义pure virtual destructor,因为每一个d...

stt2019 370

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

1.不要把函数声明为纯虚函数 因为如果这个类被继承了,那么子类在时,是会调用父类的函数的,而如果纯虚函数被调用,就会终止程序(这个不同编译器不一样) 2.指针取地址上的内容和指针指向对应地址的区别 在看书过程中看到这样一段代码: 本来我以为delete是有问题的,因为我理解成了这样: heap=&local 但是这两者是有区别的。书上那样的写法,其实是使...

qq_34489443的博客 253

深度探索C++对象模型:5.构造拷贝语意学

第五章:构造拷贝语意学 考虑下面这个abstract base class声明: class Abstract_base{ public:     virtual ~Abstract_base() = 0;     virtual void interface() const =0;     virtual const char*         mumble()

walkerkalr的专栏 1397

深度探索C++对象模型》--5 构造拷贝 && 6 执行期语意学

深度探索C++对象模型》--5 构造拷贝语意学 1、纯虚函数: (1)C++可以定义和调用一个纯虚函数,不过只可以静态调用,不可以由虚拟机制调用。 注意:pure virtual destructor一定得定义,因为每一个derivedclass destructor会被编译器加以扩张,以静态方式调用其每一个virtual base class及上一个ba

KUAILE123的专栏 1158

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

深度探索C++对象模型》读书笔记第五章:构造拷贝语意学 对于abstract base class(抽象基类),class中的data member应该被初始化,并且只在constructor中或者在其他member functions中指定初值。 纯虚函数的存在 纯虚函数,虚函数在声明后加上 = 0,纯虚函数的意义在于只定义一个可继承的接口,并没有实现(虚函数意义在于定义一个可继...

yhcuac 1022

深度探索C++对象模型》读书笔记之构造拷贝语意学

1、  继承体系下的对象构造过程是怎样的? 1>    调用所有的虚基类构造函数,从左到右,由最深到最浅(别忘记虚基类在对象模型中是以独特的方式支持的,不涉及到在对象模型中的偏移量的问题) 2>    调用所有的上一层的基类构造函数,以基类的声明顺序为顺序(这是因为一般基类的subobject都会被放在object的开始,并且按基类声明的次序放置) 3>    如果class object有

Al_xin的专栏 816

深度探索C++对象模型读书笔记】【第5章】构造拷贝语意学

1、 一般而言,class的data member应该被初始化,而且只在constructor中或其它member functions中初始化,其它任何操作都将破坏其封装性质,使其维护和修改更加困难。 2、 可以定义并调用一个pure virtualfunction,但它只能被静态调用,不能经由虚拟机制调用。 3、 class设计者一定得定义pure virtual destructor,因为

ruan875417的专栏 822

深度探索C++对象模型(五)构造拷贝语意学

考虑下面这个abstract base class声明: class Abstract_base{ public: virtual ~Abstract_base()=0; virtual void interface() const =0; virtual const char* mumble() const {return _mumble;} protected:

Super的博客 998

C++-----深度探索C++对象模型-第五章-构造拷贝语意学(二)

1、虽然一个类的函数是虚函数,但是其调用操作员静态的决议出来的。 2、构造函数的函数本体必须条件式的测试传进来的参数,然后决定调用或不调用相关的虚基类构造函数。由最底层的派生类进行虚基类的构造函数的调用,这种机制会压制中间层级类中构造函数对虚基类构造函数的调用。(菱形继承,最底层的派生类会调用最上层虚基类的构造函数,而在调用中间两个基类构造时,将参数设置为flase,这样中间两个基类的构造函...

一个人的学习和碎碎念 329

深度探索C++对象模型》学习笔记

深度探索C++对象模型》学习笔记 最近学习了c++对象模型,做下笔记,方便日后查阅 第一章:关于对象 c++对象模型 在此模型中,成员变量被配置在每一个对象中,静态成员变量、成员函数和静态成员函数放置在对象之外,虚函数则以下两个步骤支持: 每个类产生一堆指向虚函数的指针放在表格之中,这个表格叫做虚函数表(vtal) 每个对象中被添加一个指针(vptr),指向相关的虚函数表,vptr的赋值和重置由每个类的构造函数、函数和拷贝赋值运算符自动完成。每个类所关联的type_info放在虚函数表的表头

QT练手小程序 459

C++-----深度探索C++对象模型-第五章-构造拷贝语意学(一)

1、拥有纯虚函数的基类不可能有实例对象。 2、一般来说,类的数据成员应该被初始化,并且只在构造函数中或类的其他成员函数中指定初值,其他操作都将破坏封装特性。 3、C++中,纯虚函数是可以定义和调用的,但是只能被静态的调用,不能经过虚拟机制来调用。 4、不要将虚函数定义为纯虚函数,定义为虚函数比较好。因为在每一个派生类的函数都会被编译器加以扩张,以静态调用的方式调用其每一个虚基类以及上...

一个人的学习和碎碎念 280

深度探索C++对象模型复习和学习 第五章:构造拷贝语意学

待续

学峰的学习笔记 436

阿龙的学习笔记---《深度探索C++对象模型》读书笔记(五)--- 构造拷贝语意学

一个 pure virtual destructor 纯虚函数 需要被定义,因为编译器会在derived类中静态调用他。所以一般不要把虚函数声明为纯虚。 不要把不会呗重写的函数声明为虚函数,因为效率上没有静态函数高,并且不能被inline所优化。 虚函数在设计为const的时候,需要谨慎,因为你不知道derived class 会不会修改成员变量,所以建议是最好不要声明为const。 无继承情况下的构造 Plain Old Data (POD) POD 指的是这样一些数据类型:基本数据类型、指.

阿龙的博客 147
上一篇: 【C++】深度探索C++对象模型之站在对象模型的顶端
下一篇: 【C++】深度探索C++对象模型之Function语意学
小do在努力
博客等级 码龄15年 58粉丝 161原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值