深度探索C++对象模型笔记(二)

1、Default Constructor的构建操作
C++standard:对于class X ,如果没有任何user-declared constructor,那么会有一个default constructor 被暗中(implicitly)声明出来........一个被暗中声明出来的default constructor将是一个trivial(浅薄而无能,没啥用的)constructor......

一个nontrivial default constructor在ARM的术语中就是编译器所需要的那种,必要的话会由编译器合成出来。下面讨论nontrivial default constructor的四种情况。
  • 带有default constructor的member class object:如果一个类没有任何constructor,但它内含一个member object,而后者有default constructor,那么这个class的implicit default constructor就是”nontrivial“的。
既然编译器会合成默认构造函数,那么在C++各个不同的编译模块中,编译器如何避免合成出多个默认构造函数(比如一个是a.c文件,另一个是b.c文件)呢?解决办法是吧合成的default constructor、copy constructor、destructor、assignment copy operator都以inline方式完成,一个inline函数有静态链接,不会被文件以外看到。如果函数太复杂,不适合做成inline,就会合成出一个explicit non-inline static实体。
class Foo
{
public:
Foo(),Foo(int)....
};

class Bar
{
public:
Foo foo; //内含,不是继承
char *str;
};

void foo_bar()
{
Bar bar;//Bar::foo必须在此初始化
}

被合成的Bar default constructor内含必要代码,能够调用class Foo的default constructor,但是它并不产生任何代码来初始化Bar::str,将Bar:foo初始化是编译器的责任,将Bar::str初始化是程序员的责任
程序员定义default constructor,完成初始化
Bar::Bar()  { str = 0; }
编译器会扩张已经存在的constructors,在其中安插一些代码,使得user code在执行之前,先调用必要的default constructor:
//扩张之后的default constructor
Bar::Bar()
{
foo.Foo::Foo();
str = 0;
}
如果有多个class member object都要求constructor初始化操作,C++语言要求以”member object在class中被声明的次序“来调用各个constructor。
  • 带有Default Constructor的Base Class:如果一个没有任何constructor的class派生自一个带有default constructor的base class,那么这个derived class的default constructor会被视为nontrivial,并因此需要被合成出来。
  • 带有一个VIrtual Function的Class:有两种情况,也需要合成出default constructor
  1. class声明(或继承)一个virtual function
  2. class 派生自一个继承串链,其中有一个或更多的virtual base classes
有两个扩张会在编译期间发生:
  1. 一个virtual function table会被编译器产生出来,内放class的virtual functions地址
  2. 在每一个class object中,一个额外的pointer member会被编译器合成出来,内含相关的class vtbl的地址
此外虚拟操作会被改写,如widget.flip();会被改写成*widget.vptr[1](&widget) 1表示flip在vtbl中的固定索引,&widget代表要交给被调用的某个flip的函数实体的this指针。
为了让此机制发挥功效,编译器必须为每一个widget(或其派生类)object的vptr设定初值,放置适当的virtual table地址,对于class所定义的每一个constructor,编译器会安插一些代码来做这样的事情。对于未声明任何constructor的classes,编译器合成default constructor,以便正确初始化每一个class object的vptr。
  • 带有一个VIrtual Base Class的Class:Virtual Base Class的实现法在不同的编译器之间有极大差异,然而,每一种实现法必须使virtual base class在每一个derived class object中的位置,能够于执行期准备妥当。
有四种情况会导致编译器合成default constructor,C++standard把那些合成物称为implicit nontrivial default constructor,至于没有存在那四种情况而又没有声明任何constructor的classes,我们说它拥有的是implicit trivial default constructor,它们实际上并不会被合成出来。
C++新手一般有两个常见的误解:
1.任何class如果没有定义default constructor,就会被合成出来一个。
2.编译器合成出来的default constructor会明确设定class内每一个data member的默认值
2、Copy Constructor的构建操作
当class object以相同class的另一个object作为初值时,其内部是以所谓的default memberwise(按成员) initialization手法完成的。
就像default constructor一样,C++standard上说,如果class没有声明一个copy constructor,就会有隐含的声明或隐含的定义出现,C++standard把copy constructor区分为trivial何nontrivial两种,只有nontrivial的实体才会被合成于程序之中。决定一个copy constructor是否为trivial的标准在于class是否展现出所谓的”bitwise copy semantics“
  • Bitwise Copy Semantics(逐位拷贝)
    Word noun("book");
    
    void foo()
    {
    Word verb = noun;
    }
    
    很显然verb是根据noun来初始化,如果class Word没有定义一个explicit copy constructor,那么是否会有一个编译器合成的实体被调用呢?这就得看class是否展示出”bitwise copy semantics“。举个例子,如下Word的声明:
    class Word
    {
    public:
    Word(const char*);
    ~Word(){ delete[] str; }
    private:
    int cnt;
    char* str;
    };
    
    这种情况下并不需要合成一个default copy constructor,因为上述声明展现了bitwise copy semantics,而verb的初始化操作也不需要以一个函数调用收场。
    
    如果class Word如下声明:
    //以下声明并未展示出bitwise copy semantics
    class Word
    {
    public:
    Word(const String&);
    ~Word();
    private:
    int cnt;
    String str;
    };
    其中String声明了一个explicit copy constructor:
    class String
    {
    public:
    String(const char*);
    String(const String&);
    ~String();
    };
    这种情况下,编译器必须合成出一个copy constructor以便调用member class String object的copy constructor:
    //一个被合成出来的copy constructor
    inline Word::Word(const Word& wd)
    {
     str.String::String(wd.str);
    cnt = wd.cnt;
    }
  • 不要Bitwise Copy Semantics
什么时候一个class不展现出来Bitwise Copy Semantics呢?有四种情况:
  1. 当class内含一个member object 而后者的class声明有一个copy constructor时(不论是被class设计者明确地声明,还是被编译器合成);
  2. 当class继承自一个base class而后者存在一个copy constructor时(再次强调不论是被明确声明还是被合成而得)
  3. 当class声明了一个或多个virtual functions时
  4. 当class派生自一个继承串链,其中有一个或多个virtual base class时
前两种情况,编译器需将member或base class的copy constructor调用操作安插到被合成的copy constructor中,情况3和4有点复杂。
重新设定Virtual Table的指针
  • 增加一个virtual function table(vtbl),内含每一个有作用的virtual function地址
  • 将一个指向vtbl的指针(vptr),安插在每一个class object内
处理Virtual Base Class Subobject
一个class object以另一个object作为初值,而后者有一个virtual base class subobject,也会使bitwise copy semantics失效
3、程序转化语意学

明确的初始化操作

X x0;
下面有三个定义,每一个都明显地以x0来初始化
void foo_bar()
{
X x1(x0);
X x2 = x0;
X x3 = X(x0);
}
转化阶段有两个
1.重写每一个定义,初始化操作被剥除
2.class的copy constructor调用操作会被安插进去
//可能的程序转换
void foo_bar()
{
X x1;//定义被重写,初始化被剥除
X x2;//定义被重写,初始化被剥除
X x3;//定义被重写,初始化被剥除
//编译器安插
x1.X::X(x0);
x2.X::X(x0);
x3.X::X(x0);
}

参数的初始化

C++Standard说,把一个class object当做参数传递给一个函数(或作为一个函数的返回值),相当于以下形式的初始化操作:
X xx = arg; //xx代表形式参数(或返回值),arg代表实参,因此编译器会引入所谓临时对象,并调用copy constructor将其初始化,然后传给函数。
因此,若已知函数及其调用方式如下:
void foo(X x0);
X xx;
foo(xx);

转码之后如下:
X _temp0;
_temp0.X::X(xx);
foo(_temp0);
然而这样的转换只做了一半功夫而已,问题出在foo()的声明,临时对象先以class X的copy constructor正确设定初值,然后以bitwise方式拷贝到x0这个局部对象中。
解决的办法是修改foo的声明为foo(X& x0);

返回值的初始化

已知函数定义:
X bar()
{
X xx;
//...
return xx;
}
bar()返回值如何从局部对象拷贝?
Stroustrup在cfront的解决方法是一个双阶段转化:
1.加上一个额外参数,类型是reference
2.在return前安插一个copy constructor操作
//函数转化
void bar(X& _result)//加上一个额外参数
{
X xx;
xx.X::X();//编译器产生的default constructor调用操作
//....处理xx
_result.X::XX(xx);//编译器产生的copy constructor调用
return;
}
一个bar()调用被转化为:
X xx = bar();
转为:
X xx;
bar(xx);//注意不必调用default constructor

使用者层面做优化

定义一个“计算用”的constructor,直接计算xx的值:
X bar(const T &y, const T &z)
{
return X(y, z);
}
于是bar()定义被转换之后,效率会比较高:
void bar(X &_result)
{
_result.X::X(y, z);
return;
}
_restult被直接计算出来,而不是经过copy constructor拷贝

在编译器层面做优化

以_result参数取代named return value,即编译器将以上的xx以_result替代:

void bar(X &_result)

{

_result.X::X();//default constructor被调用

//....

return;

}

称为Named Return Value(NRV)优化。 由于NRV需要调用默认拷贝构造,因为需要注意不满足此前说的四种情况下,编译器不会合成默认拷贝构造,此时需要我们手动添加拷贝构造,如
pulbic :
inline test(const test& t); 

拷贝构造要还是不要?

已知下面的3D坐标点类:
class Point3d
{
public:
Point3d(float x, float y, float z);
//...
private:
float _x, _y, _z;
};
这个class的设计者应该提供一个explicit copy constructor吗?
上述的默认拷贝构造被视为trivial,它没有任何member(或base)class object带有copy constructor,也没有任何virtual base class或virtual function,所以,默认情况下会导致“bitwise copy”。这样效率很高,但安全吗?
答案是yes。
如果class需要大量的memberwise初始化操作,例如以传值方式返回object,那么提供一个copy constructor的explicit inline函数实体就非常合理(因为需要编译器NRV,参考上一条)
实现copy constructor的最简单方法像这样:
Point3d::Point3d(const Point3d &rhs)
{
_x = rhs._x;
_y = rhs._y;
_z = rhs._z;
}
这没问题,但使用C++ library的memecpy()会更有效率:
Point3d::Point3d(const Point3d &rhs)
{
memecpy(this,&rhs, sizeof(Point3d));
}
然而不管用memcpy还是memeset,如果Point3d声明一个或以上virtual functions,或内含一个virtual base class,那么上述函数将会导致那些“被编译器产生的内部members的初值被改写(如vptr)”:
编译器的扩张看起来像是:
Shape::Shape()
{
_vptr_shape = _vtbl_Shape;
//memset会将vptr清零
memset(this, 0, sizeof(Shape));
}

因此,若要正确使用memeset和memecpy,需要掌握某些C++ObjectModel语意学知识。(什么时候能用,什么时候不能用,怎么用

4、成员初始化列表
一下情况,必须使用member initialization list:
  • 初始化一个reference member时
  • 初始化const member
  • 调用base class 的 constructor,而它拥有一组参数
  • 调用member class的constructor,而它拥有一组参数
效率不好的使用方式:
class Word
{
String _name;
int _cnt;
public:
Word()
{
_name = 0;
_cnt = 0;
}
};

Word constructor会先产生一个临时String object,然后将它初始化,再以一个assignment运算符指定给_name,再销毁临时object。
更有效的方法:
Word::Word : _name(0)
{
_cnt = 0;
}
会被扩展成:
Word:Word()
{
 _name.String::String(0);
_cnt = 0;
}

这会引导某些程序员十分积极进取地坚持所有member初始化操作必须在初始化列表中完成,甚至是一个行为良好的member如_cnt;
成员初始化列表中到底会发生什么事情?C++新手误以为它是一组函数调用,当然它不是!
编译器--操作初始化列表,在constructor内添加初始化操作,list中项目的次序是由class中member声明次序决定的,不是由初始化列表的排列次序决定的。
因此需要注意一下问题:
class X
{
int i;
int j;
public:
//看出问题了吗?
X(int vla) : j(val), i(j)
{
}
};
由于声明次序的缘故,初始化列表中i(j)其实比j(val)更早执行,结果导致i无法预知其值。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深度探索C++对象模型》第版是C++领域中经典的著作之一。该书通过深入剖析C++对象模型,为C++开发者提供了深入理解和应用C++的基础知识和一些高级特性的重要指南。 该书首先介绍了C++中的基本概念,包括对象、类、继承等,并详细解释了C++中的虚函数、多态性和动态绑定等重要概念。这些概念是理解C++对象模型的基础,也是在实际开发中运用C++的关键。 接着,该书讲解了C++中的内存布局和对象模型。通过对C++对象在内存中的表示和存储方式的剖析,读者可以更好地理解C++对象的构造、析构和使用方式。同时,该书也介绍了C++中的虚函数表、虚指针和虚基类等重要机制,为读者揭示了C++中多态性的原理和实现方式。 此外,书中还介绍了C++中的派生类对象和基类对象之间的转换机制,包括指针转换、引用转换和类型转换等。这些内容对于理解C++中的对象关系以及编写灵活可扩展的代码都非常重要。 最后,该书还通过对C++中异常处理机制和模板编程的深入探讨,让读者了解到C++的一些更高级的特性和应用场景。 总之,《深度探索C++对象模型》第版是一本非常有价值的C++开发指南,适合C++的初学者和进阶开发者阅读。通过对C++对象模型的深入理解,读者可以更好地掌握C++的核心特性,从而写出高效、可扩展的C++代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值