构造、解构、拷贝语意学

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

考虑下面程序片段:
1
2
3
4
5
6
7
8
9
10
11
Point glocal; //全局内存配置
 
Point foobar()
{
    Point local; //局部栈内存配置
    Point *heap= new Point; //heap内存配置
    *heap=local;
    
    delete heap;
    return local;
}

1 把Point类写成c程序,c++标准说这是一种所谓的Plain OI Data

typedef struct
{
    float x,y,z;
}Point;
    如果我们以C++来编译这段代码,观念上,编译器会为Point 声明一个trivial default constructor、一个trival destructor、 一个trivial copy constructor,以及一个trivial copy assignment operator。但实际上,编译器会分析这个声明,并为它贴上 Plain OI Data标签。
1> L1中,观念上Point的 trivial default constructor和 trival destructor都会产生并被调用,然而,事实上那些trivial members要不是没有被定义,就是没有被调用,程序的行为一如它在c中的表现一样。
2> L5中的Point object local,同样也是既没有被构造也没有被析构。如果local没有先经初始化,可能会称为一个潜在的程序漏洞-万一第一次使用就需要其初始值。
3> L6被转化为: Point *heap=__new(sizeof(Point)); 并没有default constructor施行与new运算符所传回的Point object身上。
4> L7中,观念上该操作会触发 trivial copy assignment operator进行拷贝搬运操作。然而实际上,该对象被看作一个 Plain OI Data ,所以赋值操作将只是像C那样的纯粹位搬移操作
5> L9中的操作被转化为:__delete(heap); destructor要不是没有产生就是没有调用。
6> L10中,函数以传值的方式将local当作返回值传回,这在观念上会触发 trivial copy constructor,不过实际上return操作只是简单的位拷贝操作,因为对象是一个 Plain OI Data

2 抽象数据类型

class Point{
public :
    Point( float x = 0. 0, float y = 0. 0, float z = 0. 0) :_x(x),_y(y),_z(z){}
    //no copy constructor,copy operator or destructor defined ...
private :
    float _x,_y,_z;
};
    我们并没有为Point定义一个copy cosntructor或copy operator,因为 默认的位语意(default bitwise semantics)已经足够。我们也不需要一个destructor,因为程序 默认的内存管理方法也已经足够。
1> L1中, 有了default constructor作用与其上,由于global被定义在全局范围中,其初始化操作延迟到程序激活时才开始。
2> L5中, 会被加上default Point constructor的inline expansion;如下:
Point local;
Point local;
local._x=0.0;local._y=0.0;local._z=0.0;
3> L6配置heap Point object:
Point *heap=new Point; //现在 被附加上一个对default Point constructor的有条件调用。
Point *heap=__new(sizeof(Point));
if(Hadp!=0)
    heap->Point::Point();
然后才被编译器进行inline expansion操作,至于heap指针指向local object:
4> L7中: *heap=local; 则保持着简单的位拷贝操作。
6> L10中,以传值方式传回local object ,情况也是一样,保持着简单的位拷贝操作。
5> L9中的删除操作, 并不会导致destructor被调用, 仍采用 默认的内存管理方法 ,因为我们并没有明确地提供一个destructor函数实体。
    观念上,我们Point class有一个相关的default copy constructor、copy operator和destructor,然而都是无关痛痒的(trivial),而且编译器实际上根本没有产生他们。

3 为继承做准备

class Point{
public :
    Point( float x = 0. 0, float y = 0. 0) :_x(x),_y(y){}
    //no destructor,copy  cosntructor,or copy operator defined...
    virtual float z();
protected :
    float _x,_y;
};
virtual函数引入带来的影响:
①  促使每一个Point object拥有一个virtual table pointer,这个指针提供给我们virtual 接口的弹性,代价每一个object需要额外一个word的空间。
② 我们所定义的constructor被附加一些代码,以便初始化vptr,这些信息必须被附加在任何base class cosntructors的调用之后,但必须在任何使用者(程序员)供应的码之前。
③ 合成一个copy constructor和一个copy assignment operator,而且其操作不再是trivial。用在一个Point object被初始化或以一个derived class object赋值,正确处理vptr指针。
1> L1的gloabl初始化操作,L1的local初始化操作,L6的heap初始化操作以及L9的heap删除操作,都还和2中的一样。
2> L9中的赋值操作,很可能触发copy assignment operator的合成,及其调用操作的一个inline expansion。
3> 最戏剧性的冲击发生在以传值方式传回local的那一行,由于copy constructor的出现,foobar()很可能被转化为下面这样:
//用以支持copy constructor
Point foobar(Point &__result)
{
    Point local;
    local.Point : :Point( 0. 0, 0. 0);
    //heap的部分没变
    __result.Point : :Point(local); //copy constructor的应用
    //local对象的destructor将在这里发生
    //local.Point::~Point();
    return ;
}
如果支持NRV优化,这个函数还会进一步转化为:
//以支持NRV优化
Point foobar(Point  &__result)
{
    __result.Point : :Point(0.0,0.0);
     //heap的部分没变
     return ;
}
重要注意或提示:
    一般而言,如果你的设计之中有有 许多函数都要以传值方式传回一个local class object。那么 提供改一个copy constructor就比较合理-深知即使default memberwise语义已经足够, 它的出现会触发NRV。然而,就想上面的例子一样,NRV优化后将不再需要调用copy constructor。
 

二 继承体系下的对象构造

当我们顶一个object如下:
T object;
时,如果T有一个constructor(不论是有user提供或是编译器合成),它会被调用。
    constructor可能内含大量的隐藏码,因为编译器会扩充每一个constructor,扩充程度视class T的继承体系而定,一般而言编译器所做的扩充大致如下:
1 所有virtual base class constructors必须调用,从左到右,从最深到最浅。(正确处理虚基类对象的偏移量offset)
2 所有上一层base class cosntructors必须被调用,以base class的声明顺序为顺序。
3 如果class object有vptr,他们必须设定初值,指向适当的virtual tables。
4 记录在member initialization list中的data members初始化操作会放进constructor函数本身,并以members的声明顺序为顺序。
5 如果有一个member 并没有出现在member initialization list之中,但它有一个default constructor,那么default constructor必须被调用。

虚拟继承

    存在虚拟继承时,必须保证虚基类子对象的初始化必须有最底层的派生类完成,否则可能会出现多次初始化。方法:
1> 增加一个用以指示virtual base class constructor应不应该调用的参数。
2> 把每一个constructor分类为二,一个针对完整的object,另一个针对subobject。"完整object"版无条件地调用虚基类构造函数;"subobject”版则步调用虚基类构造函数。

vptr初始化语义学

    vptr应该在base class constructors调用之后,但是在程序员供应的码或是"member initialization list"中所列的members初始化操作之前。编译器保证这一点。
    这样解决了“ 在class中限制一组virtual function名单 ”的问题,如果每一个constructor都一直的等待到base class constructors执行完之后才设置其对象的vptr, 那么每次都能够调用正确的virtual function实体。
constructor的执行算法通常如下:
1> 在派生类构造函数中,“所有基类构造函数”及“上一层基类“的构造函数会被调用。
2> 上述完成之后,对象的vptr被初始化,指向相关的虚函数表。
3> 如果有成员初始化列表的话,将在构造函数体内扩展开来,这必须在vptr设定之后才惊醒,以免一个虚函数调用。
4> 调用类成员对象的默认构造函数,如果有的话。
5> 最后,执行程序员所提供的码。
但其实不是每个基类构造函数都必须初始化vptr的。 vptr必须设定的两种情况:
1> 当一个完整的对象被构造出来时。
2> 当一个subobject constructor调用了一个虚函数是(不论是直接调用或间接调用)
 

三 对象的赋值(copy assignment opertor)语义学

我们设计一个类,并以一个类对象指定给另一个类对象时,我们有三个选择:
1 什么都不做,因此得以实施默认行为(member copy or bitwise copy)。
    如果我们不对Point类供应一个赋值操作符,而光是依赖默认的memberwise copy,编译器会产生出一个实体吗?
    这个答案和copy constructor的情况一样。实际上不会,因为此类已经有了bitwise copy语义了,所以im plicit copy assignment operator视为无用的,也根本不会合成出来。
2 明确地拒绝一个class object指定给另一个class object。(将 copy assignment operator声明为private,并且不提供定义即可 )
3 提供一个explicit copy assignment operator。只有默认行为所导致的语义不安全或不正确是,我们才设计一个赋值操作符。
 

一个类对默认的赋值操作符,在一下情况不会表现出bitwise copy 语义

1> 当class内含一个member object,而其class有一个copy assignment opertor 时。
2> 当一个class的base class 有一个copy assignment opertor时。
3> 当一个class声明了任何virtual functions(我们一定不能拷贝右端class object的vptr地址,因为它可能是一个派生类对象)。
4> 当class 继承自一个virtual base class(不论base class 有没有copy assignment opertor)时。
    c++标准上说 copy assignment opertor并不表示bitwise copy semantics是nontrivial。实际上,只有nontivial instances才会合成出来。
 
    建议尽可能不要允许一个virtual base class的拷贝操作,甚至提供一个比较奇怪的建议:不要在任何virtual base class 中声明数据。 应为 copy assignment opertor没有什么好的方法避免virtual base class 在派生层次中重复拷贝现象。

​四 解构(析构)语义学

     如果class没有定义destructor,那么只有在class内带的member object(或是class 自己的base class)拥有destructor的情况下,编译器才会自动合成出来一个来。否则,destructor会被视为不需要,也就不会合成(当然不会调用了),或者说是trivial的。
    我们应该根据“需要”而不是“感觉”来提供destructor,更不应该因为不确定是否需要一个destructor,于是就提供它。
    为了决定class是否需要一个程序层面的destructor(或是constructor),我们应该想一下class object的声明在哪了结束(或开始)?需要什么操作才能宝座对象的完整。这是我们写程序应该了解的,也是constructor和destructor什么时候起作用的关键。
 
一个有程序员定义的destructor被扩展的方式类是constructor被扩展的方式,但顺序相反:
1 destructor的函数本身首先被执行。
2 如果class拥有member class objects,而后者拥有destructor,那么会以其声明顺序的相反顺序被调用。
3 如果内带一个vptr,则现在重新设定,指向适当之base class的virtual table。(并总是设置,只用量中情况下设置,和构造函数相似)
4 如果有任何直接的nonvirtual base classes拥有destructor,他会以声明顺序相反的顺序调用。
5 如果有任何virtual base class拥有destructor,而当前讨论的这个class是最尾端的class,那么他们会以其原来的构造顺序的相反顺序被调用。

 

转载于:https://www.cnblogs.com/botou0405/p/3764688.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值