📄 index.html
字号:
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<string></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 <stdexcept></tt>
<tt>#include <iostream></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& ex) //handle exception thrown from f()</tt>
<tt> {</tt>
<tt> cout<<ex.what()<<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&</tt> (defined in the header <tt><new></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 <new></tt>
<tt>void* operator new(std::size_t size, const std::nothrow_t&) throw();</tt>
<tt>void* operator new[](std::size_t size, const std::nothrow_t&) 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 <new></tt>
<tt>#include <string></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><new></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 <new></tt>
<tt>#include <iostream></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<< *p << p2[0] << 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 <new></tt>
<tt>#include <iostream></tt>
<tt>using namespace std;</tt>
<tt>class C</tt>
<tt>{</tt>
<tt>public:</tt>
<tt> C() { cout<< "constructed" <<endl; }</tt>
<tt> ~C(){ cout<< "destroyed" <<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->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 <new></tt>
<tt>using namespace std;</tt>
<tt>class C{/*...*/};</tt>
<tt>void __new() throw (bad_alloc)</tt>
<tt>{</tt>
<tt> C * p = reinterpret_cast<C*> (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 <new></tt>
<tt>#include <iostream></tt>
<tt>#include <string></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->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 (&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 <cstring></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 + -