Tuesday, May 1, 2007

2.3 Program Transformation Semantics

Application of the copy constructor requires the compiler to more or less transform portions of your program.

Explicit Initialization

X x0;
void foo_bar()
{
X x1 (x0);
X x2 = x0;
X x3 = x(x0);
}

The required program transformation is two-fold:
1. Each definition is rewritten with the initialization stripped out
2. An invocation of the class copy constructor is inserted

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);
}

Argument Initialization

The standard states that passing a class object as an argument to a function ( or as that function's return value) is equivalent to the following form of initialization:

X xx = arg;

where xx represents the formal argument (or return value) and arg represents the actual argument.

void foo (X x0);
X xx;
//...
foo (xx);

One implementation strategy is to introduce a temporary object, initialize it with a call of the copy constructor, and then pass that temporary object to the function. The previous code fragment would be transformed as follows:

X __temp0;
__temp0.X::X(xx);
foo (_temp0); // the declaration of foo() must also be transformed as void foo( X& x0);

Another implementation is to copy construct the actual argument directly onto its place within the function's activation record on the program stack.

Return Value Initialization

X bar()
{
X xx;
// process xx ...
return xx;
}

How might bar()'s return value by copy constructed from its local object xx? Stroustrup's solution in cfront is a two-fold transformation:
1. Add an additional argument of type reference to the class object. This argument will hold the copy constructed "return value."
2. 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.

void bar (X& __result)
{
X xx;
xx.X::X(); // compiler generated invocation of default constructor
//...processing xx
__result.X::X(xx); //compiler generated invocation of copy constructor
return;
}

Optimization at the Compiler Level

Named Return Value (NRV) optimization: substituting the __result for the named return value xx. (All return statements return the same named value)

void bar(X& __result)
{
__result.X::X(); // We save a copy constructor
return;
}

X xx = bar(); will be transformed into:
//note: no default constructor applied
X xx;
bar(xx);

bar().memfunc(); will be transformed into:
X __temp0; // compiler generated temporary
(bar(__temp0), __temp0).memfunc();

The NRV optimization is now considered an obligatory Standard C++ compiler optimization. The presence of the copy constructor will "turn on" the NRV optimization within the C++ compiler.

The following three initializations are semantically equivalent:

X xxx0(1024);
//the following two result in two constructor invocations, a temporary object, and a call to the destructor of class X on the temporary object.
X xxx1 = X(1024);
X xxx2 = (X) 1024;

The second and third will be tranformed into:
X __temp0;
__temp0.X::X(1024);
xxx1.X::X(__temp0);
__temp0.X::~X();

The copy constructor: To Have or To Have Not?
If the default trivial copy constructor is efficient, there is no need to provide your own copy constructor. If you envision returning by value, then it makes excellent sense to provide an explicit inline instance of the copy constructor - that is, provided your compiler provides the NRV optimization.

No comments: