Gotw # 21 Code Complexity - Part II
Author: Herb Sutter
Translation: k] [N g of @ rkTM
[Declaration]: This article takes the Guru of The Week column on www.gotw.ca website, and its copyright belongs to the original person. Translator Kingofark translated this article without the consent of the original person. 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 Kingofark is not responsible for people who violate the above two principles. This declaration.
Revision 1.0
GURU Of The Week Terms 21: Complexity of Codes (Part 2)
Difficulty: 7/10
(Big Challenge: Modifying the function of only three lines of code in GOTW # 20, making it strongly Exception-Safe). This exercise gives us an important lesson on exception-safty. .)
[problem]
Let us consider the function in GOTW # 20. This function is an exception-safe (still working properly when there is an abnormality) or exception-neutral (can transfer all exceptions to the caller)?
String EvaluateSarandReturnName (Employee E)
{
IF (e.title () == "CEO" || E.SALARY ()> 100000)
{
Cout << E.First () << "<< E.last ()
<< "is overpaid" << Endl;
}
Return E.First () "" E.last ();
}
Please explain your answer. If it is unusual, it is supporting Basic Guarantee or support strong guarantee? [Translation: Basic Guarantee, Basic Assurance; Strong Guarantee, Strong Guarantee] If it is not an abnormal security, how do you modify it to support Basic Guarantee or Strong Guarantee?
Here we assume that all modified functions are unusual (ie, there may be exceptions, but there is no side effects when throwing an exception), and it is assumed that any objects used (including temporary objects) are also unusual security (I.e., when these objects are destroyed, the resources occupied by it can be cleaned).
[Background knowledge: Basic Guarantee and Strong Guarantee]
For detailed discussion on Basic Guarantee and Strong Guarantee, please see the article I am in C Report Sep / NOV / DEC 1997. Simply, Basic Guarantee guarantees DESTRUCIBILITY and does not leak; and Strong Guarante guarantees a full commist-or-rollback (either "either execute, or does not perform" atomic rules) semantics .
[answer]
Let us consider the function in GOTW # 20. This function is an exception-safe (still working properly when there is an abnormality) or exception-neutral (can transfer all exceptions to the caller)? String EvaluateSarandReturnName (Employee E)
{
IF (e.title () == "CEO" || E.SALARY ()> 100000)
{
Cout << E.First () << "<< E.last ()
<< "is overpaid" << Endl;
}
Return E.First () "" E.last ();
}
[Notes about hypothesis]
[A Word ABOUT Assumptions]
As mentioned, we assume that all modified functions - including stream function - all are unusually secure (you may throw an exception, but there is no side effects when throwing an exception), and assume ready All objects - including temporary objects - are also unusual (ie, the resources occupied by these objects can be cleaned).
However, streams are biased to make a blend of bins - this degenerate "un-rollbackable" side effects. For example, an operator << may throw an exception after the output of the output String, and the part that has been output is not "un-emitted"; the same, stream ( Stream) The error status will also be set at this time (the translation: the change of the error is generated). In most cases, we have ignored these situations; the focus of this discussion is how "When the function has two side effects that do not have the same side effects, how to make functions are unusual."
[Basic Guarantee VS. Strong Guarantee]
According to the title, the function satisfies the Basic Guarantee: When an abnormality occurs, the function does not generate resource leakage.
This function does not satisfy the Strong Guarantee. Strong Guarantee means that if the function fails by exception, the status of the program must remain unchanged. However, the function here has two different side effects (as suggested by the name of the function):
1. A "... OVERPAID ..." message is sent to COUT;
2. A name string is returned.
If you take into account you 2nd, the function can meet the strong Guarantee, because the value will not be returned when an exception is generated. If you take into account the 1st, the function is still not unusual, there are two reasons:
1. If the entire message is completely thrown after the entire message is completely sent to COUT, the entire message is completely thrown out (for example, the fourth << thrown abnormality in the code), then there is already Part of the message is output. [Note 1]
2. If the message is successful output, the function is generated after successful output (), then this message has been sent to COUT, although the function fails because of an exception. To meet the strong guarantee, the behavior of the function should be: either two things (the translation:, "
Can we reach this requirement? Below is a way we may try (you may call it for the first time):
String EvaluateSarandReturnName (Employee E)
{
String result = E.first () "" E.last ();
IF (e.title () == "CEO" || E.SALARY ()> 100000)
{
String message = E.First () " E.last ()
"is overpaid / n";
Cout << Message;
}
Return Result;
}
This code is not bad. It should be noted that in order to make the entire String only use one << call, we replace ENDL with a newline (although both are not completely equivalent). . )
[A slightly worried question]
[A little bothersome issu]
To now, we still have a tiny flaw, as shown in the following user code:
String thename;
Thename = evataAndSaAndreturnName (bob);
Since the result of the function is returned by Return By Value, the copy constructor of String is evoked; the copy assignment operator is also evoked, and it is used to copy the result to the thename. . If there is any failure in these two copy operations, then the side effects of the function have occurred (because the message has been completely output, the return value has been completely constructed), and the result will not be saved. (欧!).
Can I do better? Can I avoid this problem with "avoid copying operations"? That is to say, we let the function accept a reference parameter of a non-constant String and put the return value in this parameter:
Void EvaluatesAndretaTurnName (Employee E, String & R);
{
String result = E.first () "" E.last ();
IF (e.title () == "CEO" || E.SALARY ()> 100000)
{
String message = E.First () "" E.last () "is overpaid / n";
Cout << Message;
}
R = Result;
}
Unfortunately, the assignment of R will still fail, which will cause one of the side effects to be completed and the other is not completed. The most critical problem is that this second attempt did not bring us much benefits.
So we may try to use auto_ptr to return the result (you may wish to call this time as a third time):
Auto_PTR
EvaluateSalaryandReturnName (EMPLOYEE E);
{
Auto_PTR
= New string (E.First () " E.last ());
IF (e.title () == "CEO" || E.SALARY ()> 100000)
{
String message = E.First () " E.last ()
"is overpaid / n";
Cout << Message;
}
Return Result; // Rely on Transfer of Ownership
}
This is the trick of the solution - we effectively hide the operation of generating the second side effect (return value), but also guaranteed that "after the first side effect (print message) is completed, only do not throw A Nonthrowing operation returns the result safely to the function caller. " So what is the price of this? As frequently occurring when strong exception safety, this strong security is cost-we uses additional dynamic memory allocation.
[Abnormal safety and multiple side effects]
[EXCETION SAFETY AND MULTIPLE SIDE EFFECTS]
As can be seen from this discussion, it is possible to complete the two side effects in the third attempt to complete the two side effects (except that the stream is related). The reason is because the two side effects seem to be automated through some kind of technique - this is to say that all "real" work made for two side effects can be completed in such a way. The side effects that can be seen can be done with only non-throwing operations without throwing an exception.
Although this time we are still more fortunate, the situation is not always so simple: to write a function of strong unusual security, and let the function contain two or more side effects that can be completed, mutually differential (for example,) When two side effects send messages to COUT, another to send messages to CERR, how can it be?) - This is impossible, because Strong Guarantee is required to remain unchanged when there is an abnormality. " In other words, it is not possible to generate side effects as long as there is abnormal appearance. Usually when you encounter two side effects that cannot be automatically completed, the only way to achieve strong exception security is to divide the function into two functions that can automate side effects.
This period GotW is intended to describe 3 focus:
1. To provide guarantees for strong exception safety, often (but not always) needs you to give up one-to-range performance.
2. If a function contains multiple side effects, then it is always uncommon. At this point, the only way is to divide the function into several functions so that the side effects of each partial function can be complete. 3. Not all functions require strong abnormal security. The original code in this Territor and the first attempt have been able to meet the Basic Guarantee. In many cases, the first tried code is better enough to minimize the possibility of side effects in an abnormal case, and do not need to lose a certain performance like the third try.
[And: flow and side effects]
[Postscript: streams and side effects]
As shown in this Terms, we have the assumption that "there is no side effects called" "is not entirely true. In particular, we cannot guarantee that "the stream will not suddenly fail after the output of the output". This means that we cannot implement true Commit-OR-Rollback semantics in the function of performing flow output - at least in these standard streams. In addition, if the flow output fails, the state of the stream will change. At present, we don't check this, nor try to recover it - but we can still modify the function to capture the abnormality caused by the flow, and re-turnover an exception before removing the caller Error Flags in Cout.
[Note 1]: If you think is "worry if a message can be completely applied to COUT operations", you can say that your thoughts are good. Here, no one may care about this. However, any function attempt to complete two side effects is followed by the same principle - this is why our follow-up discussion is also meaningful.
(Finish)