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,那么他们会以其原来的构造顺序的相反顺序被调用。