Clause 16: The new and delete pairs must take the same form.
Use the same form in corresponding uses ofnew and delete
When you use delete For a pointer, the only way to let delete know whether there is an "array size record" in the memory is to tell it. If square brackets are added to the delete statement, delete determines that the pointer points to an array. Otherwise, point to a single object.
Std: string * stringPtr1 = new std: string;
Std: string * stringPtr2 = newstd: string [100];
...
Delete stringPtr1;
Delete [] stringPtr2;
The use of the delete [] Form for stringPtr1 and the absence of the delete [] Form for stringPtr2 will lead to unpleasant undefined behavior.
The rule is simple: if you use [] in the new expression, you must also use [] in the corresponding delete expression. If you do not use [] in the new expression, do not use [] in the matched delete expression.
This rule is especially important when the class you write contains a pointer to the dynamically allocated memory and multiple constructors are provided, at that time, you must be careful to use the same form of new in all constructors to initialize that pointer member. Otherwise, how do you know which form of delete should be used in the Destructor?
This rule is also worth noting for those who prefer typedef, because it means that the author of typedef must make it clear which form of delete should be used to create a typedef type object with new. For example, consider the following typedef:
Typedef std: string AddressLines [4]; // each person's address has four rows, each row is a string
Std: string * pal = new AddressLines;
// Note that "new AddressLines" returns a string *, just like "new string [4 ]".
Delete pal; // action not defined!
Delete [] pal; // fine
We recommend that you do not perform typedef actions on arrays. This is easy to achieve, because the C ++ standard library contains string and vector, and those templates reduce the need for dynamically allocated arrays to almost zero. For example, AddressLines can be defined as a string vector, that is, the type is vector <string>.
· If you use [] in the new expression, you must use [] in the corresponding delete expression. If [] is not used in the new expression, you do not need to use [] in the corresponding delete expression.
Article 17: Insert a newed object into a smart pointer using an independent statement
Store newed objects in smart pointers instandalone statements
Suppose we have a function to reveal the priority of the processing program, and another function is used to perform some priority processing on a dynamically assigned Widget:
Int priority ();
Void processWidget (std: tr1: shared_ptr <Widget> pw, int priority );
Do not forget to use the best quotes for object management resources. processWidget uses a smart pointer (tr1: shared_ptr) for widgets that process dynamic distribution ). Now consider a call to processWidget:
ProcessWidget (new Widget, priority ());
The above calls cannot be compiled. Tr1: The shared_ptr constructor requires an original pointer. However, this constructor is an explicit constructor and cannot be implicitly converted, therefore, you cannot implicitly convert the original pointer returned by the new Widget to the tr1: shared_ptr required by processWidget. If it is written in this way, you can compile it:
ProcessWidget (std: tr1: shared_ptr <Widget> (new Widget), priority ());
Surprisingly, although we use object to manage resources, this call may leak resources.
Before the compiler can generate a call to processWidget, it must first calculate the real parameters to be passed. Before calling processWidget, the compiler must generate code for these three tasks:
· Call priority.
· Execute "new Widget ".
· Call the constructor tr1: shared_ptr.
The C ++ compiler allows you to determine the order in which these three tasks are completed within a very large range (this is different from the processing methods of Java, C #, and other languages, in those languages, function parameters are always calculated in a specific order ). It can be determined that "new Widget" must be executed before the tr1: shared_ptr constructor is called, because the result of this expression must be passed to the constructor of tr1: shared_ptr as a parameter, however, priority calls can be executed in the second or third place. If the compiler chooses the second one to execute it (maybe it can generate more efficient code), we finally get the operation order:
1. Execute "new Widget ".
2. Call priority.
3. Call the constructor tr1: shared_ptr.
What will happen if an exception is thrown when calling priority? In this case, the pointer returned from "new Widget" is lost because it is not saved to the tr1: shared_ptr that we expect to prevent resource leakage. Because an exception may insert a timestamp between "resource created (via newWidget)" and "resource converted to resource management object", calling processWidget may cause a leak.
The method to avoid such problems is simple: Use the separation statement to write (1) create a Widget, (2) place it in a smart pointer, and then pass the smart pointer to processWidget:
Std: tr1: shared_ptr <Widget> pw (newWidget );
// Store the newed object with a smart pointer in a separate statement
ProcessWidget (pw, priority (); // This call action will never cause leakage
This works because the compiler does not have the freedom to re-arrange operations that span statements (only within the statement can have that Degree of Freedom ). The "new Widget" expression and the tr1: shared_ptr constructor call these two actions, which are different from the priority call in the statement, so the compiler cannot select any execution order between them.
· Store new objects into smart pointers in independent statements. If you neglect this, an exception may cause imperceptible resource leaks.
From pandawuwyj's column