⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ch12.htm

📁 c++语言操作手册
💻 HTM
📖 第 1 页 / 共 5 页
字号:
  rather than initialization:</p><pre><tt>void func2() //less efficient than func()? Not necessarily</tt><tt>{</tt><tt>  string s;</tt><tt>  bool emp;</tt><tt>  emp = s.empty(); //late assignment</tt><tt>}</tt></pre><p>My compiler produces the same assembly code as it did with the initialization   version. However, as far as user-defined types are concerned, the difference   between initialization and assignment can be quite noticeable. The following   example demonstrates the performance gain in this case (by modifying the preceding   example). Instead of a <tt>bool</tt> variable, a full-blown class object is   used, which has all the four special member functions defined:</p><pre><tt>int constructor, assignment_op, copy, destr; //global counters</tt><tt>class C</tt><tt>{</tt><tt>public:</tt><tt>  C();</tt><tt>  C&amp; operator = (const C&amp;);</tt><tt>  C(const C&amp;);</tt><tt>  ~C();</tt><tt>};</tt><tt>C::C() </tt><tt>{</tt><tt>  ++constructor;</tt><tt>}</tt><tt>C&amp; C::operator = (const C&amp; other)</tt><tt>{</tt><tt>  ++assignment_op;</tt><tt>  return *this;</tt><tt>}</tt><tt>C::C(const C&amp; other)</tt><tt>{</tt><tt>  ++copy;</tt><tt>}</tt><tt>C::~C()</tt><tt>{</tt><tt>  ++destr;</tt><tt>} </tt></pre><p>As in the previous example, two different versions of the same function are   compared; the first uses object initialization and the second uses assignment:</p><pre><tt>void assign(const C&amp; c1)</tt><tt>{</tt><tt> C c2;</tt><tt> c2 = c1;</tt><tt>}</tt><tt>void initialize(const C&amp; c1)</tt><tt>{</tt><tt> C c2 = c1;</tt><tt>}</tt></pre><p>Calling <tt>assign()</tt> causes three member function invocations: one for   the constructor, one for the assignment operator, and one for the destructor.   <tt>initialize()</tt> causes only two member function invocations: the copy   constructor and the destructor. Initialization saves one function call. For   a nonsensical class such as <tt>C</tt>, the additional runtime penalty that   results from a superfluous constructor call might not be crucial. However, bear   in mind that constructors of real-world objects also invoke constructors of   their base classes and embedded objects. When there is a choice between initialization   and assignment, therefore, initialization is always preferable.</p><h3> <a name="Heading6"> Relocating Declarations</a></h3><p>Preferring initialization of objects over assignment is one aspect of localizing   declarations. On some occasions, the performance boost that can result from   moving declarations is even more appreciable. Consider the following example:</p><pre><tt>bool is_C_Needed(); </tt><tt>void use()</tt><tt>{</tt><tt>  C c1;</tt><tt>  if (is_C_Needed() == false)</tt><tt>  {</tt><tt>    return; //c1 was not needed</tt><tt>  }   </tt><tt>  //use c1 here</tt><tt>  return; </tt><tt>}</tt></pre><p>The local object <tt>c1</tt> is unconditionally constructed and destroyed in   <tt>use()</tt>, even if it is not used at all. The compiler transforms the body   of <tt>use()</tt> into something that looks like this: </p><pre><tt>void use()</tt><tt>{</tt><tt>  C c1;</tt><tt>  c1.C::C(); //1. compiler-added constructor call</tt><tt>  if (is_C_Needed() == false)</tt><tt>  {</tt><tt>    c1.C::~C(); //2. compiler-added destructor call</tt><tt>    return; //c1 was not needed but was constructed and destroyed still</tt><tt>  }   </tt><tt>  //use c1 here</tt><tt>  c1.C::~C(); //3. compiler-added destructor call</tt><tt>  return; </tt><tt>}</tt></pre><p>As you can see, when <tt>is_C_Needed()</tt> returns <tt>false</tt>, the unnecessary   construction and destruction of <tt>c1</tt> are still unavoidable. Can a clever   compiler optimize away the unnecessary construction and destruction in this   case? The Standard allows the compiler to suppress the creation (and consequently,   the destruction) of an object if it is not needed, and if neither its constructor   nor its destructor has any side effects. In this example, however, the compiler   cannot perform this feat for two reasons. First, both the constructor and the   destructor of <tt>c1</tt> have side effects -- they increment counters. Second,   the result of <tt>is_C_Needed()</tt> is unknown at compile time; therefore,   there is no guarantee that <tt>c1</tt> is actually unnecessary at runtime. Nevertheless,   with a little help from the programmer, the unnecessary construction and destruction   can be eliminated. All that is required is the relocation of the declaration   of <tt>c1</tt> to the point where it is actually used:</p><pre><tt>void use()</tt><tt>{</tt><tt>  if (is_C_Needed() == false)</tt><tt>  {</tt><tt>    return; //c1 was not needed</tt><tt>  }   </tt><tt>  C c1; //moved from the block's beginning</tt><tt>  //use c1 here</tt><tt>  return; </tt><tt>}</tt></pre><p>Consequently, the object <tt>c1</tt> is constructed only when it is really   needed -- that is, when <tt>is_C_Needed()</tt> returns <tt>true</tt>. On the   other hand, if <tt>is_C_Needed()</tt> returns <tt>false</tt>, <tt>c1</tt> is   neither constructed nor destroyed. Thus, simply by moving the declaration of   <tt>c1</tt>, you managed to eliminate two unnecessary member function calls!   How does it work? The compiler transforms the body of <tt>use()</tt> into something   such as the following: </p><pre><tt>void use()</tt><tt>{</tt><tt>  if (is_C_Needed() == false)</tt><tt>  {</tt><tt>    return; //c1 was not needed</tt><tt>  }   </tt><tt>  C c1; //moved from the block's beginning</tt><tt>  c1.C::C(); //1 compiler-added constructor call</tt><tt>  //use c1 here</tt><tt>  c1.C::~C(); //2 compiler-added destructor call</tt><tt>  return; </tt><tt>}</tt></pre><p>To realize the effect of this optimization, change the body of <tt>use()</tt>.   Instead of constructing a single object, you now use an array of 1000 <tt>C</tt>   objects:</p><pre><tt>void use()</tt><tt>{</tt><tt>  if (is_C_Needed() == false)</tt><tt>  {</tt><tt>    return; //c1 was not needed</tt><tt>  }   </tt><tt>  C c1[1000]; </tt><tt>  //use c1 here</tt><tt>  return; </tt><tt>}</tt></pre><p>In addition, you define <tt>is_C_Needed()</tt> to return <tt>false</tt>: </p><pre><tt>bool is_C_Needed() </tt><tt>{ </tt><tt>  return false;</tt><tt>}</tt></pre><p>Finally, the <tt>main()</tt> driver looks similar to the following:</p><pre><tt>int main()</tt><tt>{ </tt><tt>  for (int j = 0; j&lt;100000; j++)</tt><tt>    use();</tt><tt>  return 0;  </tt><tt>}</tt></pre><p>The two versions of <tt>use()</tt> differ dramatically in their performance.   They were compared on a Pentium II, 233MHz machine. To corroborate the results,   the test was repeated five times. When the optimized version was used, the <tt>for</tt>   loop in <tt>main()</tt> took less than 0.02 of a second, on average. However,   when the same <tt>for</tt> loop was executed with the original, the nonoptimized   version of <tt>use()</tt> took 16 seconds. The dramatic variation in these results   isn't too surprising; after all, the nonoptimized version incurs 100,000,000   constructor calls as well as 100,000,000 destructor calls, whereas the optimized   version calls none. These results might also hint at the performance gain that   can be achieved simply by preallocating sufficient storage for container objects,   rather than allowing them to reallocate repeatedly (see also Chapter 10, "STL   and Generic Programming"). </p><h3> <a name="Heading7"> Member-Initialization Lists</a></h3><p>As you read in Chapter 4, "Special Member Functions: Default Constructor, Copy   Constructor, Destructor, and Assignment Operator," a member initialization list   is needed for the initialization of <tt>const</tt> and reference data members,   and for passing arguments to a constructor of a base or embedded subobject.   Otherwise, data members can either be assigned inside the constructor body or   initialized in a member initialization list. For example</p><pre><tt>class Date //mem-initialization version</tt><tt>{</tt><tt>private:</tt><tt>  int day;</tt><tt>  int month;</tt><tt>  int year;</tt><tt>  //constructor and destructor</tt><tt>public:</tt><tt>  Date(int d = 0, int m = 0, int y = 0) : day , month(m), year(y) {}</tt><tt>};</tt></pre><p>Alternatively, you can define the constructor as follows:</p><pre><tt>Date::Date(int d, int m, int y) //assignment within the constructor body</tt><tt>{</tt><tt>  day   = d; </tt><tt>  month = m; </tt><tt>  year  = y;</tt><tt>}</tt></pre><p>Is there a difference in terms of performance between the two constructors?   Not in this example. All the data members in <tt>Date</tt> are of a fundamental   type. Therefore, initializing them by a mem-initialization list is identical   in terms of performance to assignment within the constructor body. However,   with user-defined types, the difference between the two forms is significant.   To demonstrate that, return to the member function counting class, <tt>C</tt>,   and define another class that contains two instances thereof:</p><pre><tt>class Person</tt><tt>{</tt><tt>private:</tt><tt>  C c_1;</tt><tt>  C c_2;</tt><tt>public:</tt><tt>  Person(const C&amp; c1, const C&amp; c2 ): c_1(c1), c_2(c2) {}</tt><tt>};</tt></pre><p>An alternative version of <tt>Person</tt>'s constructor looks similar to the   following:</p><pre><tt>Person::Person(const C&amp; c1, const C&amp; c2)</tt><tt>{</tt><tt> c_1 = c1; </tt><tt> c_2 = c2; </tt><tt>}</tt></pre><p>Finally, the <tt>main()</tt> driver is defined as follows:</p><pre><tt>int main()</tt><tt>{</tt><tt>  C c; //created only once, used as dummy arguments in Person's constructor </tt><tt>  for (int j = 0; j&lt;30000000; j++)</tt>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -