C++对象模型(6) - Program Transformation Semantics

C++对象模型(6) - Program Transformation Semantics

/********************************************\
|    欢迎转载, 但请保留作者姓名和原文链接, 祝您进步并共勉!     |
\********************************************/

C++对象模型(6) -  Program Transformation Semantics

作者: Jerry Cat
时间: 2006/05/11
链接:  http://www.cppblog.com/jerysun0818/archive/2006/05/11/6912.html

2.3 Program Transformation Semantics
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
1). Explicit Initialization:

Given the definition
X x0;
the following three definitions each explicitly initialize its class object with x0:

void foo_bar() {
   X x1( x0 );
   X x2 = x0;
   X x3 = x( x0 );
   // ...
}
The required program transformation is two-fold:

Each definition is rewritten with the initialization stripped out.
An invocation of the class copy constructor is inserted.
For example, foo_bar() might look as follows after this straightforward, two-fold transformation:

// Possible program transformation Pseudo C++ Code
void foo_bar() {
   X x1;
   X x2;
   X x3;

   // compiler inserted invocations of copy constructor for X
   x1.X::X( x0 );
   x2.X::X( x0 );
   x3.X::X( x0 );
   // ...
}
where the call

x1.X::X( x0 );
represents a call of the copy constructor

X::X( const X& xx );

2). Argument Initialization尽量不用传值法, 要穿指针或引用. 传值法开销大效率低,
    更要命的是涉及到深浅拷贝以及, 局部变量和临时对象的销毁问题.

3). Return Value Initialization(双重变形, Bjarne Stroutstrup的trick):
(按: 返回值(不是引用或指针,返回的是value), 其实是让一外部对象的引用做一个"悄然追加"
     的参数(编译器偷着干的, 你是看不见的:), 然后是空返回, 你的返回值呢? 诺, 就是那
     以"外追"方式进入函数内部参与处理的引用呵^_^ )

Given the following definition of bar():
X bar()
{
   X xx;
   // process xx ...
   return xx;
}
you may ask how might bar()'s return value be copy constructed from its local object xx?
Stroustrup's solution in cfront is a two-fold transformation:

Add an additional argument of type reference to the class object. This argument will hold the
copy constructed "return value."

Insert an invocation of the copy constructor prior to the return statement to initialize the
added argument with the value of the object being returned.

What about the actual return value, then? A final transformation rewrites the function to have
it not return a value. The transformation of bar(), following this algorithm, looks like this:

// function transformation to reflect application of copy constructor Pseudo C++ Code
void bar( X& __result )
{
   X xx;

   // compiler generated invocation of default constructor
   xx.X::X();
   // ... process xx

   // compiler generated invocation of copy constructor
   __result.X::X( xx );

   return;
}
Given this transformation of bar(), the compiler is now required to transform each invocation
of bar() to reflect its new definition. For example,

X xx = bar();
is transformed into the following two statements:

// note: no default constructor applied
X xx;
bar( xx );
while an invocation such as

bar().memfunc();
might be transformed into

// compiler generated temporary
X __temp0;
( bar( __temp0 ), __temp0 ).memfunc();
Similarly, if the program were to declare a pointer to a function, such as

X ( *pf )();
pf = bar;
that declaration, too, would need to be transformed:

void ( *pf )( X& );
pf = bar;

4). Optimization at the Compiler Level:
In a function such as bar(), where all return statements return the same named value, it is
possible for the compiler itself to optimize the function by substituting the result argument
for the named return value. For example, given the original definition of bar():

X bar()
{
   X xx;
   // ... process xx
   return xx;
}
__result is substituted for xx by the compiler:

void
bar( X &__result )
{
   // default constructor invocation Pseudo C++ Code
   __result.X::X();
   // ... process in __result directly

   return;
}
This compiler optimization, sometimes referred to as the Named Return Value (NRV) optimization.

Although the following three initializations are semantically equivalent:

X xx0( 1024 );
X xx1 = X( 1024 );
X xx2 = ( X ) 1024;
in the second and third instances, the syntax explicitly provides for a two-step initialization:
Initialize a temporary object with 1024.

Copy construct the explicit object with the temporary object.

That is, whereas xx0 is initialized by a single constructor invocation

// Pseudo C++ Code
xx0.X::X( 1024 );
a strict implementation of either xx1 or xx2 results in two constructor invocations, a temporary
object, and a call to the destructor of class X on that temporary object:

// Pseudo C++ Code
X __temp0;
__temp0.X::X( 1024 );
xx1.X::X( __temp0 );
__temp0.X::~X();

5). The Copy Constructor: To Have or To Have Not?
    =============================================
Given the following straightforward 3D point class:

class Point3d {
public:
   Point3d( float x, float y, float z );
   // ...
private:
   float _x, _y, _z;
};
should the class designer provide an explicit copy constructor?

The default copy constructor is considered trivial. There are no member or base class objects
with a copy constructor that need to be invoked. Nor is there a virtual base class or virtual
function associated with the class. So, by default, a memberwise initialization of one Point3d
class object with another results in a bitwise copy. This is efficient. But is it safe?

The answer is yes. The three coordinate members are stored by value. Bitwise copy results in
neither a memory leak nor address aliasing. Thus it is both safe and efficient.

So, how would you answer the question, should the class designer provide an explicit copy
constructor? The obvious answer, of course, is no. There is no reason to provide an instance
of the copy constructor, as the compiler automatically does the best job for you. The more subtle
answer is to ask whether you envision the class's requiring a good deal of memberwise
initialization, in particular, returning objects by value? If the answer is yes, then it makes
excellent sense to provide an explicit inline instance of the copy constructor that is, provided
your compiler provides the NRV optimization(虚拟语气).

For example, the Point3d class supports the following set of functions:

Point3d operator+( const Point3d&, const Point3d& );
Point3d operator-( const Point3d&, const Point3d& );
Point3d operator*( const Point3d&, int );
etc.
all of which fit nicely into the NRV template
{
   Point3d result;
   // compute result
   return result
}
The simplest method of implementing the copy constructor is as follows:

Point3d::Point3d( const Point3d &rhs )
{
   _x = rhs._x;
   _y = rhs._y;
   _z = rhs._z;
};
This is okay, but use of the C library memcpy() function would be more efficient:

Point3d::Point3d( const Point3d &rhs )
{
   memcpy( this, &rhs, sizeof( Point3d );
};
Use of both memcpy() and memset(), however, works only if the classes do not contain any
compiler-generated internal members. If the Point3d class declares one or more virtual functions
or contains a virtual base class, use of either of these functions will result in overwriting the
values the compiler set for these members. For example, given the following declaration:

class Shape {
public:
   // oops: this will overwrite internal vptr!
   Shape() { memset( this, 0, sizeof( Shape ));
   virtual ~Shape();
   // ...
};
the compiler augmentation for the constructor generally looks like this:

// Expansion of constructor Pseudo C++ Code
Shape::Shape()
{
   // vptr must be set before user code executes
   __vptr__Shape = __vtbl__Shape;

   // oops: memset zeros out value of vptr
   memset( this, 0, sizeof( Shape ));
};
As you can see, correct use of the memset() and memcpy() functions requires some knowledge of the
C++ Object Model semantics! 嘿, 把C库扯进来了, 强! C库中许多强调性能,效率的函数是用汇编写的

Summary: 编译器尽可能地"优化掉"拷贝构造函数, 代之以NRV...
---------------------------------------------------------
Application of the copy constructor requires the compiler to more or less transform portions of
your program. In particular, consider a function that returns a class object by value for a class
in which a copy constructor is either explicitly defined or synthesized. The result is profound
program transformations both in the definition and use of the function. Also, the compiler
optimizes away the copy constructor invocation where possible, replacing the NRV with an additional
first argument within which the value is stored directly. Programmers who understand these
transformations and the likely conditions for copy constructor optimization can better control the
runtime performance of their programs.

你可能感兴趣的