码迷,mamicode.com
首页 > 其他好文 > 详细

构造、解构、拷贝语意学

时间:2014-06-04 17:02:24      阅读:271      评论:0      收藏:0      [点我收藏+]

标签:des   c   style   class   code   a   

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

考虑下面程序片段:
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语义了,所以implicit 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,那么他们会以其原来的构造顺序的相反顺序被调用。

 

构造、解构、拷贝语意学,布布扣,bubuko.com

构造、解构、拷贝语意学

标签:des   c   style   class   code   a   

原文地址:http://www.cnblogs.com/botou0405/p/3764688.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!