标签:
#include "X.h" X foo() { X xx; return xx; }看到这个代码,可能做出以下假设:
X x0; //下面有三个定义,每一个都明显地以x0来初始化其 class object: void foo_bar() { X x1(x0); X x2 = x0; X x3 = X(x0); }必要的程序转化有两个阶段:
// 可能的程序转换 // C++伪码 void foo_bar() { X x1; //定义被重写,初始化操作被剥除 X x2; //定义被重写,初始化操作被剥除 X x3; //定义被重写,初始化操作被剥除 // 编译器插入X copy constructor的调用操作 x1.X::X(x0); x2.X::X(x0); x3.X::X(x0); }其中的:
x1.X::X(x0);就表现出对以下的copy constructor的调用:
X::X(const X &xx);
X xx = arg;其中xx代表形式参数(或返回值),而arg代表真正的参数值,因此,若已知这个函数:
void foo(X x0);下面这样的调用方式:
X xx; foo(xx);将会要求局部实体(local instance)x0以memberwise的方式将xx当做初值。在编译器实现技术上,有一种策略是导入所谓的暂时性object,并调用copy constructor将它初始化,然后将该暂时性object交给函数。例如将前一段程序代码转换如下:
// C++伪码 // 编译器产生出来的暂时对象 X __temp0; // 编译器对copy constructor的调用 __temp0.X::X(xx); // 重新改写函数调用操作,以便使用上述的暂时对象 foo(__temp0);注意:在C++中作用域运算符::的优先级最高,__temp0.X::X(xx)相当于__temp0.(X::X(xx))
void foo(X &x0);其中 class X声明了一个destructor,它会在foo()函数完成之后被调用,对付那个暂时性的object。
X bar() { X xx; // 处理xx... return xx; }可能会问bar()的返回值如何从局部对象xx中拷贝过来?Stroustrup在cfront中的解决办法是一个双阶段转化:
// 函数转换以反映出copy constructor的应用 // C++伪代码 void bar(X &__result) // 加上一个额外参数 { X xx; // 编译器所产生的default constructor调用操作 xx.X::X(); // ...处理xx // 编译器所产生的copy constructor调用操作 __result.X::XX(xx); return; }现在编译器必须转换每一个bar()调用操作,以反映其新定义。例如:
X xx = bar();将被转换为下列两个指令句:
// 注意,不必施行default constructor X xx; bar(xx);而:
bar().memfunc();可能被转化为:
// 编译器所产生的暂时现象 X __temp0; (bar(__temp0), __temp0).memfunc();同样道理,如果程序声明了一个函数指针,例如:
X (*pf)(); pf = bar;它必须被转化为:
void (*pf)(X &); pf = bar;
X bar(const T &y, const T &z) { X xx; // ...以y和z来处理xx return xx; }那会要求xx被"memberwise"地拷贝到编译器所产生的__result中,Jonathan定义另一个constructor,可以直接计算xx的值:
X bar(const T &y, const T &z) { return X(y, z); }于是当bar()定义被转换之后,效率会比较高:
// C++伪代码 void bar(X &__result) { __result.X::X(y, z); return; }__result被直接计算出来,而不是经由copy constructor拷贝得到。不过这种解决方法受到了某种批评,担心那些特殊计算用途的constructor可能会大量扩散。在这个层次上, class 的设计是以效率考虑居多,而不是以"支持抽象化"为优先。
bar()定义: X bar() { X xx; // ...处理xx return xx; }编译器把其中的xx以及__result取代:
void bar(X &__result) { // default constructor 被调用 // C++伪代码 __result.X::X(); // ...直接处理__result return; }这样的编译器优化操作,有时被称为Named Return Value(NRV)优化。NRV优化如今被视为是标准C++编译器的一个义不容辞的优化操作——虽然其需求其实超越了正式标准之外,为了对效率的改善有所感觉,请看下面的例子:
class test { friend test foo(double); public: test() { memset(array, 0, 100 *sizeof(double)); } private: double array[100]; };同时考虑以下函数,它产生、修改,并传回一个test class object:
test foo(double val) { test local; local.array[0] = val; local.array[99] = val; return local; }有一个main()函数调用上述foo()函数一千万次:
main() { for (int cnt = 0; cnt < 10000000; cnt++) { test t = foo(double(cnt)); } return 0; } // 整个程序的意义是重复循环1000万次,每次产生一个test object // 每个test object配置一个拥有100个double的数组,所有的元素都设初值为0 // 只有#0和#9元素以循环计数器的值作为初值这个程序的第一个版本不能实施NRV优化,因为test class 缺少一个copy constructor。第二个版本加上一个online copy constructor如下:
inline test::test(const test &t) { memcpy(this, &t, sizeof(test)); } // 不要忘记在 class test声明中加上一个member function如下 // public: // inline test(const test &t);这个copy constructor的出现激活了C++编译器的NRV优化,NRV优化的执行并不通过另外独立的优化工具完成。
void foo() { // 这里希望有一个copy constructor X xx = bar(); // ... // 这里调用destructor }在此情况下,对称性被优化给打破:程序虽然比较快,确实错误的。难道编译器因为抑制copy constructor的调用而在这里出错吗?也就是说,难道copy constructor在"object是经由copy而完成其初始化"的情况下,一定要被调用吗?
X xx0(1024); X xx1 = X(1024); X xx2 = (X)1024;但是在第二行和第三行中,语法明显地提供了两个步骤的初始化操作:
// C++伪代码 xx0.X::X(1024);而xx1或xx2却调用两个constructor,产生一个暂时性object,并针对该暂时性objet调用 class X的destructor:
// C++伪代码 X __temp0; __temp0.X::X(1024); xx1.X::X(__temp0); __temp0.X::~X();一般而言,面对"以一个 class object作为另一个 class object的初值"的情况,语言允许编译器有大量的自由发挥空间,其好处是导致码产生时有明显的效率提升,缺点是不能够安全地规划copy constructor的副作用,必须视其执行而定。
class Point3d { public: Point3d(float x, float y, float z); private: float _x, _y, _z; };上述 class 的 default copy constructor被视为trivial。它既没有任何member(或base) class object带有的copy constructor,也没有任何的 virtual base class 或 virtual function,所以默认情况下,一个Point3d class object的"memberwise"初始化操作会导致"bitwise copy",这样的效率很高,但是安全吗?
Point3d operator+(const Point3d &, const Point3d &); Point3d operator-(const Point3d &, const Point3d &); Point3d operator*(const Point3d &, int);所有的那些函数都能够良好地符合NRV template:
{ Point3d result; // 计算result return result; }实现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(),都只有在"class不含任何由编译器产生的内部members"时才能有效地运行。如果Point3d class 声明一个或一个以上的 virtual functions,或内含一个 virtual base class ,那么使用上述函数将会导致那些"被编译器产生的内部members"的初值被改写。例如,已知下面声明:
class Shape { public: // 这会改变内部的vptr Shape() { memset(this, 0, sizeof(Shape)); } virtual ~Shape(); };编译器为此constructor扩张的内容看起来像是:
// 扩张后的constructor // C++伪代码 Shape::Shape() { // vptr必须在使用者的代码执行之前先设定妥当 __vptr__Shape = __vtbl__Shape; // memset会将vptr清为0 memset(this, 0, sizeof(Shape)); };如上所述,如要正确使用memset()和memcpy(),需要掌握某些C++ Object Model的语意学知识。
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:
原文地址:http://blog.csdn.net/yiranant/article/details/47171553