📄 ch04.htm
字号:
<p>A copy constructor is said to be <i>trivial</i> if it is implicitly declared, if its class has no virtual member functions and no virtual base classes, and if its entire direct base classes and embedded objects have trivial copy constructors. The implementation implicitly defines an implicitly-declared, nontrivial copy constructor to initialize an object of its type from a copy of an object of its type (or from one derived from it). The implicitly-defined copy constructor performs a memberwise copy of its subobjects, as in the following example:</p><pre><tt>#include<string></tt><tt>using std::string;</tt><tt>class Website //no user-defined copy constructor</tt><tt>{</tt><tt>private:</tt><tt> string URL;</tt><tt> unsigned int IP;</tt><tt>public:</tt><tt> Website() : IP(0), URL("""") {}</tt><tt>};</tt><tt>int main ()</tt><tt>{</tt><tt> Website site1;</tt><tt> Website site2(site1); //invoke implicitly-defined copy constructor</tt><tt>}</tt></pre><p>The programmer did not declare a copy constructor for class <tt>Website</tt>. Because <tt>Website</tt> has an embedded object of type <tt>std::string</tt>, which happens to have a user-defined copy constructor, the implementation implicitly defines a copy constructor for class <tt>Website</tt> and uses it to copy construct the object <tt>site2</tt> from <tt>site1</tt>. The synthesized copy constructor first invokes the copy constructor of <tt>std::string</tt>, and then performs a bitwise copying of the data members of <tt>site1</tt> into <tt>site2</tt>.</p><p>Novices are sometimes encouraged to define the four special member functions for every class they write. As can be seen in the case of the <tt>Website</tt> class, not only is this unnecessary, but it is even undesirable under some conditions. The synthesized copy constructor (and the assignment operator, as you are about to see) already do the "right thing". They automatically invoke the constructors of base and member subobjects, they initialize the virtual pointer (if one exists), and they perform a bitwise copying of fundamental types. In many cases, this is exactly the programmer's intention anyway. Furthermore, the synthesized constructor and copy constructor enable the implementation to create code that is more efficient than user-written code because it can apply optimizations that are not always possible otherwise.</p><h3> <a name="Heading16">Implementation-Required Initializations</a></h3><p>Like ordinary constructors, copy constructors -- either implicitly-defined or user-defined -- are augmented by the compiler, which inserts additional code into them to invoke the copy constructors of direct base classes and embedded objects. It is guaranteed, however, that virtual base subobjects are copied only once.</p><h2> <a name="Heading17">Simulating Virtual Constructors</a></h2><p>Unlike ordinary member functions, a constructor has to know the exact type of its object at compile time in order to construct it properly. Consequently, a constructor cannot be declared <tt>virtual</tt>. Still, creating an object without knowing its exact type is useful in certain conditions. The easiest way to simulate virtual construction is by defining a virtual member function that returns a constructed object of its class type. For example</p><pre><tt>class Browser</tt><tt>{</tt><tt>public:</tt><tt> Browser();</tt><tt> Browser( const Browser&);</tt><tt> virtual Browser* construct() </tt><tt> { return new Browser; } //virtual default constructor</tt><tt> virtual Browser* clone() </tt><tt> { return new Browser(*this); } //virtual copy constructor</tt><tt> virtual ~Browser();</tt><tt>//...</tt><tt>};</tt><tt>class HTMLEditor: public Browser</tt><tt>{</tt><tt>public:</tt><tt> HTMLEditor ();</tt><tt> HTMLEditor (const HTMLEditor &);</tt><tt> HTMLEditor * construct() </tt><tt> { return new HTMLEditor; }//virtual default constructor</tt><tt> HTMLEditor * clone() </tt><tt> { return new HTMLEditor (*this); } //virtual copy constructor</tt><tt> virtual ~HTMLEditor();</tt><tt> //...</tt><tt>};</tt></pre><p>The polymorphic behavior of the member functions <tt>clone()</tt> and <tt>construct()</tt> enables -you to instantiate a new object of the right type, without having to know the exact type of the source object.</p><pre><tt>void create (Browser& br)</tt><tt>{</tt><tt> br.view();</tt><tt> Browser* pbr = br.construct();</tt><tt> //...use pbr and br</tt><tt> delete pbr;</tt><tt>}</tt></pre><p><tt>pbr</tt> is assigned a pointer to an object of the right type -- either <tt>Browser</tt> or any class publicly derived from it. Note that the object <tt>br</tt> does not delete the new object it has created; this is the user's responsibility. If it did, the lifetime of the reproduced objects would depend on the lifetime of their originator -- which would significantly compromise the usability of this technique.</p><h3> <a name="Heading18">Covariance of Virtual Member Functions</a></h3><p>The implementation of virtual constructors relies on a recent modification to C++, namely virtual functions' covariance. An overriding virtual function has to match the signature and the return type of the function it overrides. This restriction was recently relaxed to enable the return type of an overriding virtual function to co-vary with its class type. Thus, the return type of a public base can be changed to the type of a derived class. The covariance applies only to pointers and references.</p><blockquote> <hr> <strong>CAUTION: </strong> Please note that some compilers do not support virtual member functions' covariance yet. <hr></blockquote><h2> <a name="Heading19">Assignment Operator</a></h2><p>A user-declared assignment operator of class <tt>C</tt> is a nonstatic, nontemplate member function of its class, taking exactly one argument of type <tt>C</tt>, <tt>C&</tt>, <tt>const C&</tt>, <tt>volatile C&</tt>, or <tt>const volatile C&</tt>.</p><h3> <a name="Heading20">Implicitly-Defined Assignment Operator</a></h3><p>If there is no user-defined assignment operator for a class, the implementation implicitly declares one. An implicitly-declared assignment operator is an <tt>inline public</tt> member of its class, and it has the form</p><pre><tt>C& C::operator=(const C&);</tt></pre><p>if each base class of <tt>C</tt> has an assignment operator whose first argument is a reference to a <tt>const</tt> object of base class type, and if all the nonstatic embedded objects in <tt>C</tt> also have an assignment operator that takes a reference to a <tt>const</tt> object of their type. Otherwise, the implicitly-declared assignment operator is of the following type:</p><pre><tt>C& C::operator=(C&);</tt></pre><p>An implicitly-declared assignment operator has an exception specification. The exception specification contains all the exceptions that might be thrown by other special functions that the assignment operator invokes directly. An assignment operator is said to be <i>trivial</i> if it is implicitly declared, if its class has no virtual member functions or virtual base classes, and if its direct base classes and embedded objects have a trivial assignment operator.</p><h3> <a name="Heading21">Simulating Inheritance Of Assignment Operator</a></h3><p>Because an assignment operator is implicitly declared for a class if it is not declared by the programmer, the assignment operator of a base class is always hidden by the assignment operator of a derived class. In order to extend -- rather than override -- the assignment operator in a derived class, you must first invoke the assignment operator of the base explicitly, and then add the operations that are required for the derived class. For example</p><pre><tt>class B</tt><tt>{</tt><tt>private:</tt><tt> char *p;</tt><tt>public:</tt><tt> enum {size = 10};</tt><tt> const char * Getp() const {return p;}</tt><tt> B() : p ( new char [size] ) {}</tt><tt> B& operator = (const C& other);</tt><tt> {</tt><tt> if (this != &other)</tt><tt> strcpy(p, other.Getp() );</tt><tt> return *this;</tt><tt> }</tt><tt> };</tt><tt>class D : public B</tt><tt>{</tt><tt>private:</tt><tt> char *q;</tt><tt>public:</tt><tt> const char * Getq() const {return q;}</tt><tt> D(): q ( new char [size] ) {}</tt><tt> D& operator = (const D& other)</tt><tt> {</tt><tt> if (this != &other)</tt><tt> { </tt><tt> B::operator=(other); //first invoke base's assignment operator explicitly</tt><tt> strcpy(q, (other.Getq())); //add extensions here</tt><tt> }</tt><tt> return *this;</tt><tt> }</tt><tt>};</tt></pre><h2> <a name="Heading22"> When Are User-Written Copy Constructors And Assignment Operators Needed?</a></h2><p>The synthesized copy constructor and assignment operator perform a memberwise copy. This is the desirable behavior for most uses. However, it can be disastrous for classes that contain pointers, references, or handles. In such cases, you have to define a copy constructor and assignment operator to avoid <i>aliasing</i>. Aliasing occurs when the same resource is used simultaneously by more than one object. For example</p><pre><tt>#include <cstdio></tt><tt>using namespace std;</tt><tt>class Document</tt><tt>{</tt><tt>private:</tt><tt> FILE *pdb;</tt><tt>public:</tt><tt> Document(const char *filename) {pdb = fopen(filename, "t");}</tt><tt> Document(FILE *f =NULL) : pdb{}</tt><tt> ~Document() {fclose(pdb);} //bad, no copy constructor </tt><tt> //or assignment operator defined</tt><tt>};</tt><tt>void assign(Document& d)</tt><tt>{</tt><tt> Document temp("letter.doc");</tt><tt> d = temp; //Aliasing; both d and temp are pointing to the same file</tt><tt>}//temp's destructor is now called and closes file while d is still using it</tt><tt>int main()</tt><tt>{</tt><tt> Document doc;</tt><tt> assign(doc);</tt><tt> return 0;</tt><tt> //doc now uses a file which has just been closed. disastrous</tt><tt>}}//OOPS! doc's destructor is now invoked and closes 'letter.doc' once again</tt></pre><p>Because the implementer of class <tt>Document</tt> did not define a copy constructor and assignment operator, the compiler defined them implicitly. However, the synthesized copy constructor and assignment operator result in aliasing. An attempt to open or close the same file twice yields undefined behavior. One way to solve this problem is to define an appropriate copy constructor and assignment operator. Please note, however, that the aliasing results from the reliance on low-level language constructs (file pointers in this case), whereas an embedded <tt>fstream</tt> object can perform the necessary checks automatically. In that case, a user-written copy constructor and assignment operator are unnecessary. The same problem occurs when bare pointers to <tt>char</tt> are used as data members instead of as <tt>string</tt> objects. If you use a pointer to <tt>char</tt> rather than <tt>std::string</tt> in class <tt>Website</tt>, you face an aliasing problem as well.</p><h2> <a name="Heading23">Implementing Copy Constructor And Assignment Operator</a></h2><p>Another conclusion that can be drawn from the preceding example is that whenever you define a copy constructor, you must also define the assignment operator. When you define only one of the two, the compiler creates the missing one -- but it might not work as expected.</p><blockquote> <hr> <b>The "Big Three Rule" or the "Big Two Rule"? </b> <p> The famous "Big Three Rule" says that if a class needs any of the Big Three member functions (copy constructor, assignment operator, and destructor), it needs them all. Generally, this rule refers to classes that allocate memory from the free store. However, many other classes require only that the Big Two (copy constructor and assignment operator) be defined by the user; the destructor, nonetheless, is not always required. Examine the followingexample:</p> <pre><tt>class Year</tt><tt>{ </tt><tt>private: </tt><tt> int y;</tt><tt> bool cached; //has the object been cached? </tt><tt>public:</tt><tt> //...</tt><tt> Year(int y);</tt><tt> Year(const Year& other) //cached should not be copied</tt><tt> { </tt><tt> y = other.getYear();</tt><tt> } </tt><tt> Year& operator =(const Year&other) //cached should not be copied</tt><tt> { </tt><tt> y = other.getYear(); </tt><tt> return *this;</tt><tt> } </tt><tt> int getYear() const { return y; }</tt><tt>};//no destructor required for class Year</tt></pre> Class <tt>Year</tt> does not allocate memory from the free store, nor does it acquire any other resources during its construction. A destructor is therefore unnecessary. However, the class needs a user-defined copy constructor and assignment operator to ensure that the value of the member that is <tt>cached</tt> is not copied because it is calculated for every individual object separately. <hr></blockquote><p>When a user-defined copy constructor and assignment operator are needed, it is important to implement them in a way that prevents self-assignment or aliasing. Usually, it is sufficient to fully implement only one of the two, and then define
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -