📄 ch12.htm
字号:
<tt> {</tt><tt> Person p(c, c);</tt><tt> }</tt><tt> return 0; </tt><tt>}</tt></pre><p>The two versions were compared on a Pentium II, 233MHz machine. To corroborate the results, the test was repeated five times. When a member initialization list was used, the <tt>for</tt> loop in <tt>main()</tt> took 12 seconds, on average. The nonoptimized version took 15 seconds, on average. In other words, the assignment inside the constructor body is slower by a factor of 25% compared to the member-initialized constructor. The member function counters can give you a clue as to the reasons for the difference. Table 12.1 presents the number of member function calls of class <tt>C</tt> for the member initialized constructor and for the assignment inside the constructor's body.</p><h4> Table 12.1 Comparison Between Member Initialization and Assignment Within the Constructor's Body for Class Person </h4><table border> <tr valign="TOP" align="left"> <td colspan=1 align="left"> <p><b>Initialization Method</b></p> </td> <td colspan=1 align="left"> <p><b>Default Constructor Calls</b></p> </td> <td colspan=1 align="left"> <p><b>Assignment Operator Calls</b></p> </td> <td colspan=1 align="left"> <p><b>Copy Constructor Calls</b></p> </td> <td colspan=1 align="left"> <p><b>Destructor Calls</b></p> </td> </tr> <tr valign="TOP" align="left"> <td colspan=1 align="left"> <p>Member initialization list</p> </td> <td colspan=1 align="left"> <p>0</p> </td> <td colspan=1 align="left"> <p>0</p> </td> <td colspan=1 align="left"> <p>60,000,000</p> </td> <td colspan=1 align="left"> <p>60,000,000</p> </td> </tr> <tr valign="TOP" align="left"> <td colspan=1 align="left"> <p>Assignment within Constructor</p> </td> <td colspan=1 align="left"> <p>60,000,000</p> </td> <td colspan=1 align="left"> <p>60,000,000</p> </td> <td colspan=1 align="left"> <p>0</p> </td> <td colspan=1 align="left"> <p>60,000,000</p> </td> </tr></table><p>When a member initialization list is used, only the copy constructor and the destructor of the embedded object are called (note that <tt>Person</tt> has two embedded members), whereas the assignment within the constructor body also adds a default constructor call per embedded object. In Chapter 4, you learned how the compiler inserts additional code into the constructor's body before any user-written code. The additional code invokes the constructors of the base classes and embedded objects of the class. In the case of polymorphic classes, this code also initializes the <tt>vptr</tt>. The assigning constructor of class <tt>Person</tt> is transformed into something such as the following:</p><pre><tt>Person::Person(const C& c1, const C& c2) //assignment within constructor body</tt><tt>{</tt><tt> //pseudo C++ code inserted by the compiler before user-written code</tt><tt> c_1.C::C(); //invoke default constructor of embedded object c_1</tt><tt> c_2.C::C(); //invoke default constructor of embedded object c_2</tt><tt>//user-written code comes here:</tt><tt> c_1 = c1; </tt><tt> c_2 = c2; </tt><tt>}</tt></pre><p>The default construction of the embedded objects is unnecessary because they are reassigned new values immediately afterward. The member initialization list, on the other hand, appears before any user-written code in the constructor. Because the constructor body does not contain any user-written code in this case, the transformed constructor looks similar to the following:</p><pre><tt>Person::Person(const C& c1, const C& c2) // member initialization list ctor</tt><tt>{</tt><tt> //pseudo C++ code inserted by the compiler before user-written code</tt><tt> c_1.C::C(c1); //invoke copy constructor of embedded object c_1</tt><tt> c_2.C::C(c2); //invoke copy constructor of embedded object c_2</tt><tt>//user-written code comes here (note: there's no user code)</tt><tt>}</tt></pre><p>You can conclude from this example that for a class that has subobjects, a member initialization list is preferable to an assignment within the constructor's body. For this reason, many programmers use member initialization lists across the board, even for data members of fundamental types.</p><h3> <a name="Heading8"> Prefix Versus Postfix Operators </a></h3><p>The prefix operators <tt>++</tt> and <tt>--</tt> tend to be more efficient than their postfix versions because when postfix operators are used, a temporary copy is needed to retain the value of the operand before it is changed. For fundamental types, the compiler can eliminate the extra copy. However, for user-defined types, this is nearly impossible. A typical implementation of the overloaded prefix and postfix operators demonstrates the difference between the two: </p><pre><tt>class Date</tt><tt>{</tt><tt>private:</tt><tt> //...</tt><tt> int AddDays(int d); </tt><tt>public:</tt><tt> Date operator++(int unused); </tt><tt> Date& operator++(); </tt><tt>};</tt><tt>Date Date::operator++(int unused) //postfix</tt><tt>{</tt><tt> Date temp(*this); //create a copy of the current object</tt><tt> this->AddDays(1); //increment current object</tt><tt> return temp; //return by value a copy of the object before it was incremented</tt><tt>}</tt><tt>Date& Date::operator++() //prefix</tt><tt>{ </tt><tt> this->AddDays(1); //increment current object</tt><tt> return *this; //return by reference the current object</tt><tt>}</tt></pre><p>The overloaded postfix <tt>++</tt> is significantly less efficient than the prefix for two reasons: It requires the creation of a temporary copy, and it returns that copy by value. Therefore, whenever you are free to choose between postfix and prefix operators of an object, choose the prefix version.</p><h2> <a name="Heading9"> Inline Functions</a></h2><p>Inline functions can eliminate the overhead incurred by a function call and still provide the advantages of ordinary functions. However, inlining is not a panacea. In some situations, it can even degrade the program's performance. It is important to use this feature judiciously.</p><h3> <a name="Heading10"> Function Call Overhead</a></h3><p>The exact cost of an ordinary function call is implementation-dependent. It usually involves storing the current stack state, pushing the arguments of the function onto the stack and initializing them, and jumping to the memory address that contains the function's instructions -- only then does the function begin to execute. When the function returns, a sequence of reverse operations also takes place. In other languages (such as Pascal and COBOL), the overhead of a function call is even more noticeable because there are additional operations that the implementation performs before and after a function call. For a member function that merely returns the value of a data member, this overhead can be unacceptable.<b> </b>Inline functions were added to C++ to allow efficient implementation of such accessor and mutator member functions (<i>getters</i> and <i>setters</i>, respectively). Nonmember functions can also be declared <tt>inline</tt>. </p><h3> <a name="Heading11"> Benefits of Inline Functions</a></h3><p>The benefits of inlining a function are significant: From a user's point of view, the inlined function looks like an ordinary function. It can have arguments and a return value; furthermore, it has its own scope, yet it does not incur the overhead of a full-blown function call. In addition, it is remarkably safer and easier to debug than using a macro. But there are even more benefits. When the body of a function is inlined, the compiler can optimize the resultant code even further by applying context-specific optimizations that it cannot perform on the function's code alone. </p><p>All member functions that are implemented inside the class body are implicitly declared <tt>inline</tt>. In addition, compiler synthesized constructors, copy constructors, assignment operators, and destructors are implicitly declared <tt>inline</tt>. For example</p><pre><tt>class A </tt><tt>{</tt><tt>private:</tt><tt> int a;</tt><tt>public:</tt><tt> int Get_a() { return a; } // implicitly inline</tt><tt> virtual void Set_a(int aa) { a = aa; } //implicitly inline</tt><tt> //compiler synthesized canonical member functions also declared inline</tt><tt>};</tt></pre><p>It is important to realize, however, that the <tt>inline</tt> specifier is merely a recommendation to the compiler. The compiler is free to ignore this recommendation and <i>outline</i> the function; it can also inline a function that was not explicitly declared <tt>inline</tt>. Fortunately, C++ guarantees that the function's semantics cannot be altered by the compiler just because it is or is not inlined. For example, it is possible to take the address of a function that was not declared <tt>inline</tt>, regardless of whether it was inlined by the compiler (the result, however, is the creation of an outline copy of the function). How do compilers determine which functions are to be inlined and which are not? They have proprietary heuristics that are designed to pick the best candidates for inlining, depending on various criteria. These criteria include the size of the function body, whether it declares local variables, its complexity (for example, recursion and loops usually disqualify a function from inlining), and additional implementation- and context-dependent factors.</p><h3> <a name="Heading12"> What Happens When a Function that Is Declared inline Cannot Be Inlined?</a></h3><p>Theoretically, when the compiler refuses to inline a function, that function is then treated like an ordinary function: The compiler generates the object code for it, and invocations of the function are transformed into a jump to its memory address. Unfortunately, the implications of outlining a function are more complicated than that. It is a common practice to define inline functions in the class declaration. For example</p><pre><tt> // filename Time.h</tt><tt>#include<ctime></tt><tt>#include<iostream></tt><tt>using namespace std;</tt><tt>class Time</tt><tt>{</tt><tt>public:</tt><tt> inline void Show() { for (int i = 0; i<10; i++) cout<<time(0)<<endl;}</tt><tt>};</tt><tt> // filename Time.h</tt></pre><p>Because the member function <tt>Time::Show()</tt> contains a local variable and a <tt>for</tt> loop, the compiler is likely to ignore the <tt>inline</tt> request and treat it as an ordinary member function. However, the class declaration itself can be <tt>#included</tt> in separately compiled translation units:</p><pre><tt> // filename f1.cpp</tt><tt>#include "Time.hj"</tt><tt>void f1()</tt><tt>{</tt><tt> Time t1;</tt><tt> t1.Show();</tt><tt>}</tt><tt> // f1.cpp</tt><tt>// filename f2.cpp</tt><tt>#include "Time.h"</tt><tt>void f2()</tt><tt>{</tt><tt> Time t2;</tt><tt> t2.Show();</tt><tt>}</tt><tt> // f2.cpp</tt></pre><p>As a result, the compiler generates two identical copies of the same member function for the same program: </p><pre><tt>void f1();</tt><tt>void f2();</tt><tt>int main()</tt><tt>{</tt><tt> f1(); </tt><tt> f2();</tt><tt> return 0;</tt>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -