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

📄 ch11.htm

📁 c++语言操作手册
💻 HTM
📖 第 1 页 / 共 4 页
字号:
  plain <tt>delete</tt> instead of <tt>delete[]</tt> to release an array of objects   allocated by <tt>new[]</tt> results in undefined behavior and should never happen.   Similarly, using <tt>delete[]</tt> to release a single object that was allocated   by plain <tt>new</tt> is also disastrous: It might cause memory leaks, heap   corruption, or a program crash.</p><p>Contrary to popular belief, the same rules apply to arrays of fundamental types   -- not just to arrays of objects. Although <tt>delete[]</tt> does not invoke   destructors in the case of fundamental types, it still has to retrieve the number   of elements in the array to calculate the complete size of the memory block.   For example</p><pre><tt>#include&lt;string&gt;</tt><tt>void f()</tt><tt>{</tt><tt>  char *pc = new char[100];</tt><tt>  string *ps = new std::string[100];</tt><tt>  //...use pc and ps</tt><tt>  delete[] pc; //no destructors invoked, still delete[] is required</tt><tt>                   // for arrays allocated by new[]</tt><tt>  delete[] ps //ensures each member's destructor is called</tt><tt>}</tt></pre><h3> <a name="Heading18"> Exceptions and Operator new</a></h3><p>In pre-Standard C++, <tt>new</tt> returned a <tt>NULL</tt> pointer when it   failed to allocate the requested amount of memory. In this respect, <tt>new</tt>   behaved like <tt>malloc()</tt> in C. Programmers had to check the value that   was returned from <tt>new</tt> before they used it to make sure that it was   not <tt>NULL</tt>. For example</p><pre><tt>void f(int size) //anachronistic usage of new</tt><tt>{</tt><tt>  char *p = new char [size];</tt><tt>  if (p == 0)   //this was fine until 1994</tt><tt>  {</tt><tt>    //...use p safely</tt><tt>    delete [] p;</tt><tt>  }</tt><tt>  return;</tt><tt>}</tt><tt>const int BUF_SIZE = 1048576L;</tt><tt>int main()</tt><tt>{</tt><tt>  f(BUF_SIZE);</tt><tt>  return 0;</tt><tt>}</tt></pre><p>Returning a <tt>NULL</tt> pointer upon failure, however, was problematic.<i>   </i>(Note that the <tt>NULL</tt> pointer policy was applicable to both plain   <tt>new</tt> and <tt>new[]</tt>. Similarly, the modified behavior applies to   <tt>new</tt> as well as <tt>new[]</tt>.) It forced programmers to test the value   that was returned by every invocation of operator <tt>new</tt>, which is a tedious   and error-prone process. In addition, the recurrent testing of the returned   pointer can increase the size of the programs and add a runtime performance   overhead (you might recall that these are the drawbacks associated with the   return value policy, discussed in Chapter 6, "Exception Handling"). Failures   in dynamic memory allocation are rather rare and generally indicate an unstable   system state. This is exactly the kind of runtime errors that exception handling   was designed to cope with. For these reasons, the C++ standardization committee   changed the specification of <tt>new</tt> a few years ago. The Standard now   states that operator <tt>new</tt> throws an exception of type <tt>std::bad_alloc</tt>   when it fails, rather than returning a <tt>NULL</tt> pointer.</p><blockquote>  <hr>  <strong>CAUTION: </strong> Although compiler vendors have been sluggish in adopting   this change, most C++ compilers now conform to the standard in this respect,   and throw an exception of type <tt>std::bad_alloc</tt> when <tt>new</tt> fails.   Please consult your compiler's documentation for more details.   <hr></blockquote><p>A program that calls <tt>new</tt> either directly or indirectly (for example,   if it uses STL containers, which allocate memory from the free store) must contain   an appropriate handler that catches a <tt>std::bad_alloc</tt> exception. Otherwise,   whenever <tt>new</tt> fails, the program terminates due to an uncaught exception.   The exception-throwing policy also implies that testing the pointer that is   returned from <tt>new</tt> is completely useless. If <tt>new</tt> is successful,   the redundant test wastes system resources. On the other hand, in the case of   an allocation failure, the thrown exception aborts the current thread of execution   from where it was thrown -- so the test is not executed anyway. The revised,   standard-conforming form of the previously presented program looks similar to   the following:</p><pre><tt>void f(int size) //standard-conforming usage of new</tt><tt>{</tt><tt>  char *p = new char [size];</tt><tt>  //...use p safely</tt><tt>  delete [] p;</tt><tt>  return;</tt><tt>}</tt><tt>#include &lt;stdexcept&gt;</tt><tt>#include &lt;iostream&gt;</tt><tt>using namespace std;</tt><tt>const int BUF_SIZE = 1048576L;</tt><tt>int main()</tt><tt>{</tt><tt>  try</tt><tt>  {</tt><tt>    f(BUF_SIZE);</tt><tt>  }</tt><tt>  catch(bad_alloc&amp; ex)  //handle exception thrown from f()</tt><tt>  {</tt><tt>    cout&lt;&lt;ex.what()&lt;&lt;endl;</tt><tt>    //...other diagnostics and remedies</tt><tt>  }</tt><tt>  return -1;</tt><tt>}</tt></pre><h3> <a name="Heading19"> Exception-Free Version of Operator new</a></h3><p>Still, under some circumstances, throwing an exception is undesirable. For   example, exception handling might have been turned off to enhance performance;   on some platforms, it might not be supported at all.</p><p>The Standardization committee was aware of this and added an exception-free   version of <tt>new</tt> to the Standard. The exception-free version of <tt>new</tt>   returns a <tt>NULL</tt> pointer in the event of a failure, rather than throwing   a <tt>std::bad_alloc</tt> exception. This version of <tt>new</tt> takes an additional   argument of type <tt>const std::nothrow_t&amp;</tt> (defined in the header <tt>&lt;new&gt;</tt>).   It comes in two flavors, one for plain <tt>new</tt> and another for <tt>new[]</tt>.</p><pre><tt>//exception-free versions of new and new[] defined in the header &lt;new&gt;</tt><tt>void* operator new(std::size_t size, const std::nothrow_t&amp;) throw();</tt><tt>void* operator new[](std::size_t size, const std::nothrow_t&amp;) throw();</tt></pre><p>The exception-free <tt>new</tt> is also called <i>nothrow new</i>. It is used   as follows:</p><pre><tt>#include &lt;new&gt;</tt><tt>#include &lt;string&gt;</tt><tt>using namespace std;</tt><tt>void f(int size) // demonstrating nothrow new</tt><tt>{</tt><tt>  char *p = new (nothrow) char [size]; //array nothrow new</tt><tt>  if (p == 0)</tt><tt>  {</tt><tt>    //...use p</tt><tt>    delete [] p;</tt><tt>  }</tt><tt>  string *pstr = new (nothrow) string; //plain nothrow new</tt><tt>  if (pstr == 0)</tt><tt>  {</tt><tt>    //...use pstr</tt><tt>    delete [] pstr;</tt><tt>  }</tt><tt>  return;</tt><tt>}</tt><tt>const int BUF_SIZE = 1048576L;</tt><tt>int main()</tt><tt>{</tt><tt>  f(BUF_SIZE);</tt><tt>  return 0;</tt><tt>}</tt></pre><p>The argument <tt>nothrow</tt> is defined and created in header <tt>&lt;new&gt;</tt>   as follows:</p><pre><tt>extern const nothrow_t nothrow;</tt></pre><p>Class <tt>nothrow_t</tt> is defined as follows:</p><pre><tt>struct nothrow_t {}; //an empty class</tt></pre><p>In other words, the type <tt>nothrow_t</tt> is an empty class (the empty class   idiom is discussed in Chapter 5, "Object-Oriented Program and Design") whose   sole purpose is to overload global <tt>new</tt>.</p><h3> <a name="Heading20"> Placement new</a></h3><p>An additional version of operator <tt>new</tt> enables you to construct an   object (or an array of objects) at a predetermined memory position. This version   is called <i>placement new</i> and has many useful applications, including building   a custom-made memory pool or a garbage collector. Additionally, it can be used   in mission-critical applications because there is no danger of allocation failure   (the memory that is used by placement <tt>new</tt> has already been allocated).   Placement <tt>new</tt> is also faster because the construction of an object   on a preallocated buffer takes less time. Following is an example of using placement   <tt>new</tt>:</p><pre><tt>#include &lt;new&gt;</tt><tt>#include &lt;iostream&gt;</tt><tt>using namespace std;</tt><tt>void placement()</tt><tt>{</tt><tt>  int   *pi = new int;     //plain new</tt><tt>  float *pf = new float[2]; //new []</tt><tt>  int   *p  = new (pi) int (5);  //placement new</tt><tt>  float *p2 = new (pf) float;  //placement new[]</tt><tt>  p2[0] = 0.33f;</tt><tt>  cout&lt;&lt; *p &lt;&lt; p2[0] &lt;&lt; endl;</tt><tt>  //...</tt><tt>  delete pi;</tt><tt>  delete [] pf;</tt><tt>}</tt></pre><h4> Explicit Destructor Invocation Is Required for an Object Created by Placement   new</h4><p>Destructors of objects that were constructed using placement <tt>new</tt> have   to be invoked explicitly. To see why, consider the following example:</p><pre><tt>#include &lt;new&gt;</tt><tt>#include &lt;iostream&gt;</tt><tt>using namespace std;</tt><tt>class C</tt><tt>{</tt><tt>public:</tt><tt>  C() { cout&lt;&lt; "constructed" &lt;&lt;endl; }</tt><tt>  ~C(){ cout&lt;&lt; "destroyed" &lt;&lt;endl; }</tt><tt>};</tt><tt>int main()</tt><tt>{</tt><tt>  char * p = new char [sizeof ];  // pre-allocate a buffer</tt><tt>   C *pc = new (p) C;  // placement new</tt><tt>   //... used pc</tt><tt>  pc-&gt;C::~C(); // 1:explicit destructor invocation is required</tt><tt>  delete [] p; //2</tt><tt>  return 0;</tt><tt>}</tt></pre><p>Without an explicit destructor invocation in (1), the object that is pointed   to by <tt>p</tt> will never be destroyed, but the memory block on which it was   created will be released by the <tt>delete[]</tt> statement in (2).</p><h2> <a name="Heading21"> Exceptions During Object Construction</a></h2><p>As was previously noted, <tt>new</tt> performs two operations: It allocates   memory from the free store by calling an allocation function, and it constructs   an object on the allocated memory. The question is, does the allocated memory   leak when an exception is thrown during the construction process? The answer   is no, it doesn't. The allocated memory is returned to the free store by the   system before the exception propagates to the program. Thus, an invocation of   operator <tt>new</tt> can be construed as two consecutive operations. The first   operation merely allocates a sufficient memory block from the free store with   the appropriate alignment requirements. In the event of failure, the system   throws an exception of type <tt>std::bad_alloc</tt>. If the first operation   was successful, the second one begins. The second operation consists of invoking   the object's constructor with the pointer that is retained from the previous   step. Put differently, the statement</p><pre><tt>C* p = new C;</tt></pre><p>is transformed by the compiler into something similar to the following:</p><pre><tt>#include &lt;new&gt;</tt><tt>using namespace std;</tt><tt>class C{/*...*/};</tt><tt>void __new() throw (bad_alloc)</tt><tt>{</tt><tt>  C * p = reinterpret_cast&lt;C*&gt; (new char [sizeof ]); //step 1: allocate</tt><tt>                                                       // raw memory</tt><tt>  try</tt><tt>  {</tt><tt>    new (p) C;  //step 2: construct the objects on previously allocated buffer</tt><tt>  }</tt><tt>  catch(...)  //catch any exception thrown from C's constructor</tt><tt>  {</tt><tt>    delete[] p;  //free the allocated buffer</tt><tt>    throw;  //re-throw the exception of C's constructor</tt><tt>  }</tt><tt>}</tt></pre><h2> <a name="Heading22"> Alignment Considerations</a></h2><p>The pointer that is returned by <tt>new</tt> has the suitable alignment properties   so that it can be converted to a pointer of any object type and then used to   access that object or array. Consequently, you are permitted to allocate character   arrays into which objects of other types will later be placed. For example</p><pre><tt>#include &lt;new&gt;</tt><tt>#include &lt;iostream&gt;</tt><tt>#include &lt;string&gt;</tt><tt>using namespace std;</tt><tt>class Employee</tt><tt>{</tt><tt>private:</tt><tt>  string name;</tt><tt>  int age;</tt><tt>public:</tt><tt>  Employee();</tt><tt>  ~Employee();</tt><tt>};</tt><tt>void func() //use a pre allocated char array to construct</tt><tt>            //an object of a different type</tt><tt>{</tt><tt>  char * pc = new char[sizeof(Employee)];</tt><tt>  Employee *pemp = new (pc) Employee;  //construct on char array</tt><tt>  //...use pemp</tt><tt>  pemp-&gt;Employee::~Employee(); //explicit destruction</tt><tt>  delete [] pc;</tt><tt>}</tt></pre><p>It might be tempting to use a buffer that is allocated on the stack to avoid   the hassle of deleting it later:</p><pre><tt>char pbuff [sizeof(Employee)];</tt><tt>Employee *p = new (pbuff ) Employee;  //undefined behavior</tt></pre><p>However, <tt>char</tt> arrays of automatic storage type are not guaranteed   to meet the necessary alignment requirements of objects of other types. Therefore,   constructing an object of a preallocated buffer of automatic storage type can   result in undefined behavior. Furthermore, creating a new object at a storage   location that was previously occupied by a <tt>const</tt> object with static   or automatic storage type also results in undefined behavior. For example</p><pre><tt>const Employee emp;</tt><tt>void bad_placement() //attempting to construct a new object</tt><tt>                     //at the storage location of a const object</tt><tt>{</tt><tt>  emp.Employee::~Employee();</tt><tt>  new (&amp;emp) const Employee; //  undefined behavior</tt><tt>}</tt></pre><h3> <a name="Heading23"> Member Alignment</a></h3><p>The size of a class or a struct might be larger than the result of adding the   size of each data member in it. This is because the compiler is allowed to add   additional <i>padding</i> <i>bytes</i> between members whose size does not fit   exactly into a machine word (see also Chapter 13). For example</p><pre><tt>#include &lt;cstring&gt;</tt><tt>using namespace std;</tt><tt>struct Person</tt><tt>{</tt><tt>  char firstName[5];</tt>

⌨️ 快捷键说明

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