Abnormal processing in C and C ++ 7

zhaozj2021-02-16  40

Partial constructor and placement delete

Discuss part constructs in general, dynamically generate objects, and to solve part of the construction problem with Placement delete.

C standard requirements Standard Running Boot File Provides several Operator delete overload form. In these overload form, Visual C 6 is missing:

l void Operator Delete (void *, void *)

And Visual C 5 is missing:

l void Operator Delete (void *, void *)

l void Operator Delete (void *, std :: nothrow_t const)

These overload forms support the Placement delete expression and solve a special problem: the object is released. At this time and next, I will give some constructs in general, dynamically generate some of the objects, and use Placement Delete to solve examples of partial construction problems.

1.1 part of the structure

Look at this example:

// EXAMPLE 1

#include

Class A

{

PUBLIC:

A ()

{

Throw 0;

}

}

int main ()

{

Try

{

A a a;

}

Catch (...)

{

Std :: cout << "caught exception" << std :: end1;

}

Return 0;

}

Because the constructor of A throws an exception, the A object is not fully constructed. In this example, there is no constructor having a visible role: because a no sub-object, the constructor does not actually do anything. But consider this change:

// EXAMPLE 2

#include

Class B

{

PUBLIC:

B ()

{

Throw 0;

}

}

Class A

{

Private:

B const b;

}

// ... Main Same as Before ...

Now, a constructor is not active because it constructs a B member object, and it will throw it in it. What reacts made this exception?

From the C standard, four (slightly simplified) principles:

l An object is completely constructed, and it is not only possible when it is fully executed, and its destructor has not started.

l If an object contains a sub-object, the constructor of the inclusive object is only implemented after all sub-objects are completely constructed.

l An object is destructured, and it is only a complete configuration.

The object is destructed in the reverse sequence they are constructed.

Because I throw an exception, B :: b is not fully executed. Therefore, the object A :: B of B is neither a fully constructed and has not been destructed.

To prove this, track the corresponding class members:

// EXAMPLE 3

#include

Class B

{

PUBLIC:

B ()

{

Std :: cout << "b :: b enter" << std :: endl;

Throw 0;

Std :: cout << "b :: b exit" << std :: endl;

}

~ B ()

{

Std :: cout << "b :: ~ b" << std :: endl;

}

Class A

{

PUBLIC:

A ()

{

Std :: cout << "A :: A" << std :: endl;

}

~ A ()

{

Std :: cout << "A :: ~ a" << std :: endl;

}

Private:

B const b;

}

// ... Main Same as Before ...

When running, the program will only output

B :: b Enter

Caught Exception

This shows that objects A and B have neither fully constructed.

1.2 multi-object

Make an example be more interesting and more descriptive, reform it to the allowable part (not all) objects are completely constructed:

// EXAMPLE 4

#include

Class B

{

PUBLIC:

B (int const id): ID_ (id)

{

Std :: cout << id_ << "b :: b enter" << std :: end1;

IF (id_> 2)

Throw 0;

Std :: cout << id_ << "b :: b exit" << std :: end1;

}

~ B ()

{

Std :: cout << id_ << "b :: ~ b" << std :: end1;

}

Private:

INT const id_;

}

Class A

{

PUBLIC:

A (): B1 (1), B2 (2), B3 (3)

{

Std :: cout << "A :: A" << std :: endl;

}

~ A ()

{

Std :: cout << "A :: ~ a" << std :: endl;

}

Private:

B Const B1;

B const b2;

B const b3;

}

// ... Main Same Asbefore ...

Note that the constructor of B is now accepting a parameter of an object ID value. Use it the unique marker of the object of B and determines if the object is completely constructed. Most tracking information starts with these IDs:

1 b :: b Enter

1 b :: b exit

2 b :: b enter

2 b :: b exit

3 b :: b Enter

2 b :: ~ b

1 b :: ~ b

Caught Exception

B1 and B2 are completely constructed and B3 is not. Therefore, B1 and B2 are destructed and B3 is not. Further, the sequence of B1 and B2 is carried out in the reverse sequence of its configuration. Finally, because a sub-object (B3) is not fully constructed, the inclusive object A does not have a fully constructed and destructure.

1.3 Dynamic Allocation Object

Change class A to its member variable is dynamically generated:

// EXAMPLE 5

#include

// ... Class B Same as Before ...

Class A

{

PUBLIC:

A (): B1 (New B (1)), B2 (New B (2)), B3 (New B (3))

{

Std :: cout << "A :: A" << std :: endl;

}

~ A ()

{

DELETE B1;

Delete B2;

Delete B3;

Std :: cout << "A :: ~ a" << std :: endl;

}

Private:

B * Const B1; B * Const B2;

B * Const B3;

}

// ... Main Same as Before ...

This form complies with C habitual usage: assigns a member variable in the constructor of the inclusive object and fill it on the data function of the inclusive object, and then releases them in the destructor.

Compile and operate Example 5. The output is:

1 b :: b Enter

1 b :: b exit

2 b :: b enter

2 b :: b exit

3 b :: b Enter

Caught Exception

The results are similar to the example 4, but there is a huge difference: because ~ a is not performed, the DELETE statement is not executed, and the destructive function of * B1 and * B2 successfully assigned is not called. The unpredictable situation in Example 4 (two objects destructured) are now worse (three objects have no descent).

In fact, there is no harder than this. Remember, the Delete B1 statement has two functions:

l Call the destructor of * b1 ~ b.

l Call the Operator Delete release * B1's memory.

So we don't just encounter ~ b There are no problems caused by the call, and there is a memory leak problem caused by each B object. This is not a good thing.

The B object is A private, which is the implementation details, which is invisible to the other parts of the program. Instead of automatically generating a sub-object that is dynamically generated, the sub-object of automatically generating B does not change the external behavior of the program, which indicates that our example is designed in design.

1.4 Destructured Dynamic Generated Object

In order to close closer 4, we need to force the execution of the Delete statement in any situation. Place them in ~ a. We need to find a place where we can work, we know that it can be implemented. In the solution to the brain, the most elegant approach comes from the C standard running library:

// EXAMPLE 6

#include

#include

// ... Class B Same as Before ...

Class A

{

PUBLIC:

A (): B1 (New B (1)), B2 (New B (2)), B3 (New B (3))

{

Std :: cout << "A :: A" << std :: endl;

}

~ A ()

{

Std :: cout << "A :: ~ a" << std :: endl;

}

Private:

Std :: auto_ptr Const B1;

Std :: auto_ptr const b2;

Std :: auto_ptr const b3;

}

// ... Main Same as Before ...

AUOT_PTR reads "Auto-Pointer". As shown, Auoto-Pointer appears as a usual pointer and a mixture of an automatic object.

Std :: auto_ptr is a class template declared in . A std :: auto_ptr The performance of an object is very similar to a usual B * type object. The key difference is: Auto_Ptr is a real class object, which has a destructive function, and this destructor will Call the delete on the object referred to B *. The final result is that the dynamically generated B object is as destructed as an automatic B object.

A auto_ptr object can be used as a simple package for dynamically generated B objects. When the packaging disappears (destructure), it will also be taken away by the package. To actually look at this magic trick, compile and operate the case 6. turn out:

1 b :: b Enter

1 b :: b exit

2 b :: b enter

2 b :: b exit

3 b :: b Enter

2 b :: ~ b

1 b :: ~ b

Caught Exception

Bingo! The output is the same as the Example 4.

You may be strange why there is no call to B3 ~ b. This shows the failure of auto_ptr packaging? Not at all. The rules we have read are still playing. The call to the constructor for B3 accepts the parameters passing through the New B (3). A configuration of an abnormality terminates B3 occurs. Because B3 is not fully constructed, it will not be destructed.

There is no new place behind the atuo-pointer; String object is actually the AUTO-POINTER package for the char array. Nonetheless, I still expect to discuss auto_ptr and their families a more detailed one day. Currently, as long as Auto_PTR is used as a simple method of dynamically generated objects that guarantees an abnormality.

1.5 notice

Since B3's destructive function is not called, DELETE is not called for its memory. As seen earlier, the packaged B object is subject to two effects:

l The destructor ~ b is not called. This is the meaningful or even expectation because the B object is not fully constructed before.

l The memory is not released by Operator delete. Regardless of whether it is unexpected, it is never expected because the memory occupied by the B object is allocated, even if the B object is not completely constructed in this memory.

I need Operator delete called, even ~ b is not called. To achieve this, the compiler must call the Operator Delete in the case of being separated from the DELETE statement. Because I know that B3 is an annoying object in my example, I can explicitly call Operator delete for the memory of B3; but know this is just a teaching program, usually we can't predict which constructor will fail.

No, what we need is that the compiler detects that the constructor when the dynamically generated object fails, implies to call Operator Delete to release the memory occupied by the object. This has some act imitation the behavior of the compiler when the automatic object construct fails: the memory of the object is the same as the useless cell in the program body, is recyclable.

Fortunately, it has a big joy ending. To see this ending, you need to go back. At the end of the next time, I will reveal.

How does the C language provide this perfect feature, why the Standard Runar has declated Placement Operator Delete, and why you may want to do the same thing in your own library or class.

转载请注明原文地址:https://www.9cbs.com/read-26747.html

New Post(0)