📄 ch04.htm
字号:
<tt>}</tt></pre><p>Extensive amounts of legacy C++ code rely on the implicit conversion of constructors. The C++ Standardization committee was aware of that. In order to not make existing code break, the implicit conversion was retained. However, a new keyword, <tt>explicit</tt>, was introduced to the languageto enable the programmer to block the implicit conversion when it is undesirable. As a rule, a constructor that can be invoked with a single argument needs to be declared <tt>explicit</tt>. When the implicit type conversion is intentional and well behaved, the constructor can be used as an implicit conversion operator.</p><h3> <a name="Heading11">Blocking Undesirable Object Instantiation</a></h3><p>Sometimes it might be useful to disable programmers from instantiating an object of a certain class, for example, a class that is meant to be used only as a base class for others. A <tt>protected</tt> constructor blocks creation of class instances, yet it does so without disallowing derived objects' instantiation:</p><pre><tt>class CommonRoot</tt><tt>{</tt><tt>protected:</tt><tt> CommonRoot(){}//no objects of this class should be instantiated</tt><tt> virtual ~CommonRoot ();</tt><tt>};</tt><tt>class Derived: public CommonRoot</tt><tt>{</tt><tt>public:</tt><tt> Derived();</tt><tt>};</tt><tt>int main()</tt><tt>{</tt><tt> Derived d; // OK, constructor of d has access to </tt><tt> //any protected member in its base class</tt><tt> CommonRoot cr; //compilation error: attempt to </tt><tt> //access a protected member of CommonRoot</tt><tt>}</tt></pre><p>The same effect of blocking instantiation of a class can be achieved by declaring pure virtual functions. However, these add runtime and space overhead. When pure virtual functions aren't needed, you can use a <tt>protected</tt> constructor instead.</p><h3> <a name="Heading12">Using Member Initialization Lists</a></h3><p>A constructor might have a member initialization (mem-initialization for short) list that initializes the data members of the class. For example</p><pre><tt>class Cellphone //1: mem-init</tt><tt>{</tt><tt>private:</tt><tt> long number;</tt><tt> bool on;</tt><tt>public:</tt><tt> Cellphone (long n, bool ison) : number(n), on(ison) {}</tt><tt>};</tt></pre><p>The constructor of <tt>Cellphone</tt> can also be written as follows:</p><pre><tt>Cellphone (long n, bool ison) //2 initialization within constructor's body</tt><tt>{</tt><tt> number = n;</tt><tt> on = ison;</tt><tt>}</tt></pre><p>There is no substantial difference between the two forms in the case of <tt>Cellphone</tt>'s constructor. This is due to the way mem-initialization lists are processed by the compiler. The compiler scans the mem-initialization list and inserts the initialization code into the constructor's body before any user-written code. Thus, the constructor in the first example is expanded by the compiler into the constructor in the second example. Nonetheless, the choice between using a mem-initialization list and initialization inside the constructor's body is significant in the following four cases: </p><ul> <li> <p> Initialization of <tt>const</tt> members</p> </li> <p></p> <li> <p> Initialization of reference members</p> </li> <p></p> <li> <p> Passing arguments to a constructor of a base class or an embedded object</p> </li> <p></p> <li> <p> Initialization of member objects</p> </li></ul><p></p><p>In the first three cases, a mem-initialization list is mandatory; in the fourth case, it is optional. Consider the concrete examples that are discussed in the following paragraphs.'</p><h4> const Data Members</h4><p><tt>const</tt> data members of a class, including <tt>const</tt> members of a base or embedded subobject, must be initialized in a mem-initialization list.</p><pre><tt>class Allocator</tt><tt>{</tt><tt>private:</tt><tt> const int chunk_size;</tt><tt>public:</tt><tt> Allocator(int size) : chunk_size(size) {}</tt><tt>};</tt></pre><h4> Reference Data Members</h4><p>A reference data member must be initialized by a mem-initialization list.</p><pre><tt>class Phone;</tt><tt>class Modem</tt><tt>{</tt><tt>private:</tt><tt> Phone & line;</tt><tt>public:</tt><tt> Modem(Phone & ln) : line(ln) {}</tt><tt>};</tt></pre><h4> Invoking A Constructor Of A Base Or A Member Object With Arguments</h4><p>When a constructor has to pass arguments to the constructor of its base class or to the constructor of an embedded object, a mem-initializer must be used.</p><pre><tt>class base</tt><tt>{</tt><tt>private:</tt><tt> int num1;</tt><tt> char * text;</tt><tt>public:</tt><tt> base(int n1, char * t) {num1 = n1; text = t; } //no default constructor</tt><tt>};</tt><tt>class derived : public base</tt><tt>{</tt><tt>private:</tt><tt> char *buf;</tt><tt>public:</tt><tt> derived (int n, char * t) : base(n, t) //pass arguments to base constructor </tt><tt> { buf = (new char[100]);} </tt><tt>};</tt></pre><h4> Embedded Objects</h4><p>Consider the following example:</p><pre><tt>#include<string></tt><tt>using std::string;</tt><tt>class Website</tt><tt>{</tt><tt>private:</tt><tt> string URL</tt><tt> unsigned int IP</tt><tt>public:</tt><tt> Website()</tt><tt> {</tt><tt> URL = "";</tt><tt> IP = 0;</tt><tt> }</tt><tt>};</tt></pre><p>Class <tt>Website</tt> has an embedded object of type <tt>std::string</tt>. The syntax rules of the language do not force the usage of mem-initialization to initialize this member. However, the performance gain in choosing mem-initialization over initialization inside the constructor's body is significant. Why? The initialization inside the constructor's body is very inefficient because it requires the construction of the member <tt>URL</tt>; a temporary <tt>std::string</tt> object is then constructed from the value <tt>""</tt>, which is in turn assigned to <tt>URL</tt>. Finally, the temporary object has to be destroyed. The use of a mem-initialization list, on the other hand, avoids the creation and destruction of a temporary object (the performance implications of mem-initialization lists are discussed in further detail in Chapter 12, "Optimizing Your Code"). </p><h4> ""The Order Of A Mem-Initialization List Must Match The Order Of Class Member Declarations</h4><p>Due to the performance difference between the two forms of initializing embedded objects, some programmers use mem-initialization exclusively -- even for fundamental types. It is important to note, however, that the order of the initialization list has to match the order of declarations within the class. This is because the compiler transforms the list so that it coincides with the order of the declaration of the class members, regardless of the order specified by the programmer. For example</p><pre><tt>class Website</tt><tt>{</tt><tt>private:</tt><tt> string URL; //1</tt><tt> unsigned int IP; //2</tt><tt>public:</tt><tt> Website() : IP(0), URL("") {} // initialized in reverse order</tt><tt>};</tt></pre><p>In the mem-initialization list, the programmer first initializes the member <tt>IP</tt>, and then <tt>URL</tt>, even though <tt>IP</tt> is declared after <tt>URL</tt>. The compiler transforms the initialization list to the order of the member declarations within the class. In this case, the reverse order is harmless. When there are dependencies in the order of initialization list, however, this transformation can cause unexpected surprises. For example</p><pre><tt>class string</tt><tt>{</tt><tt>private:</tt><tt> char *buff;</tt><tt> int capacity;</tt><tt>public:</tt><tt> explicit string(int size) : </tt><tt> capacity(size), buff (new char [capacity]) {} undefined behavior</tt><tt>};</tt></pre><p>The mem-initialization list in the constructor of <tt>string</tt> does not follow the order of declaration of <tt>string</tt>'s members. Consequently, the compiler transforms the list into</p><pre><tt>explicit string(int size) : </tt><tt>buff (new char [capacity]), capacity(size) {} </tt></pre><p>The member <tt>capacity</tt> specifies the number of bytes that <tt>new</tt> has to allocate; but it has not been initialized. The results in this case are undefined. There are two ways to avert this pitfall: Change the order of member declarations so that <tt>capacity</tt> is declared before <tt>buff</tt>, or move the initialization of <tt>buff</tt> into the constructor's body.</p><h3> <a name="Heading13">The Exception Specification Of An Implicitly-Declared Default Constructor </a></h3><p>An implicitly-declared default constructor has an <i>exception specification</i> (exception specifications are discussed in Chapter 6, ""Exception Handling""). The exception specification of an implicitly-declared default constructor contains all the exceptions of every other special member function that the constructor invokes directly. For example</p><pre><tt>struct A </tt><tt>{</tt><tt> A(); //can throw any type of exception</tt><tt>};</tt><tt>struct B </tt><tt>{</tt><tt> B() throw(); //not allowed to throw any exceptions</tt><tt>};</tt><tt>struct C : public B</tt><tt>{</tt><tt> //implicitly-declared C::C() throw;</tt><tt>}</tt><tt>struct D: public A, public B </tt><tt>{</tt><tt> //implicitly-declared D::D(); </tt><tt>};</tt></pre><p>The implicitly-declared constructor in class <tt>C</tt> is not allowed to throw any exceptions because it directly invokes the constructor of class <tt>B</tt>, which is not allowed to throw any exceptions either. On the other hand, the implicitly-declared constructor in class <tt>D</tt> is allowed to throw any type of exception because it directly invokes the constructors of classes <tt>A</tt> and <tt>B</tt>. Since the constructor of class <tt>A</tt> is allowed to throw any type of exception, <tt>D</tt>'s implicitly-declared constructor has a matching exception specification. In other words, <tt>D</tt>'s implicitly-declared constructor allows all exceptions if any function that it directly invokes allows all exceptions; it allows no exceptions if every function that it directly invokes allows no exceptions either. As you will see soon, the same rules apply to the exception specifications of other implicitly-declared special member functions. </p><h2> <a name="Heading14">Copy Constructor</a></h2><p>A <i>copy constructor</i> is used to initialize its object with another object. A constructor of a class <tt>C</tt> is a copy constructor if its first argument is of type <tt>C&</tt>, <tt>const C&</tt>, <tt>volatile C&</tt>, or <tt>const volatile C&</tt>, and if there are no additional arguments or if all other arguments have default values. If there is no user-defined copy constructor for a class, the implementation implicitly declares one. An implicitly-declared copy constructor is an <tt>inline public</tt> member of its class, and it has the form</p><pre><tt>C::C(const C&);</tt></pre><p>if each base class of <tt>C</tt> has a copy constructor whose first argument is a reference to a <tt>const</tt> object of the base class type, and if all the nonstatic embedded objects in <tt>C</tt> also have a copy constructor that takes a reference to a <tt>const</tt> object of their type. Otherwise, the implicitly-declared copy constructor is of the following type:</p><pre><tt>C::C(C&);</tt></pre><p>An implicitly-declared copy constructor has an exception specification. The exception specification contains all the exceptions that might be thrown by other special functions that the copy constructor invokes directly.</p><h3> <a name="Heading15"> Implicitly-Defined Copy Constructors</a></h3>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -