C/Cpp / 对象模型探索
介绍 Cpp 对象模型相关知识。
Ruo_Xiao
己所不欲,勿施于人。
展开
专栏收录文章
- 默认排序
- 最新发布
- 最早发布
- 最多阅读
- 最少阅读
-
C/Cpp / 如何定义一个只能在堆上(栈上)生成对象的类?
在 C++ 中,类的对象建立分为两种,一种是静态建立,如 A a;另一种是动态建立,如 A* ptr = new A;这两种方式是有区别的。 静态建立一个类对象,是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。使用这种方法,直接调用类的构造函数。 动态建立类对象,是使用 new 运算符将对象建立在堆空间中。这个过程分为两步,第一步是执行 operator new() 函数,在堆空间中搜索合适的内存并进行分配;第二步是调用构造函数转载 2021-04-06 11:55:52 · 523 阅读 · 0 评论 -
C/Cpp / Cpp 中 struct 和 class 区别
答案 默认的继承访问权限不同。struct 是 public 的,class 是 private 的。 原因 struct 作为数据结构的实现体,它默认的数据访问控制是 public 的。 class 作为对象的实现体,它默认的成员变量访问控制是 private 的。 (SAW:Game Over!) ...原创 2021-04-06 11:33:00 · 537 阅读 · 0 评论 -
C/Cpp / C++ 构造函数和析构函数可以是虚函数吗
答案 构造函数不可以是虚函数,而析构函数可以且常常是虚函数。 原因 1、构造函数不可以是虚函数 当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储成员函数指针的数据结构。 虚函数表是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中,当存在虚函数时,每个对象都有一个指向虚函数的指针(vptr指针)。在实现多态的过程中,父类和派生类都有vptr指针。 vptr的初始化:当对象在创建时,由编译器对 vptr 指针进行初始化。在定义子类对象时,vptr 先指向转载 2021-04-06 10:52:56 · 1243 阅读 · 0 评论 -
C/Cpp / 模板类中可以使用虚函数吗?模板成员函数可以是虚函数吗?
一、答案 前者是可以的,后者是不可以的。 #include <iostream> template <class T> class Base { public: virtual void f1() { std::cout << "Base'f1 is called." << std::endl; } virtual void f2() = 0; }; template <class T> class Dev : pub原创 2021-04-06 10:38:35 · 810 阅读 · 0 评论 -
C/Cpp / 虚函数是否可以用 inline 修饰
答案 可以。 但是 ...... inline 修饰虚函数不是所有情况均可以生效的,必须满足以下两点: 虚函数被当做普通函数调用。 通过对象调用则是将虚函数当做普通函数调用; 若通过指针或者应用调用,则将通过多态调用虚函数。 详情 虚函数体足够小。 原因 内联是在编译期间建议编译器内联,而虚函数的多态性是在运行期间。编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。 (SAW:Game Over!) ...原创 2021-04-06 10:14:52 · 340 阅读 · 1 评论 -
C/Cpp / 虚函数是否可用 static 修饰
答案 不能。 原因 因为在多态时,父类调用虚函数的方法是使用 this 指针找到虚函数表指针,再通过虚函数表指针获取到虚函数表,最后通过指针偏移获取实际的虚函数的指针,完成调用即可。 所以根据上述步骤,若没有 this 指针,是没有办法找到虚函数表指针的,也就无从谈起多态。 (SAW:Game Over!) ...原创 2021-04-06 09:40:27 · 491 阅读 · 0 评论 -
Cpp 对象模型探索 / 不能被继承的类
两种方法 C++ 11 final 关键字 友元类 + 虚继承。 栗子 class A { private: A() {} friend class B; }; class B : virtual public A { public: int m_b; }; 我们的目的是让 B 成为一个不能被继承的类。分析代码,由于 B 是 A 的友元,所以即使 A 的构造函数...原创 2019-12-29 11:48:49 · 261 阅读 · 0 评论 -
Cpp 对象模型探索 / 带有虚继承类的构造函数的调用顺序
栗子 #include <iostream> class A { public: A() { std::cout << "A" << std::endl; } }; class B : public A { public: B() { std::cout << "B" << std::endl; } }; class ...原创 2019-12-29 11:16:28 · 818 阅读 · 1 评论 -
Cpp 对象模型探索 / 外部调用私有的虚函数的方法
答案 通过虚函数表指针找到虚函数表,再通过虚函数表中的元素,即:函数指针,得到私有函数的首地址。 示例 #include <iostream> class A { private: virtual void func() { std::cout << "func()" << std::endl; } }; using PFUNC = voi...原创 2019-12-29 09:52:30 · 214 阅读 · 0 评论 -
Cpp 对象模型探索 / 静态局部对象只构造一次的原因和执行析构的方法
代码 class A { public: A() {} ~A() {} }; void func() { static A a; } int main() { func(); return 0; } 问题 1、函数中的局部静态对象只执行一次构造函数的原因。 答案: 因为编译器为该局部对象设置了标志位,来表示该对象是否已经执行了构造函数。 详情可以通过汇编代码来查看,如下...原创 2019-12-19 09:38:31 · 645 阅读 · 0 评论 -
Cpp 对象模型探索 / 编译器为对象创建缺省析构函数的条件
1、基类中含有析构函数的子类,编译器为子类创建析构函数。 2、类成员变量是类对象,该类对象含有析构函数,则编译器为子类创建析构函数。 代码 class Parent { public: ~Parent() {} }; class Son : public Parent { }; int main() { Son sn; return 0; } 分析 使用 dumpbin /a...原创 2019-12-16 19:07:17 · 189 阅读 · 0 评论 -
Cpp 对象模型探索 / 父类和子类调用构造函数和析构函数的顺序
2018-03-19 创建人:Ruo_Xiao 邮箱:xclsoftware@163.com 建立对象时,会先调用父类的构造函数再调用子类的构造函数。 销毁对象时,会先调用子类的析构函数再调用父类的析构函数 ...原创 2018-03-19 17:17:14 · 4008 阅读 · 3 评论 -
Cpp 对象模型探索 / 虚继承带虚函数的基类的子类的内存布局
源码 class Base { public: Base() {} virtual void func() {} int bi_; }; class Son:virtual public Base { public: int i_; }; int main() { Son s; s.bi_ = 3; s.i_ = 9; return 0; } 分析 内存布局图如下:...原创 2019-12-10 07:16:47 · 194 阅读 · 0 评论 -
Cpp 对象模型探索 / 多重继承下基类指针释放子类对象的原理说明(虚析构函数的作用)
源码 #include <iostream> class Base1 { public: virtual void func_1_1(){ std::cout << "Base1::func_1_1()" << std::endl; } virtual void func_1_2(){ std::cout << "Base1::...原创 2019-12-07 11:09:11 · 581 阅读 · 0 评论 -
Cpp 对象模型探索 / 多态的本质
普通成员函数的调用方式是直接通过编译期间确定的函数地址来调用。 多态是通过查询对象的虚函数表来获取虚函数的地址。因为像工厂模式这样,并不能在编译期间知道基类指向的是哪个子类,也就导致了不能在编译期间获取到函数的首地址,也就是不能实现动态绑定。 实际上动态绑定的实现原理是,在程序执行的时候,将子类的虚函数表地址赋值给基类对象的虚函数表指针,再通过查询虚函数表的方式获取最新的函数地址,从而完成了动...原创 2019-12-05 20:37:27 · 334 阅读 · 0 评论 -
Cpp 对象模型探索 / 类静态成员函数的调用方式
栗子: class CA { public: static void func() {} }; int main() { CA A; A.func(); CA::func(); return 0; } 查看汇编代码,如下: 12: A.func(); 001E20F8 call CA::CA (01E14C4h) 13: CA::f...原创 2019-12-04 20:57:31 · 1100 阅读 · 0 评论 -
Cpp 对象模型探索 / 虚函数的调用方式
虚函数有两种调用方式: 方案1,直接使用函数地址调用。 方案2,通过对象的虚函数表指针找到虚函数表,从而得到函数地址,完成调用。 应用场景主要有如下三种情况: 当对象直接调用时,采用方案 1 调用虚函数。 当对象的指针或者引用调用时,采用方案 2 调用虚函数。 类成员函数中调用虚函数。 若是直接调用,则走方案 2 路线。 若是使用 类名::虚函数,则走方案 1 路线。 ...原创 2019-12-04 14:00:49 · 549 阅读 · 0 评论 -
Cpp 对象模型探索 / 类普通成员函数的调用方式
C++设计时有一个要求,类普通成员函数的调用性能要和全局函数差不多。所以编译器在处理类的普通成员函数的宗旨是将其当作全局函数来处理。 为了达到上述目的,编译器会对类的普通成员函数进行如下操作: 在函数形参中第一位插入类对象的 this 指针。 修改函数名称,使之与类名等信息挂钩。这样也就解决了既然将普通成员函数当作全局函数,但是各个类中有重名函数的情况。 栗子: clas...原创 2019-12-04 06:58:51 · 351 阅读 · 0 评论 -
Cpp 对象模型探索 / 对象访问成员变量的原理
一、栗子 1、源码 #include <iostream> #include <stdio.h> class Base { public: Base() { std::cout << "Base 的 this 指针:" << this << std::endl; } public: int bi_; }; class A :...原创 2019-12-03 07:10:18 · 306 阅读 · 0 评论 -
Cpp 对象模型探索 / 单一继承的类的内存布局
目录 1、父类和子类都没有虚函数 2、父类有虚函数、子类没有虚函数 3、父类没有虚函数,子类有虚函数 4、父类和子类都有虚函数 5、总结 #include <iostream> class Base { public: int bi_; int bk_; }; class A : public Base { public: int i_; }; int mai...原创 2019-11-21 09:06:35 · 252 阅读 · 0 评论 -
Cpp 对象模型探索 / 类引入虚函数有哪些成本?
编译时会为类对象引入虚函数表。 类对象中会产生虚函数表指针 vptr,用于指向虚函数表。 在对象的构造函数中插入向 vptr 赋值虚函数表的首地址的代码。 若是多重继承,每个父类均有虚函数,那么子类会继承每一个父类的 vptr。子类若也有虚函数,则将该虚函数的地址添加到第一个父类的虚函数表中。 析构函数中也增加了虚函数表指针 vptr 赋值虚函数表首地址的代码。 (SAW:Game Ov...原创 2019-11-21 08:46:33 · 239 阅读 · 0 评论 -
Cpp 对象模型探索 / 成员初始化列表
目录 一、何时必须使用? 二、优势 三、细节探究 一、何时必须使用? 成员变量是 const 类型。 成员变量是引用。 基类中含有带形参的构造函数且不存在默认的构造函数。 成员变量是类对象,该对象含有带形参的构造函数且不存在默认的构造函数。 二、优势 #include <iostream> class X { X(int value = 0) :m_i(val...原创 2019-11-18 09:20:41 · 393 阅读 · 0 评论 -
Cpp 对象模型探索 / 深浅拷贝
2018-04-19 创建人:Ruo_Xiao 开发环境:VS2019 邮箱:xclsoftware@163.com 一、定义 浅拷贝:仅仅是对两个指针变量中的内容进行了赋值,即:两个指针指向的是同一个内存区域。 深拷贝:将一个指针按照另一个指针进行申请内存,即:两个指针指向的是不同的内存区域。 二、分析 #include <iostream> class CA { ...原创 2019-11-15 07:15:23 · 349 阅读 · 0 评论 -
Cpp 对象模型探索 / 拷贝构造函数 和 赋值构造函数 的调用时机
class A { public: A() {}; A(const A& obj) {} A& operator=(const A& obj) { return *this; } }; int main() { A a1, a2; // 调用 构造函数 。 A a3 = a1; // 调用 拷贝构造函数 。 a3 = a2; // 调...原创 2019-11-13 06:56:44 · 311 阅读 · 0 评论 -
Cpp 对象模型探索 / 程序转化语义
一、定义时初始化 源码 #include <iostream> using namespace std; class A { public: A(){std::cout << "构造函数" << std::endl;} ~A(){std::cout << "析构函数" << std::endl;} public: A(con...原创 2019-11-13 06:46:55 · 150 阅读 · 0 评论 -
Cpp 对象模型探索 / 虚基类表作用
一、结论 虚基类表的作用是帮助编译器找到该类中的虚基类中各个成员变量在内存布局中的位置。虚基类表中的值是偏移值,即:各个虚基类的成员变量在子类中的内存布局中相对于虚函数指针的偏移值。 二、栗子 1、唯一父类是虚基类 (1)源码 class Grand { public: int i_grand_; }; class Parent : public virtual ...原创 2019-11-05 07:24:34 · 502 阅读 · 0 评论 -
Cpp 对象模型探索 / 含有虚基类的类的内存布局
一、栗子 class Grand { public: int i_grand_ = 8; }; class Parent1 : public virtual Grand { public: int i_parent1_ = 9; }; class Parent2 : public virtual Grand { public: int i_parent2_ = 10; }; cla...原创 2019-11-04 07:18:29 · 278 阅读 · 0 评论 -
Cpp 对象模型探索 / 系列文章的索引
一、对象 普通类对象占用的空间。 子类的内存布局。 编译器为对象创建缺省构造函数的条件。 二、虚函数(完) 对象的虚函数表指针的位置。 继承关系下的虚函数手动调用。 虚函数表和虚函数表指针的创建时机。 多重继承虚函数表分析。 静态联编和动态联编。 三、数据语义 含有虚基类的类的内存布局。 四、函数语义 五、对象构造语义 new 运算符内...原创 2019-11-03 07:16:56 · 188 阅读 · 0 评论 -
C++对象模型探索 / 普通类对象占用的空间
一、空类的大小 #include <iostream> class A{}; int main() { A obja; std::cout << "obja 的地址:" << &obja << std::endl; std::cout << "obja 的大小:" << sizeof(...原创 2019-09-07 14:59:37 · 358 阅读 · 0 评论 -
C++对象模型探索 / 子类的内存布局
一、栗子 #include <iostream> class Father { public: Father() { std::cout << "I am father,this is " << this << std::endl; } public: void func_father() ...原创 2019-10-14 11:29:22 · 392 阅读 · 0 评论 -
Cpp 对象模型探索 / 编译器为对象创建缺省构造函数的条件
零、前言 书本上常说,编译器会给没有任何构造函数的类自动创建一个缺省的构造函数(没有形参的构造函数)。但是事实上不是这样么?栗子: class A { public: int i; }; int main() { A a; return 0; } 编译上述代码,生成 test1.obj 文件。 这里为了检查编译器是否为我们创建了 a的构...原创 2019-10-15 22:45:05 · 399 阅读 · 0 评论 -
Cpp 对象模型探索 / 对象的虚函数表指针的位置
一、源码 #include <iostream> class A { public: virtual void func(){}; public: int count_ = 0; }; int main() { std::cout << "类 A 的大小为 " << sizeof(A) << std::endl; ...原创 2019-10-18 09:00:48 · 240 阅读 · 0 评论 -
Cpp 对象模型探索 / 继承关系下的虚函数手动调用
一、多态机理 #include <iostream> class Father { public: virtual void Func1() { std::cout << "Fahter::Func1()" << std::endl; } virtual void Func2() { ...原创 2019-10-20 21:09:03 · 242 阅读 · 0 评论 -
Cpp 对象模型探索 / 多重继承虚函数表分析
一、源码 #include <iostream> class Base1 { public: virtual void func11() { std::cout << "Base1::func11()" << std::endl; }; virtual void func12() { ...原创 2019-10-23 13:07:02 · 329 阅读 · 0 评论 -
Cpp 对象模型探索 / 虚函数表和虚函数表指针的创建时机
一、虚函数表 在编译期间创建。编译器会为每个类确定好虚函数表(vtbl)的内容。 二、虚函数表指针 虚函数表指针跟随着对象,在运行期间创建。由于在编译期间编译器为每个类创建好了 vtbl,并且编译器会在类的构造函数中插入将虚函数表的地址赋值给虚函数表指针的隐藏代码,所以对象只有在运行期间才能获取到虚函数表指针的内容。 (SAW:Game Over!) ...原创 2019-10-24 06:14:16 · 1090 阅读 · 0 评论 -
Cpp 对象模型探索 / 静态联编和动态联编
一、源码 #include <iostream> class Father { public: Father() { /** * 该处直接将该对象清零,意味着虚函数表指针亦被清零。 */ memset(this, 0, sizeof(Father)); } public: virtual void Func1() { std::cout <...原创 2019-10-24 06:51:34 · 287 阅读 · 0 评论 -
Cpp 对象模型探索 / new 对象时加括号和不加括号时的差别
一、结论 CTest *pt1 = new CTest(); CTest* pt2 = new CTest; 1、若类 CTest 是空类,则二者new 的结果没有区别。 2、若类存在显示声明的缺省的构造函数,则二者 new 的结果没有区别。 3、若类没有显示声明的缺省的构造函数,即使编译器为类创建了缺省的构造函数,二者的结果依然不一样。带括号的结果是会将 pt1 的 count_ 初...原创 2019-10-24 21:19:59 · 625 阅读 · 0 评论 -
Cpp 对象模型探索 / new 运算符内部调用过程分析
一、new 调用过程 1、测试代码 class CTest { public: CTest(){} }; int main() { CTest *pt1 = new CTest(); delete pt1; return 0; } 在 new 的位置打下断点,查看反汇编代码(调试 -> 窗口 -> 反汇编),得到如下代码, 查看...原创 2019-10-24 23:05:44 · 541 阅读 · 0 评论 -
Cpp 对象模型探索 / delete 运算符内部调用过程分析
一、delete 调用过程 1、测试代码,在 delete 处打下断点。 class CTest { public: CTest() {} ~CTest() {} }; int main() { CTest* pt1 = new CTest(); delete pt1; return 0; } 2、查看反汇编,代码如下: 之后再查看 operator d...原创 2019-10-25 06:54:02 · 1707 阅读 · 0 评论 -
Cpp 对象模型探索 / operator new、operator delete、operator new[] 和 operator delete [] 重载
零、前言 对于函数 operator new 和 operator delete 来说,分为全局重载和局部重载。 全局重载的形式: void *::operator new(size_t size); void ::operator delete(void *pdata); 局部重载的形式: void *A::operator new(size_t size); voi...原创 2019-10-26 11:06:41 · 560 阅读 · 0 评论
分享