asm, c, c++ are my all
-- Core In Computer
posts - 139,  comments - 123,  trackbacks - 0

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

C++对象模型(5) -  Copy Constructor Construction

作者: Jerry Cat
时间: 2006/05/05

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(注:对内建成员对象可递归用之):
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 {
   // ... no explicit copy constructor
   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 {
   // explicit copy constructor
   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 {
   Word( const char* );
   ~Word() { delete [] str; }
   // ...
   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 {
   Word( const String& );
   // ...
   int    cnt;
   String str;
where String declares an explicit copy constructor:

class String {
   String( const char * );
   String( const 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 {
   virtual ~ZooAnimal();

   virtual void animate();
   virtual void draw();
   // ...
   // data necessary for ZooAnimal's version of animate() and draw()

class Bear : public ZooAnimal {

   void animate();
   void draw();
   virtual void dance();
   // ...
   // 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 {
   Raccoon()          { /* private data initialization */ }
   Raccoon( int val ) { /* private data initialization */ }
   // ...
   // 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

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 {
   RedPanda()          { /* private data initialization */ }
   RedPanda( int val ) { /* private data initialization */ }
   // ...
   // 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 阅读(918) 评论(0)  编辑 收藏 引用

【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理