/********************************************\
|????歡迎轉載, 但請保留作者姓名和原文鏈接, 祝您進步并共勉!???? |
\********************************************/
C++對象模型(5) -? Copy Constructor Construction
作者: Jerry Cat
時間: 2006/05/05
鏈接:?http://www.shnenglu.com/jerysun0818/archive/2006/05/05/6632.html
2.2 Copy Constructor Construction
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
引子:
Say the class designer explicitly defines a copy constructor (a constructor requiring a single
argument of its class type), such as either of the following:
// examples of user defined copy constructors may be multi-argument provided each second
// and subsequent argument is provided with a default value
X::X( const X& x );
Y::Y( const Y& y, int = 0 );
In this case, that constructor is invoked, under most circumstances, in each program instance
where initialization of one class object with another occurs. This may result in the generation
of a temporary class object or the actual transformation of program code (or both).
1). Default Memberwise Initialization(注:對內建成員對象可遞歸用之):
bitwise:按位
memberwise:逐成員
What if the class does not provide an explicit copy constructor? Each class object initialized
with another object of its class is initialized by what is called default memberwise initialization.
Default memberwise initialization copies the value of each built-in or derived data member (such
as a pointer or an array) from the one class object to another. A member class object, however,
is not copied; rather, memberwise initialization is recursively applied. For example, consider
the following class declaration:
class String {
public:
?? // ... no explicit copy constructor
private:
?? char *str;
?? int?? len;
};
The default memberwise initialization of one String object with another, such as
String noun( "book" );
String verb = noun;
is accomplished as if each of its members was individually initialized in turn:
// semantic equivalent of memberwise initialization
verb.str = noun.str;
verb.len = noun.len;
If a String object is declared as a member of another class, such as the following:
class Word {
public:
?? // ...no explicit copy constructor
private:
?? int???? _occurs;
?? String? _word;
};
then the default memberwise initialization of one Word object with another copies the value of
its built-in member _occurs and then recursively applies memberwise initialization to its class
String member object _word.
How is this operation in practice carried out? The original ARM tells us:
??? Conceptually, for a class X [this operation is] implemented by…a copy constructor.
The operative word here is conceptually. The commentary that follows explains:
??? In practice, a good compiler can generate bitwise copies for most class objects since they
??? have bitwise copy semantics….
That is, a copy constructor is not automatically generated by the compiler for each class that
does not explicitly define one. Rather, as the ARM tells us:
?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? 缺省者按需也
??? Default constructors and copy constructors…are generated (by the compiler) where needed.
Needed in this instance means when the class does not exhibit bitwise copy semantics.(此處的
"needed"意味著彼時該類并無按位拷貝之意). The Standard retains the meaning of the ARM, while
formalizing its discussion (my comments are inserted within parentheses) as follows:
??? A class object can be copied in two ways, by initialization (what we are concerned with
??? here)…and by assignment (treated in Chapter 5). Conceptually (my italics), these two
??? operations are implemented by a copy constructor and copy assignment operator.
As with the default constructor, the Standard speakers of an implicitly declared and implicitly
defined copy constructor if the class does not declare one. As before, the Standard distinguishes
between a trivial and nontrivial copy constructor. It is only the nontrivial instance that in
practice is synthesized within the program(實際上只有"并非不重要"的拷貝構造函數才會被合成進程序
中). The criteria for determining whether a copy constructor is trivial is whether the class
exhibits bitwise copy semantics(類只對"不重要"的拷貝構造函數進行按位拷貝). In the next
section, I look at what it means to say that a class exhibits bitwise copy semantics.
2). Bitwise Copy Semantics:
In the following program fragment:
#include "Word.h"
Word noun( "block" );
void foo()
{
?? Word verb = noun;
?? // ...
}
it is clear that verb is initialized with noun. But without looking at the declaration of class
Word, it is not possible to predict the program behavior of that initialization. If the designer
of class Word defines a copy constructor, the initialization of verb invokes it. If, however,
the class is without an explicit copy constructor, the invocation of a compiler-synthesized
instance depends on whether the class exhibits bitwise copy semantics. For example, given the
following declaration of class Word:
// declaration exhibits bitwise copy semantics
class Word {
public:
?? Word( const char* );
?? ~Word() { delete [] str; }
?? // ...
private:
?? int?? cnt;
?? char *str;
};
a default copy constructor need not be synthesized, since the declaration exhibits bitwise copy
semantics, and the initialization of verb need not result in a function call.
However, the following declaration of class Word does not exhibit bitwise copy semantics:
此時的按位拷貝將是致命的, 最好應顯式定義一拷貝構造函數, 用new從堆中另辟內存塊空間.
??? Of course, the program fragment will execute disastrously given this declaration of class
??? Word. (Both the local and global object now address the same character string. Prior to
??? exiting foo(), the destructor is applied to the local object, thus the character string is
??? deleted. The global object now addresses garbage.) The aliasing problem with regard to member
??? str can be solved only by overriding default memberwise initialization with an explicit copy
??? constructor implemented by the designer of the class (or by disallowing copying altogether).
??? This, however, is independent of whether a copy constructor is synthesized by the compiler.
// declaration does not exhibits bitwise copy semantics
class Word {
public:
?? Word( const String& );
?? ~Word();
?? // ...
private:
?? int??? cnt;
?? String str;
};
where String declares an explicit copy constructor:
class String {
public:
?? String( const char * );
?? String( const String& );
?? ~String();
?? // ...
};
In this case, the compiler needs to synthesize a copy constructor in order to invoke the copy
constructor of the member class String object:
// A synthesized copy constructor (Pseudo C++ Code)
inline Word::Word( const Word& wd )
{
?? str.String::String( wd.str );
?? cnt = wd.cnt;
}
It is important to note that in the case of the synthesized copy constructor, the nonclass
members of types such as integers, pointers, and arrays are also copied, as one would expect.
3). Bitwise Copy Semantics Not! (此時)對按位拷貝大聲說不!
When are bitwise copy semantics not exhibited by a class? There are four instances:
(1). When the class contains a member object of a class for which a copy constructor exists
???? (either explicitly declared by the class designer, as in the case of the previous String
???? class, or synthesized by the compiler, as in the case of class Word)
(2). When the class is derived from a base class for which a copy constructor exists (again,
???? either explicitly declared or synthesized)
(3). When the class declares one or more virtual functions
(4). When the class is derived from an inheritance chain in which one or more base classes are virtual
4). Resetting the Virtual Table Pointer:
Recall that two program "augmentations" occur during compilation whenever a class declares one
or more virtual functions.
??? A virtual function table that contains the address of each active virtual function associated
??? with that class (the vtbl) is generated.
??? A pointer to the virtual function table is inserted within each class object (the vptr).
Obviously, things would go terribly wrong if the compiler either failed to initialize or
incorrectly initialized the vptr of each new class object. Hence, once the compiler introduces
a vptr into a class, the affected class no longer exhibits bitwise semantics. Rather, the
implementation now needs to synthesize a copy constructor in order to properly initialize the
vptr. Here's an example.
First, I define a two-class hierarchy of ZooAnimal and Bear:
class ZooAnimal {
public:
?? ZooAnimal();
?? virtual ~ZooAnimal();
?? virtual void animate();
?? virtual void draw();
?? // ...
private:
?? // data necessary for ZooAnimal's version of animate() and draw()
};
class Bear : public ZooAnimal {
public:
?? Bear();
?? void animate();
?? void draw();
?? virtual void dance();
?? // ...
private:
?? // data necessary for Bear's version of animate(), draw(), and dance()
};
The initialization of one ZooAnimal class object with another or one Bear class object with
another is straightforward and could actually be im-plemented with bitwise copy semantics (apart
from possible pointer member aliasing, which for simplicity is not considered). For example, given
Bear yogi;
Bear winnie = yogi;
yogi is initialized by the default Bear constructor. Within the constructor, yogi's vptr is
initialized to address the Bear class virtual table with code inserted by the compiler. It is
safe, therefore, to simply copy the value of yogi's vptr into winnie's. The copying of an
object's vptr value, however, ceases to be safe when an object of a base class is initialized
with an object of a class derived from it. For example, given
ZooAnimal franny = yogi;
the vptr associated with franny must not be initialized to address the Bear class virtual table
(which would be the result if the value of yogi's vptr were used in a straightforward bitwise
copy此時就不能按位拷貝). Otherwise the invocation of draw() in the following program fragment
would blow up when franny were passed to it:
??? [5]The draw() virtual function call through franny must invoke the ZooAnimal instance rather
??? than the Bear instance, even though franny is initialized with the Bear object yogi because
??? franny is a ZooAnimal object. In effect, the Bear portion of yogi is sliced off when franny
??? is initialized.
??? 虛擬語句(下面是多態的情況, 指針或引用):
??? ---------------------------------------
??? Were franny declared a reference (or were it a pointer initialized with the address of
??? yogi), then invocations of draw() through franny would invoke the Bear instance. This is
??? discussed in Section 1.3
void draw( const ZooAnimal& zoey ) { zoey.draw(); }
void foo() {
// franny's vptr must address the ZooAnimal virtual table not the Bear virtual table yogi's vptr addresses
????? ZooAnimal franny = yogi;
????? draw( yogi );?? // invoke Bear::draw()
????? draw( franny ); // invoke ZooAnimal::draw()
?? }
That is, the synthesized ZooAnimal copy constructor explicitly sets the object's vptr to the
ZooAnimal class virtual table rather than copying it from the right-hand class object.
5). Handling the Virtual Base Class Subobject:
The presence of a virtual base class also requires special handling. The initialization of one
class object with another in which there is a virtual base class subobject also invalidates
bitwise copy semantics. 又一次讓按位拷貝失效.
Each implementation's support of virtual inheritance involves the need to make each virtual base
class subobject's location within the derived class object available at runtime. Maintaining the
integrity of this location is the compiler's responsibility. Bitwise copy semantics could result
in a corruption of this location, so the compiler must intercede with its own synthesized copy
constructor. For example, in the following declaration, ZooAnimal is derived as a virtual base
class of Raccoon:
class Raccoon : public virtual ZooAnimal {
public:
?? Raccoon()????????? { /* private data initialization */ }
?? Raccoon( int val ) { /* private data initialization */ }
?? // ...
private:
?? // all necessary data
};
Compiler-generated code to invoke ZooAnimal's default constructor, initialize Raccoon's vptr,
and locate the ZooAnimal subobject within Raccoon is inserted as a prefix within the two Raccoon
constructors.
What about memberwise initialization? The presence of a virtual base class invalidates bitwise
copy semantics. (這才是Again, the problem is not when one object of a class is initialized with
a second object of the same exact class. It is when an object is initialized with an object of
one of its derived classes問題的實質). For example, consider the case in which a Raccoon object
is initialized with a RedPanda object, where RedPanda is declared as follows:
class RedPanda : public Raccoon {
public:
?? RedPanda()????????? { /* private data initialization */ }
?? RedPanda( int val ) { /* private data initialization */ }
?? // ...
private:
?? // all necessary data
};
Again, in the case of initializing one Raccoon object with another, simple bitwise copy is sufficient:
// simple bitwise copy is sufficient
Raccoon rocky;
Raccoon little_critter = rocky;
However, an attempt to initialize little_critter with a RedPanda object requires the compiler
to intercede, if subsequent programmer attempts to access its ZooAnimal subobject are to execute
properly (not an unreasonable programmer expectation!):(上一行are的主語是attempts)
// simple bitwise copy is not sufficient, compiler must explicitly initialize little_critter's
// virtual base class pointer/offset
RedPanda?? little_red;
Raccoon??? little_critter = little_red;
To summarize: We have looked at the four conditions under which bitwise copy semantics do not
hold for a class and the default copy constructor, if undeclared, is considered nontrivial
(在那四種不應進行按位拷貝的情形, 設計者如未申明則缺省的構造函數還是很有必要的). Under these
conditions, the compiler, in the absence of a declared copy constructor, must synthesize a copy
constructor in order to correctly implement the initialization of one class object with another.
In the next section, the implementation strategies for invoking the copy constructor and how
those strategies affect our programs are discussed.
posted on 2006-05-05 03:41
Jerry Cat 閱讀(1033)
評論(0) 編輯 收藏 引用