目录
1、显示初始化(Explict Initilization)
2、参数初始化(Argument Initialization)
3、返回值初始化(Return Value Initialization)
4、在使用者层面优化(Optimization at the user level)
5、在编译器层面优化(Optimization at the Compiler level)
1、显示初始化(Explict Initilization)
已知有这样的定义, 下面有三个定义,每一个都明显地以 x0 来初始化其 class object:
X x0;
void foo bar() {
X xl( x0 ); // 译注:定义了 x1
X x2 = x0; // 译注:定义了 x2
X x3 =X( xO );// 译注:定义了 x3
//..
可能的程序转换,分为以下两步:
- 定义被重写,初始化操作被剥除
- 编译器安插 X copy construction 的调用操作
// C++ 伪码
void foo bar() {
X xl;// 译注:定义被重写,初始化操作被剥除
X x2;// 译注:定义被重写,初始化操作被剥除
X x3;// 译注:定义被重写,初始化操作被剥除
// 编译器安插 X copy construction 的调用操作
x1.X::x( x0 );
x2.X::;X(x0);
x3.X::X(x0);
//.
2、参数初始化(Argument Initialization)
若已知这个函数,下面这样的调用方式:
void foo( X x0 );
X xx;
//..
foo( xx );
将会要求局部实体(局部实例)x0以成员方式将xx当做初值。在编译器实现技术上,有一种策略是引入临时对象,然后将该临时对象交给函数。可能的程序转换,分为以下三步:
- 引入临时变量
- 使用拷贝构造对之初始化
- 改变函数声明
// C++ 伪码
// 编译器产生出来的暂时对象
X temp0;
// 编译器对 copy constructor 的调用
temp0.X::X( xx )
// 重新改写函数调用操作,以便使用上述的暂时对象
foo( temp0 ) ;
// 改变函数声明
void foo( X& x0 );
3、返回值初始化(Return Value Initialization)
已知下面这个函数定义:
X bar(){
X xx;
// 处理 xx
return xx;
}
返回值如何从函数中的局部变量xx拷贝来呢?可能的程序转换,分为以下两步:
- 函数增加一个引用参数,用代表返回值;
- 在return指令前,增加一个拷贝构造函数,以将局部变量修改引用参数。
// 以反映出 copy constructor 的应用
// C++ 伪码
void
bar( X& _result ) // 译注:加上一个额外参数
{
X xx;
// 编译器所产生的 default constructor 调用操作
xx.X::X();
// 处理 xx
// 编译器所产生的 copy constructor 调用操作
__result.X::XX( xx );
return;
}
那么,调用处就会被改写成这样:
- 函数返回到临时变量(左值)
// --- 转换前 --- X xx = bar(); // --- 转换后 --- X xx; bar( xx );
- 函数返回右值直接调用
// --- 转换前 --- bar().memfunc();// 译注:执行 bar() 所传回之 x class object 的memfunc() // --- 转换后 --- X temp0; ( bar( temp0 ),temp0 ) .memfunc();
- 函数指针
// --- 转换前 --- X ( *pf)(); pf = bar; // --- 转换后 --- void ( *pf )( X& ); pf = bar;
4、在使用者层面优化(Optimization at the user level)
针对这些程序改写的情况,我们做出一些优化,来避免临时对象的构造拷贝与析构。
(1) 在使用者层面做优化(Optimization at the User Level)
程序员需要避免返回函数内局部变量的情况,
// 这导致 xx 被“memberwise”地拷贝到 编译器 产生的临时对象 result 之中。
X bar( const T &y, const T &z ){
X xx;
//..·以y和z 来处理 xX
return xx;
}
为了避免此情况,可以定义另一个 constructor,可以直接计算 xx 的值:
X bar( const T &y, const T &z ){
return x( y, z );
}
于是 result 被直接计算出来,而不是经由 copy constructor 拷贝而得!
// C++ 伪码
void bar( X & result ) { // 译注:上行是否应为 bar( X& result,const T &y,const T &z )
result.X::X( y, z );
return;
}
但这样不好,会导致出现大量的 constructor ,大大增加了代码维护难度。class 的设计是以效率考虑居多,而不是以“支持抽象化”为优先。
5、在编译器层面优化(Optimization at the Compiler level)
return 指令传回相同类型的局部变量(named value),因此编译器有可能自己做优化,也就是大名鼎鼎的NRVO。方法是以 resulf 参数取代 named return value。(针对3.1 的第二点)
X bar(){
x xx;
// .. 处理xx
return xx;
}
编译器把其中的 xx 以 __result 取代:
void bar( X & result ) {
// default constructor 被调用
// C++ 伪码
result.X::X();
// 直接处理result
return;
}
6、Copy Constructor: 要还是不要
🍉 什么时候需要?
- 对于仅值存储来说,默认的 bitwise copy 效率高,且安全(没有memory leak 和 addres aliasing 的问题);
- 如果你的 class 需要大量的 memberwise 初始化操作,例如以传值(by value)的方式传回 objects,那么在允许NRV 的前提下,提供一个 copy constructor 的 explicit inline 函数实体就非常合理。
已知下面的 3D 坐标点类:
class Point3d {
public:
Point3d( float x, float y, float z );
private:
float _x,_y, _z;
}
🍇应该如何实现
实现 copy constructor 的最简单方法像这样
Point3d;:Point3d( const Point3d &rhs )
x = rhs._x;
y = rhs._y;
z = rhs._z;
使用 C++ library 的 memcpy() 会更有效率,但有一定的约束前提。
Point3d::Point3d( const Point3d &rhs ){
memcpy( this, &rhs, sizeof( point3d ) );
}
不管使用 memcpy 或 memset,都只有在“classes 不含任何由编译器产生的内部 members”时才能有效运行。如果 Point3dclass 声明一个或一个以上 virtual functions,或内含一个 virtual base class,那么使用上述函数将会导致那些“被编译器产生的内部 members”的初值被改写。例如,已知下面声明:
// 扩张后的 constructor
// C++ 伪码
Shape:: shape () {
// vptr 必须在使用者的代码执行之前先设定妥当
_vptr_shape =_vtbl Shape;
// 喔欧:memset 会将 vptr 清为 0
memset( this, 0, sizeof( Shape ) );
};
请注意:
- copy constructor 的应用,迫使编译器多多少少对你的程序代码做部分转化。
- 尤其是 当一个函数以传值(by value)的方式传回一个 class object,而该 class 有 copy constructor,可能会触发NRVO。
参考