C++对象模型之一 关于对象笔记<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
前言
<<Inside The C++ Object Model>>Stanley B.Lippman著;中文版《深度探索C++对象模型》侯捷翻译。看到大家都认为是本好书,所以从Chinapub中购买会来。看了一遍后觉得昏昏沉沉,主要是很琐碎,其中此书有点过时感觉,中文出版时在2001年,英文原版写的时后大概是1994年到1998年之间;所举的列子关于编译器如何解决,有点给人过时感觉。Stanley B.Lippman对写作看来不太在行,侯捷翻译时纠正了很多错误。但是侯捷风格喜欢中文夹带英文的习惯,好比是“你今天shopping了吗?”;书中有很多小刮号,小刮号里面的话很多是废话。所翻译的中文有些很不流畅。以上几个方面对阅读和理解带来很大的不便,所以本鲨做做笔记啦!!
第一章关于对象
1 C++对象模型 Stroustrup当初设计C++时对象模型是从简单对象模型派生而来的,并对内存空间和存取时间做了优化,在此模型中非静态数据成员被配置于每个对象之内;静态数据成员,静态成员函数,非静态成员函数被配置在对象之外;虚函数也被配置在对象之外,而且与虚函数表(vbtl)一一对应,对象中有个虚函数表指针指向虚函数表(vptr),虚函数表第一个位置放的是对象类型信息(RTTI)
class Point
{
public:
Point(float xval);
Float x() const;
Static int PointCount();
~Point();
private:
float x;
static int pointcount;
}
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><img alt="" hspace="0" src="file:///C:/Documents%20and%20Settings/ZengMuAnSha/%E6%A1%8C%E9%9D%A2/1.bmp" align="baseline" border="0"></shapetype>
class Point
{
public:
Point(float xval);
Float x() const;
Static int PointCount();
Virtual ~Point();
Virtual double x() const;
private:
float x;
static int pointcount;
}
在单继承,多继承及虚继承后的对象模型是 基类的对象直接加到派生类对象的头部,如果是虚基类的话就是采用指针(bptr)指向虚基类
2 C++对象的大小
由其 非静态数据成员的大小; 内存对齐的需求而填补上去的空间大小;支持虚函数而内部产生的额外负之和。(内存对齐:将大小调整到某个数的倍数,在32位机器上是4的倍数)
独立类
class zoo
{
public:
zoo();
virtual ~zoo();
virtual void rate();
private:
int loc
string name;
};
main()
{
zoo za(“zoey”);
zoo *pza=&za;
}
单继承
class Bear :public Zoo
{
public:
Bear();
~Bear();
void rtate();
virtual void dance();
private:
enum Dances{….};
dance dances_know;
int CellBlock;
};
多继承和虚继承跟单继承一样的。注意点类中有char a 的数据成员 a只占一个字节,内存对齐后补上了3个空字节,在以后继承中派生类不会对基类重新进行内存对齐。也就是说派生类中如果有char b,c,d 不会和基类中a 共用4个字节空间。
C++对象模型之二 构造语句笔记
构造函数
你是不是这样认为:
1. 任何类如果没有定义默认构造函数,编译器就会合成一个;
2. 编译器合成的会明确设定类中每个数据成员的默认值;
事实并非如此的,只有C++编译器需要的时候才会合成个默认构造函数。
并且在4种情况下会那么做。
第一种情况:类中带有对象成员
class A{……};
class B
{
private:
A a; //对象成员
int x;
}
合成的构造函数
B::B ( )
{
a.A::A();
}
假如你定义了个构造函数
A::A ( )
{
x=0;
}
编译器会追加代码到里面去
A::A ()
{
a.A::A();
x=0;
}
第二种情况 基类中有默认构造函数(包括被合成的)
和第一种一样,你定义了很多构造函数,编译器会追加代码到每个构造函数中
第三种情况 带有虚函数的类 (包括纯虚函数)
编译器的必要的操作
1. 一个虚函数表将产生,内放虚函数的地址;
2. 一个虚函数指针将产生,指向虚函数表地址;
所以编译器会为每个对象设定vptr的初始值,放置适当的虚函数表地址,对于类所定义的构造函数追加代码完成这些事。
第四种情况 虚继承
class X{publci: int i ; };
class A :public virtual X { public : int j ;};
class B: public virtual X { public: double d;};
class C: public A,public B{ public: int k;};
void foo(const A *pa) { pa->i=1024;} //无法在编译期决定出pa->X::i 的位置
main() {foo(new A); foo(new C);} // 可能转变为 void foo (const A*pa) { pa->_vbcx->i=1024} vbcx是对象指向类X的指针,当然要在构造函数中确定。
拷贝构造函数
拷贝构造函数: 以一个对象的内容去初始化另个对象。 (关键在于初始化)
有三种情况下会调用拷贝构造函数
class x {……};
X x; X xx=x //不是赋值操作而是拷贝构造函数
Void foo (X x); // 对象参数
FooBar() { X x; return x;} // 返回对象
如果类没有提供显示的拷贝构造函数,编译器采用的是位逐次拷贝。也就是把对象数据成员值拷贝到另个对象上,如果遇到对象成员就会递归进去。
class String
{ public: //类没有提供显示的拷贝构造函数
private:
char *str ;
int len;
};
class word
{
public: //类没有提供显示的拷贝构造函数
private:
int occurs;
String str: //类含了对象成员
}
位逐次拷贝时先位逐次拷贝int occurs 遇到word后递归进 class String 位逐次拷贝。
当以下情况下编译器不会采用位逐次拷贝,而是生成默认的拷贝构造函数
1. 当类内含一个对象成员,而后者的类中声明了一个拷贝构造函数时 (包括程序员写的,编译器合成的)
2. 当类继承一个基类时,而后者存在一个拷贝构造函数时
3. 当类声明了虚函数时
4. 当类派生自一个继承链中有虚继承时
1.当类内含一个对象成员,而后者的类中声明了一个拷贝构造函数时
class String
{ public:
String (const char *);
String (const String &) //类提供显示的拷贝构造函数
~String();
private:
char *str ;
int len;
};
class word
{
public: //类没有提供显示的拷贝构造函数
private:
int occurs;
String str:
}
//就会合成一个 : word::word(const word &wd) { str.String::String( wd.str); occurs=wd.occurs;}
2 当类继承一个基类时,而后者存在一个拷贝构造函数时
class word :public String
{
public: //类没有提供显示的拷贝构造函数
private:
int occurs;
String str:
} //就会合成一个
3 当类声明了虚函数时
class word
{
public: //类没有提供显示的拷贝构造函数
virtural cout(); //类声明了虚函数
private:
int occurs;
String str:
} // 同理与构造函数一样的理由,拷贝构造函数要处理虚函数指针和虚函数表。
4 当类派生自一个继承链中有虚继承时
class Zoo{…}; class Racconn :public virtual Zoo {….} ; class ReadPanda : public Racconn {…};
这个时候不是一个类对象要另个类对象做初始化而是基类对象要派生类对象来初始化
Raccoonn rocky; Racconn little=rocky; // 简单的位逐次拷贝就行了
RedPanda litteRed; Racconnn littecritter = rocky ;
// 简单的位逐次拷贝就不行了,编译器必须明确地初始化littercritter 虚继承的指针。
C++对象模型之三 数据成员笔记
首先纠正第一章中关于静态成员在对象之外,对象用指针连接,实际上对象没有用指针去连接他们,而是通过类存取的. 下面是空类的虚继承.
Class x {};
class y :public virtual x{};
lass z:public virtual x{};
class A: public y,public z {};
其大小是 X =1 Y=4 Z=4 A=8;虚继承基类 X 隐藏1字节字符使的类两个对象在内存中有不同的地址。
数据成员的布局
非静态数据成员在类对象中的排列顺序和其被声明的顺序一样,任何中间介入的静态数据成员都不会放进去。在同一个存取节中各个成员并不一定连续排列,符合较晚出现的成员在类对象中有较高的地址。编译器可能合成些内部使用的数据成员以支持整个对象模型。编译器把多个相同的存取节合并在一起。
数据成员的存取
1 静态数据成员:在对象之外,并做为全局变量看,每一个对象的存取许可及类的关联并不会导致空间和时间的负担。
Point3d origin, *pt=&origin;
Origin.chunSize=250;
Pt->chunSize=250;
被内部转化为:
Point3d::chunkSize=250; Point3d::chunkSize=250; 无论是对象还是指针都一样。
如果静态数据成员经函数调用:foobar ( ).chunkSize=250; 被转化为:(void) foobar(); Point3d::chunkSize=250;
取一个静态数据成员的地址,得到的是指向该数据类型的指针,而不是一个指向其类成员的指针。
&Point3d::chunkSize; è const int *
2 非静态数据存取
欲要对非静态数据存取,编译器需要把类对象的起始地址加上数据成员的偏移量。
Origin.y=0.9; è &Origin+(&Point3d::y-1);
虚继承比其它方式存取数据成员会比较慢点。用对象和指针存取虚继承而来的数据成员时指针存取操作必须延迟到执行期,而对象在编译期就固定了。
继承与数据成员
在C++模型中派生类对象所表现出来的东西,是自己的成员们加上其基类们的成员的总和,基类成员总是先出现,但是虚继承类除外。
1 单继承: 类Point3d单继承于Point2d
2 单继承含虚函数:Point3d单继承于Point2d,Point2d有virtual void foobar();
3 多继承:Point3d继承于Point2d; Vertex3d 多继承于 Point3d 和Vertex
4虚继承:Point3d virtual Point2d ; Vertex :virtual Point2d; Vertex3d:public Point3d,public Vertex;
1 每个对象会多一个指针指向虚继承基类 (Point2d);
2 每次继承时存取时间会增加,因为要调整一些指针;
所以有两种对象模型:一种就是增加指针继承时调整指针;另一种是把虚继承基类的偏移地址放在虚函数表第一位值。
对象成员的效率
我使用BCB5 在塞扬A466 128SDRAM 下
void __fastcall TForm1::Button1Click(TObject *Sender)
{ //单个变量
float pA_x,pB_x,pA_y,pB_y,pA_z,pB_z;
pA_x=1.725;pA_y=0.875;pA_z=0.478;pB_x=0.315;pB_y=0.317;pB_z=0.838;
long StartTime=GetTickCount();
for(int i=0;i<10000000;i++)
{
pB_x=pA_x-pB_z;
pB_y=pA_y+pB_x;
pB_z=pA_z+pB_y;
}
long EndTime=GetTickCount();
Label1->Caption=EndTime-StartTime;
}
void __fastcall TForm1::Button2Click(TObject *Sender)
{
class Point3d
{
public:
Point3d(float xx=1.725,float yy=0.838,float zz=0.315): _x(xx),_y(yy),_z(zz){}
float & x() {return _x;}
float & y() {return _y;}
float & z() {return _z;}
private:
float _x,_y,_z;
};
Point3d A,B;
long StartTime=GetTickCount();
for(int i=0; i<10000000;i++)
{
B.x()=A.x()-B.z();
B.y()=A.y()+B.x();
B.z()=A.z()+B.y();
}
long EndTime=GetTickCount();
Label2->Caption=EndTime-StartTime;
}
剩余的代码没有给出,大家自己可以做做,对象不会很慢,纳闷结构体比单个变量快?
|
未优化
|
优化
|
单个变量
|
0.530 (秒)
|
0.521 (秒)
|
结构体
|
0.260 (秒)
|
0.200 (秒)
|
数组
|
0.531 (秒)
|
0.521 (秒)
|
对象
|
2.083 (秒)
|
0.521 (秒)
|
书上还做了各种继承的测试结论:
1 关闭优化后内联函数存取比较慢,所以程序员应该实际测试下,不要光凭推理或常识判断。
2 有时侯优化操作并不一定总是有效运行。
3 虚继承在优化后同样效率令人失望。
指向数据成员的指针
class Point3d
{
public:
virtual ~Point3d();
protected:
static Point3d origin;
float x,y,z;
};
因为 vptr 占4个字节 float 占4个字节 所以 vptr放在头部三个数据成员的偏移量是 4,8,12;vptr在尾部0,4,8。
如果你取数据成员的地址 &Point3d::x 返回的总是多1个字节,为什么?
因为
float Point3d::*p1=0;
float Point3d::*p2=&Point3d::x;
if (p1==p2) //无法区分 所以不得不加上1个字节
而 &Point3d::x; 和 &origin.Z 是不同的 前者得到的是在类中的偏移量,后者得到是真正的内存地址。
指向成员的指针效率
|
未优化
|
优化
|
直接存取
|
1.42
|
0.8
|
指针指向已绑定的成员
|
3.04
|
0.8
|
指针指向已数据成员
|
5.34
|
font-size: 9
分享到:
Global site tag (gtag.js) - Google Analytics
|
相关推荐
本篇文章基于《深度探索C++对象模型笔记》的部分内容,对C++对象模型进行深入探讨。 #### 对象模型的基本概念 在C语言中,数据与处理数据的操作通常是分开的,通过一系列分布于不同函数中的算法来处理共享的外部...
### C++对象模型学习笔记 #### 第1章 关于数据成员 **1.1 单个类** 在探讨C++对象模型时,我们首先关注单个类的数据成员布局及其内部结构。 ##### 1.1.1 没有虚函数存在 当我们定义一个简单的类,如`Point3d`,...
总结笔记,关于侯捷翻译的《深入探索c++对象模型》的笔记 作者Lippman参与设计了全世界第一套C++编译程序cfront,这本书就是一位伟大的C++编译程序设计者向你阐述他如何处理各种explicit(明确出现于C++程序代码中)...
通过阅读《深度探索C++对象模型》的学习笔记,我们可以更深入地理解C++的底层机制,这对于成为一名精通C++的开发者来说是必不可少的。同时,结合《Effective C++》的学习,可以让我们写出更加高效、健壮的C++代码。...
C++对象模型是C++编程语言的核心组成部分,它涉及到C++如何在内存中表示类、对象、成员函数以及继承等特性。这篇读书笔记主要聚焦于深入理解这些概念,以帮助我们更好地编写高效、可靠的C++代码。 首先,C++对象...
在深入探讨C++对象模型这一主题时,我们首先需要理解C++语言的核心概念,它是一种静态类型、编译式、通用的、大小写敏感、不支持自动垃圾回收的编程语言。C++以其强大的面向对象特性著称,这些特性包括封装、继承和...
《深度探索 C++对象模型》是一本深入剖析C++内部机制的著作,它揭示了C++如何在内存中表示和管理对象,以及由此产生的性能影响。通过对C++对象模型的了解,程序员可以编写出更高效、更少错误的代码。 在C++中,类是...
《深入C++对象模型》是一本探讨C++底层机制的书籍,主要讲解C++对象的构建、拷贝、赋值等过程。以下是该书部分核心知识点的总结: 1. **默认构造函数(Default Constructors)**: - 当用户未定义构造函数时,...
### C++ 学习笔记精华版 #### 一、C++ 语言概述 **1、历史背景** - **C++ 的江湖地位** - Java、C、C++、Python、C# 是当前主流的编程语言之一,而 C++ 在这些语言中以其高效性和灵活性著称。 - **C++ 之父 ...
理解这些内容对于进一步学习C++对象模型和类的概念至关重要。 【高级C语言】章节则更深入地探讨了C语言的高级特性,如预处理器、位操作、文件I/O、结构体和联合体,以及动态内存分配等。这部分内容为后续学习C++的...
首先,"深入探索C++对象模型"是理解C++核心机制的关键。C++的对象模型涉及到内存布局、构造与析构过程、成员访问控制、继承和多态等。深入理解这些内容能帮助开发者编写更高效、更可靠的代码。例如,了解对象的存储...
《Inside the C++ Object Model》一书深入剖析了C++对象模型的核心概念和技术细节,尤其在前七章中,作者详尽地介绍了C++中对象的内部表示、虚拟函数机制、基类与派生类的关系以及编译器如何处理这些元素。...
【C++学习笔记概述】 C++是一门强大的编程语言,其在C语言的基础上进行了扩展,引入了许多现代化的特性。这份笔记主要涵盖了C++的基础知识,包括C++11和C++17的新特性,旨在帮助初学者理解C++的核心概念。 ### C++...
本篇笔记主要探讨了C++程序在内存中的存储方式,即内存分区模型,包括代码区、全局区、栈区和堆区。 1. **内存分区模型** - **代码区**:存放编译后的二进制代码,由操作系统管理,具有共享和只读属性,确保程序...
1. **C++编程语言**:C++是一种通用的、面向对象的编程语言,以其高效性和灵活性而著名。VC++6.0是Microsoft Visual Studio的一个早期版本,它提供了C++的开发环境,包括编译器、调试器和其他工具,用于创建Windows...
纹理映射是一种将图像或颜色信息应用到三维模型表面的技术,可以极大地增强游戏画面的真实感和细节。DirectX 11 提供了丰富的 API 和工具来处理纹理映射,使得开发者能够轻松地为游戏中的物体添加各种视觉效果。 **...
《Visual C++程序设计学习笔记》是一份深入探讨VC++编程技术的综合资料,涵盖了从基础知识到实际系统开发的广泛内容。Visual C++是Microsoft公司推出的一种强大的集成开发环境,它集成了C++编译器、调试器以及MFC...
2. 选择过程模型还是对象模型 3. 将模型实现为源程序 4. 编辑:使用文本编辑工具编写C++程序 5. 编译:用编译器源代码转换为主机使用的内部语言 6. 链接:将若干个目标代码和现有的二进制代码库经过链接器连接,产生...
C++编程惯用法笔记主要涵盖抽象模型设计、类的构建以及相关成员函数的使用。以下是对这些知识点的详细阐述: **一、抽象** 1. 抽象是软件设计的基础,通过抽象,我们将复杂的问题转化为更易管理的模型和实现。在...