2.2 Copy Constructor 的建构操作
有三种情况,会以一个object的内容作为另一个class object的初值。
- 1、对一个object做明确的初始化操作时;
- 2、当object被当做参数交给某个函数时;
- 3、当函数传回一个class object时。
如果程序员明确定义了一个copy construct函数,那么在大多数情况下,当一个class object以另一个实体作为初值时,copy construct函数会被调用,但这可能会导致一个暂时性class object的产生或程序代码的蜕变(或两者都有)。
而如果 ** class没有明确提供一个explicit copy constructor **时,当class object 以“同一类型的另一个object”作为初值时,其内部是 采用default memberwise initialization(深拷贝?) 完成的,也就是将每一个内建的或派生的data member的值,从某个object拷贝一份到另一个object上。不过这并不会拷贝其中的成员类对象,而是以递归的方式实施memberwise initialization。
Default constructors 和copy constructors 在必要的时候才有编译器参数出来。
有四种情况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 classes.
这里的意思是-----除了上面这四种情况,其他情况都会用到bitwise 吗???
2.3程序转换语意学
c++中函数的参数初始化,把一个class object 当做参数传给一个函数(或是作为一个函数的返回值),相当于以下形式的初始化操作:
上面的调用方式会要求局部实体x0以MemberWise的方式将xx当做初值,在编译器实现技术中,有一种策略是** 导入所谓的暂时性object,并调用copy construct将它初始化,然后将暂时性object交给函数 **。所以上面函数的调用方式可以展开为:
存在一个问题:
这里会产生暂时性的Object先以class X的copy constructor正确地设定初值,然后再以bitwise方式拷贝到x0这个局部实体中。 (这里为什么会采用bitwise的方式?)
所以为了避免这个问题,foo函数的声明必须被转化,形式参数必须从原先的一个class X object改变为一个class X reference。 比如:
void foo(X& x0);
已知下面这个函数定义:
X bar()
{
X xx;
// 处理 xx ...
return xx;
}
这里bar()的返回值会从函数的局部对象xx中拷贝到出来。
- 1、首先加上一个额外的参数,类型是class object 的一个reference 。这个参数被用来放置“拷贝建构”而得的返回值;
- 2、在return指令之前安插一个copy constructor 调用操作,以便将欲传回值object的内容当做上述新增参数的初值。
最后一个操作会重新改写函数,使函数不返回任何值。
根据以上信息,bar()转换如下:
///函数转换
///用来反映copy constructor的应用
//c++ 伪代码
void
bar(X &__result) // 加了一个额外的参数
{
X xx;
//编译器产生的default constructor调用操作
xx.X::X();
//对xx进行处理操作
//编译器所产生的copy constructor调用操作
__result.X::X(xx);
return ;
}
在编译器层面上的优化
在一个如bar()这样的函数中,所有的return指令传回相同的具名数值(named value),因此编译器有可能自己做优化,方法是以result参数取代named return value。
这样的编译器优化操作被称为Named Return Value(NRV)优化。
在一个自定义类型,它既没有任何member(或base)class objects带有copy constructor,也没有任何的virtual base class 或virtual function。所以,默认情况下该类型的"memberwise"初始化操作会导致“bitwise copy”。
这在个类型中,三个坐标成员是以数值来储存。bitwise copy既不会导致memory leak,也不会产生address aliasing(地址冲突),因此它即快速又安全。所以这个class的设计者就没有必要提供一个explicit copy constructor。因为编译器已经提供了最好的选择。而如果我们可以预见的是class需要大量的memberwise初始化操作,例如以传值的方式传回objects,那么提供一个copy constructor的explicit inline 函数实体就很有必要了。
在初始化时,有时可以使用memcpy()和memset()可以使得程序运行更有效率,但这需要在掌握c++ object Model的语义学知识基础上,不然可能会出大问题。
copy constructor的应用,会使得编译器或多或少的对我们的程序代码进行了部分转化,尤其是在使用一个函数以传值(by value)的方式传回一个class object,而该class有一个copy constructor (明确定义的或隐式合成的)时,这都会导致深奥的程序转化。此外编译器也将copy constructor 的调用操作优化,以一个额外的第一个参数(数值被直接存放其中)取代NRV。
2.4成员们的初始化队伍
当在类中写下一个constructor时,我们有计划设定class members的初值。由member initialization list,或在constructor函数本身之内。
有四种情况,我们为了使程序能够被顺利编译,必须使用member initialization list:
- 1、当初始化一个reference member时;
- 2、当初始化一个const member 时;
- 3、当调用一个base class 的constructor ,而它拥有一组参数时;
- 4、当调用一个member class的constructor ,而它拥有一组参数时。
在这里就会有个困惑,为什么必须要使用member initialization list,而member initialization list在程序运行过程中到底会发生什么事情?
编译器会一一操作initialization list,以适当次序在constructor之内安插初始化操作,并且在任何explicit user code之前。
在这之中还有一些微妙的地方需要注意:list中的项目次序是由class中members __ 声明次序 __ 决定,不是由initialization list中的排列次序决定的。
初始化次序和initialization list 中的项目排列次序之间的外观错乱,可能会引发意想不到的错误。