GURU Of The Week Terms 23: Survival of the Object (Part 2)

zhaozj2021-02-08  461

Gotw # 23 Object Lifetimes - Part II

Author: Herb Sutter

Translation: Cat * G

[Declaration]: This article takes the Guru of The Week column on www.gotw.ca website, and its copyright belongs to the original person. Translator Cat * G translates this article without the consent of the protocol. This translation is only for self-study and reference, please read this article, do not reprint, spread this translation; people download this translation content, please delete its backup immediately after reading. Translator Cat * G is not responsible for people who violate the above two principles. This declaration.

Revision 1.0

GURU Of The Week Terms 23: Survival of the Object (Part 2)

Difficulty: 6/10

(Next Terms 22, the Terms of this section consider a C customary method that is often recommended - it is often dangerous and wrong.)

[Problem]

[problem]

Review the following usual method (expressed as follows in the form of common code):

T & T :: Operator = (const t & other) {

IF (this! = & other) {

THIS-> ~ T ();

NEW (THIS) T (Other);

}

RETURN * THIS;

}

1. What kind of legal purpose is trying to achieve? Fix all encoding defects in the above code.

2

. If all the defects have been fixed, is this customary? Explan to your answer. If it is unsafe, how do programmers achieve their expected goals?

(See GotW Terms 22, and October 1997 C Report.)

[Solution]

[answer]

Review the following usual method (expressed as follows in the form of common code):

T & T :: Operator = (const t & other) {

IF (this! = & other) {

THIS-> ~ T ();

NEW (THIS) T (Other);

}

RETURN * THIS;

}

[Summary] [1]

[Small] [Note 1]

This customary method is often recommended and appears as an example in the C standard draft. [Note 2] But it has a bad form, and - if you want to describe - it is harmful. Please don't do this.

1. What kind of legal purpose is trying to achieve?

This inertial method implements a copy assignment operation with a copy construction operation. That is to say, the method is attempting to ensure that the "T 's copy construction is the same as the copy assignment," to avoid programmers to repeat the same code in two places.

This is a noble goal. In any case, it makes programming easier, because you don't have to write the same code twice, and when T is changed (for example, add a new member variable to t), you will not be updated as before. One of them later forgot to update the other.

If the virtual base class has a data member, then this usual method is still quite useful, because if this method is not used, the data member will be assigned several times in the best case, and in the worst case, it will be applied. Operate with incorrect assignments. This sounds quite, but there is still no big use, because the virtual base class is actually should not have data members. [Note 3] In addition, since there is a virtual base class, the class means that the class is designed for inheritance - this again means: (as we are about to see), we can't use this usual method, reasons It is too dangerous. Fix all encoding defects in the above code.

The above code contains a modified defect, as well as a number of defects that cannot be corrected.

[Problem # 1: IT CAN SLICE OBJECTS]

[Question # 1: It cuts the object]

If t is a base class with a virtual destructor, "this-> ~ t ();" This sentence has performed the error operation. If this call is executed for an object of a derived class, the execution of this sentence will destroy the object of the object and replace it with a T object. And this result is almost a positive destructive effect. (More Discussion on "Slicing" problem, see GotW Terms 22.)

In particular, this situation will cause the encoders who write derivatives to fall into the world's hell (there are other potential traps on derived class, see the following description). Recall that the derived assignment operator is usually written based on the base class-based assignment operation:

Derived &

Derived :: Operator = (const derived&) {

Base :: Operator = (other);

/ / ... now assigns a member of the derived member ...

RETURN * THIS;

}

This way we get:

Class u: / * ... * / t {/ * ... * /};

U & u :: operator = (const u & other) {

T :: operator = (other);

// ... now assigns the U member ... 呜呼

Return * this; // 呜呼

}

As shown in the code, the call to the T :: Operator = () has a destructive impact on all code (including the assignment operation of the U member, and the return statement). If the destructor of u does not reset its data member to an invalid value (the translation: You can compile the run), which will be a mysterious, extremely difficult to debug period error.

To correct this problem, you can call "this-> T :: ~ t ();" as an alternative, this can guarantee "For a derived class object, only the T SubObject is replaced (instead of the entire derived class object is cut) The error converted to a T object) ". This is just a more dangerous danger, and this replacement will still affect the writer of derived class (see the following description).

2. If all the defects have been fixed, is this customary?

No, not safe. Note: If you don't give up the entire usual method, any of the following questions cannot be resolved:

[Problem # 2: It's not exception-way]

[Question # 2: It is not unusual security]

The 'new' statement will evoke the copy constructor of T. If this constructor can throw an exception (in fact, many or even the classes report to report the error of constructor by throwing an exception), then this function is not unusual because it throws an exception in the constructor. It will result in "destroying the original object without replacing the new object". As with a sliament problem, this defect will have a destructive impact on any of the subsequent attempts to use this object. Worse, this may also result in the case where the program is trying to destroy the same object to be destroyed. Because the external code is unable to know if the destructor of this object has been run. (See Gotw Terms 22 More about Repeated Destructure Discussion.)

[Problem # 3: It's inefficient for assocignment]

[Question # 3: It makes the assignment operation inefficient]

This usual method is inefficient because the constructor during the assignment is almost always involved in more than the reset value. Conventional and refactoring is made more effort.

[Problem # 4: IT Changes Normal Object Lifetimes]]

[Question # 4: It changed the normal object life]

This usual destructive affects the code that relies on normal object survival. In particular, it destroys or intervenes all the common "initialization is the" Initialization IS Resource Acquisition ".

For example, if T is acquired a mutext lock or a database transaction in the constructor, it will happen in the destructive function, what happens? This lock or transaction will be released in an incorrect manner and is re-acquired in the assignment operation - this generally destructive influence of customer code (Client Code) and this class itself. In addition to the base class of T and T, if the derived class of T also depends on the normal survival semantic semantic, it also devysces these derived classes.

Some people will say, "I will certainly never use this customary method for a class that acquires and releases mutex in the constructor and the destructive function!" Answer is simple: "Really? How do you know that you are using? Those (direct or indirect) base classes don't do this? "Said that you often can't know this situation, you should never rely on those who work that works that it seems normal but with the object life. class.

The fundamental problem of this conventional method is that it is stitched on the meaning of constructor and sect. Constructive operation and sectoral operations respectively correspond to the beginning and end of the object survival, and objects usually acquire and release resources at these two times. Construction operation and destructive operation is not used to change the object value (in fact, they do not change the value of the object, they just destroy the original object and replace it with a look, just have a new value, actually This new thing is not a matter at all about the original object).

[Problem # 5: IT CAN STILL BREAK Derived Classes]

[Question # 5: It can have a destructive impact on the school

Use "this-> t :: ~ t ();" as an alternative statement, after the problem # 1, this usual method only replaces T SubObject in the derived class object. Many derived classes can work so normal, exchange their base class subobject, but some derived classes may not work.

In particular, some derived classes can be controlled to their base classes. If this information is blindly modified, it is destroyed and reconstructed in an invisible manner without knowing this information. Objects are certainly counted as a modification), then these derived classes may result in failure. Once the assignment operation has made any additional operations that exceed "a" a "normal write" assignment operator should do, this hazard will be reflected. [Problem # 6: IT Relies on Unreliable Pointer Comparisons]

[Question # 6: It relies on unreliable pointer comparison operations]

This customary method is fully dependent on "this! = & OTHER" test. (If you have questions about this, consider the situation of self-assignment.)

Its problem is that this test does not guarantee what you want to guarantee: C standard guarantee "The result of the comparison of multiple pointers to the same object must be" equal ", but not guaranteed" The result of the comparison of multiple pointers points to different objects must be "unpane" ". If this happens, the assignment cannot be completed. (About "this! = & Other" test, see GotW Terms 11.)

If someone thinks this is too drill angle, please see the brief discussion in GOTW Terms 11: All "must" check the copy assignment of self-assignment is not unusually safe. [Note 4] [Note: Please see Exceptional C and its errata to get updated information. ]

There are also potential hazards that can affect customer code and / or derive class (such as virtual assignment operators - this even in the best case, it is still very strange, but there is already enough Multi-content is used to demonstrate the serious problems of the usual method.

[So What Should We Do Instead?]

Now how should we do it?

If it is unsafe, how do programmers achieve their expected goals?

Cautions with the same member function (copy construct and copying value) Note: This means we only need to write and maintain operational code in one place. The usual method in this Terms of Terms is just that the error is selected to do this. that is it.

In fact, the usual method should be in turn realized: copy constructive should be implemented in copying assignments, not in turn. E.g:

T :: T (const t & other) {

/ * T :: * / Operator = (other);

}

T & T :: Operator = (const t & other) {

// Real work is here

// (Probably can be completed in an abnormal state, but now

// It can throw an abnormality, but it will not have any adverse effects like it.

RETURN * THIS;

}

This code has all the benefits of the original usage, but there is no problem in any original method. [Note 5] For the beauty of the code, you may also write a common private assist function, use it to do real work; but this is the same, there is no difference:

T :: T (const t & other) {

DO_COPY (OTHER);

}

T & T :: Operator = (const t & other) {

DO_COPY (OTHER);

RETURN * THIS;

}

T & T :: DO_COPY (Const T & Other) {

// The real job is here / / (probably can be completed in an abnormal state, but now

// It can throw an abnormality, but it will not have any adverse effects like it.

}

[Conclusion]

[in conclusion]

The original customary method is full of defects and is often wrong, which makes the derivative of the writer of the world hell-like life. I can't help but I want to put this original usage law in the kitchen in the office and indicate: "There is a tyrannosaurus."

Excerpted from the GOTW coding standard:

- If necessary, write a private function to make copy operation and copy assignment sharing code; do not use the "use of explicit destructor and followed by a Placement New" method to achieve "copy constructive implementation copy assignment Operation "This purpose, even if this so-called technique will appear a few times in the newsgroup every three months. (That is, do not write the following code :)

T & T :: Operator = (const t & other)

{

IF (this! = & taher)

{

THIS-> ~ T (); // harmful!

New (this) t (other); // harmful!

}

RETURN * THIS;

}

[Notes]

[Note 1]: Here I ignore some metamorphosis (for example, heavy load T :: Operator & () makes it a thing other than this). GotW Terms 11 refer to some situation.

[Note 2]: The example in the C standard is intended to demonstrate the rules of the object life, not to recommend a good reality (it is not realistic!). The example in 3.8 / 7 will be given below (a small modification in space) is interested in readers:

[example:

Struct c {

INT I;

Void f ();

Const C & Operator = (Const C &);

}

Const C & C :: Operator = (Const C & Other)

{

IF (this! = & taher)

{

THIS-> ~ c (); // '* this' ending

NEW (THIS) C (Other);

// New C-type object is created

f (); // is defined herein

}

RETURN * THIS;

}

C1;

C C2;

C1 = C2; // The definition is well defined here

C1.f (); // The definition is well defined; C1 refers to

// New C-type object

- Example finished]

It is not recommended to actually use this code further evidence in: C :: Operator = () returns a const c & not simply C &, which unnecessary avoids these objects in the standard library container (Container) Portable usage.

Excerpted from the GOTW coding standard:

-

Copy the copy assignment

"T & T :: Operator = (const t &)"

- Do not return Const T &, although this avoids the use of "(a = b) = c"; this means: you can't put T object into the standard library container for transplantive considerations - Because it needs to assign a value to return a simple T & (CLINE95: 212; Murray93: 32-33)

[Note 3] See "Effective C " of Scott Meyers

[Note 4]: Although you can't rely on the "this! = & OTHER" test, it is not wrong if you do this in order to troubleshoot the known self-assignment situation by optimizing processing. If it works, you can save an assignment operation. Of course, if it doesn't work, your assignment operator should still be written in the way "for self-assignment is safe." With regard to the use of this test as an optimization, some people agree that someone object - but this is beyond the discussion scope of the GOTW of this issue. [Note 5]: Indeed, it still requires a default constructor and may still be the most efficient; but you know that you can use the initialized list (Initializer Lists) to get the best efficiency (list of initialization That is, the member variable is initialized while the construction process is initialized, rather than being divided into first construction, and then assigns two steps to complete). Of course, this is also the commonality to sacrifice the code, and the trade-off of this also exceeds the discussion scope of the GOTW.

(Finish)

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

New Post(0)