Gotw # 08 Challenge Edition Exception Safety
Author: Herb Sutter
Translation: Kingofark
[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 08: GOTW Challenge - Unusual Treatment Security
Difficulty: 9/10
(The abnormal handling mechanism is the best way to solve some problems, but it also introduces many hidden control flows; sometimes it is not easy to use it correctly. Try yourself to achieve a simple container (this It is a stack that can perform PUSH and POP operations), see which things will happen in an exception-safe (Exception-Safe) and an exception-neutral case.)
[problem]
1. Implement the following exception - EXCEPTION-neutral Container. Requirements: The status of the Stack object must maintain its consistency; even if there is an internal operation, the Stack object must also be a destructible; T 's exception must be able to pass to its caller.
Template
// T must have a default constructor and copy constructor
Class Stack
{
PUBLIC:
Stack ();
~ Stack ();
Stack (Const Stack &);
Stack & Operator = (Const Stack &);
Unsigned count (); // Returns the number inside the stack
Void Push; CONST T & CONST;
T POP (); // If it is empty, return the default-constructed T
Private:
T * v_; // Point to an object for 'vsize_' T object
// Sufficient large memory space
The size of the unsigned vsize_; // 'v_' area
Number of T actually used in unsigned vused_; // 'v_'
}
Additional questions:
2. According to the current C standard, the container in the standard library is an abnormally-safe (Exception-Safe) or exception - EXCEPTION-NEUTRAL?
3. Should Container become an exception - EXCEPTION-NEUTRAL? why? Is there any compromise?
4. Does Container use an abnormal rule? For example, we should not do this, such as "stack :: stack ().
Challenge the limits:
5. Since Try and Catch will bring some additional loads to your program in many compilers, we are best to avoid using them in the Reusable Container. Can you implement all member functions of the Stack without using try and catch? Two examples are provided here for reference (Note that these two examples do not necessarily meet the requirements in the above topics, for reference only, so that you will get the problem):
Template
Stack
: v_ (0),
VSIZE_ (10),
vused_ (0)
{
v_ = new t [vsize_]; // Initial memory allocation (creation object)
}
Template
T stack
{
T result; // If it is empty, return to the default-constructed T
IF (vused_> 0)
{
Result = V _ [- vused_];
}
Return Result;
}
[answer]
[Author ": The solution here is not completely correct. This Paper has been amended, you can find on the September, November 1997, November 1997 in C Report; in addition, its final version is in my "Exceptional C ". ]
Important: I really dare not guarantee the following solutions to fully meet the requirements of my original question. In fact, I can't connect the compiler that can correctly compile these code! Here, I discussed all the interactions I could want; and the main purpose of this article is to explain that they need extra care when writing an exception-safe (Exception-Safe) code.
In addition, Tom Cargill also has a great article "Exception Handling: a false Sense of Security" (C Report, Vol.9 No.6, NOV-DEC 1994). He explained this article that abnormal treatment is a tricky small flower trick, which is very strong, but it is not generally not to use abnormal treatment, but it is said that people don't excessive superstition. Just recognize this and be careful when using it.
[Author Remember: Let's later speak. In order to simplify the solution, I decided not to discuss the base class technology (Base Class Technique) that is used to solve the homematic problem of Exception-Safe resources. I will invite Dave Abrahams (or others) to continue discussing, elaborating this very effective technology. ]
Now review our problems first. The interface required is as follows:
Template
// T must have a default constructor and copy constructor
Class Stack
{
PUBLIC:
Stack ();
~ Stack ();
Stack (Const Stack &);
Stack & Operator = (Const Stack &);
Unsigned count (); // Returns the number inside the stack
Void Push; CONST T & CONST;
T POP (); // If it is empty, return the default-constructed T
Private:
T * v_; // Point to an object for 'vsize_' T object
// Sufficient large memory space
Unsigned vsize_; // 'V_' area The number of Unsigned Vused_; // 'V_' is actually used in the number of T.
}
Now let's take a look at the realization. We have a request for T. The destructor cannot throw an exception. This is because if the destructor is allowed to throw an exception, then we are difficult to implement it under the premise of ensuring code security.
// ----- Default CTOR ----------------------------------------- -----
Template
Stack
: v_ (New T [10]), // Default memory allocation (creation object)
VSIZE_ (10),
vused_ (0) // is now not used
{
/ / If the program arrives here, there is no problem in the construction process, okay!
}
// ----- Copy constructor -------------------------------------- ---------
Template
Stack
: v_ (0), // Nothing allocated memory, is not used
vsize_ (other.vsize_),
vused_ (other.vused_)
{
v_ = newcopy (other.v_, other.vsize_, other.vsize_);
// If the program arrives here, there is no problem with the copy construction process, okay!
}
// ----- Copy value ----------------------------------------- -
Template
Stack
{
IF (this! = & taher)
{
T * v_new = newcopy (other.v_, other.vsize_, odher.vsize_);
/ / If the program arrives here, there is no problem in memory allocation and copying process, okay!
delete [] V_;
// This cannot be thrown out, because the destructive function of T cannot throw an abnormality;
// :: Operator delete [] is declared to throw ()
v_ = v_new;
vsize_ = other.vsize_;
vused_ = other.vused_;
}
Return * this; // Safety, no copy issues
}
// ----- Destructor ---------------------------------------- ----------------
Template
Stack
{
DELETE [] V_; //, this is not possible to throw an exception
}
// ----- Count ---------------------------------------------------------------------------------------------------- -----------
Template
Unsigned Stack
{
Return vused_; // This is just a built-in type, no problem
}
// ----- Push operation ----------------------------------------- ------------ Template
Void Stack
{
IF (vused_ == vsize_) // can grow as needed
{
Unsigned vsize_new = (vsize_ 1) * 2; // Growth Factor
T * v_new = newcopy (v_, vsize_, vsize_new);
/ / If the program arrives here, there is no problem in memory allocation and copying process, okay!
DELETE [] V_; //, this is not possible to throw an exception
v_ = v_new;
vsize_ = vsize_new;
}
v_ [vused_] = t; // If you throw an abnormality here, add operations will not be executed.
vused_; // State will not change
}
// ----- POP operation ----------------------------------------- -------------
Template
T stack
{
T result;
IF (vused_> 0)
{
Result = v_ [vused_-1]; // If you throw an exception, the subtraction operation will not be executed,
--VUSED_; // State will not change
}
Return Result;
}
//
// Note: Attentive readers, Wil Evers, pointed out,
// "As defined in the question, POP () forced users write non-abnormal - secure code,
// This first produces a negative effect (ie, from the middle of the stack out of the POP);
// Second, this may also result in missing some abnormalities (such as copying the return value to the code caller)
// On the object). "
//
/ / This also shows that it is difficult to write an exception - one reason for the safety code is because
// It not only affects the implementation of the code, but also affects its interface!
// Some interfaces (such as this one here) cannot be implemented in the case of complete guarantees.
//
// A possible way to solve this problem is to reform the function
// "Void Stack
// This, we can learn whether the copy of the result is really successful before the status of the stack.
// For example, as follows,
// This is a POP () that is more abnormal - security
//
Template
Void Stack
{
IF (vused_> 0)
{
Result = v_ [vused_-1]; // If you throw an exception here,
--VUSED_; // The subtraction operation will not be executed,
} // The status will not change
}
//
/ / We can also let POP () return void, then provide a Front () member function,
// Use to access the top object
//
// ----- Auxiliary function ----------------------------------------- -
// When we want to copy T from the buffer to a larger buffer, the // This auxiliary function helps allocate the new buffer and copy the elements.
// If an abnormality occurs here, the auxiliary function will release all temporary resources.
// and transfer this abnormally to ensure that memory leaks do not occur.
//
Template
T * NewCopy (Const T * SRC, Unsigned Srcsize, Unsigned Destsize)
{
Destsize = max (srcsize, destsize); // basic parameter check
T * DEST = New T [destsize];
// If the program arrives here, there is no problem with the memory allocation and constructor, okay!
Try
{
Copy (SRC, SRC SRCSIZE, DEST);
}
Catch (...)
{
DELETE [] DEST;
Throw; // Re-throw the original exception
}
// If the program reaches this, there is no problem with copy operation, Okay!
Return DEST;
}
Answer for additional questions:
Question 2: According to the current C standard, the container in the standard library is an abnormally-safe (Exception-Safe) or an exception - EXCEPTION-NEUTRAL?
There is no clear statement about this problem. Recent committees have also launched some relevant discussions involving providing and ensuring weak abnormalities (ie "Container can always operate") or should provide and ensure strong unusual security (ie "all Container operations To have the characteristics of "commit-or-rollback)" to be semantically "). Just as Dave Abrahams's discussion in the committee and subsequent discussions in the discussion of email, if the guarantee for weak abnormal security is achieved, then strong abnormal security is easy to guarantee. Several operations we mentioned above are this.
Question 3: Do you have to become an exception - EXCEPTION-NEUTRAL? why? Is there any compromise?
Sometimes, in order to ensure some Container anomaly-neutral (Exception-NeutRality), some of its operations will inevitably pay some space costs. Visible abnormal - neuttRality itself is good, but when the space or time cost to be paid to achieve strong abnormal security is much greater than the power of weak abnormal security, exception - neutrality (Exception- NeutRality is too unrealistic. There is a better compromise, that is, using the document records not allowing to throw an exception operation, and then ensure its exception-neutrality by complying with these document rules.
Question 4: Does Container use an abnormal rules? For example, we should not do this, such as "stack :: stack ()," Stack :: Stack () declaration?
the answer is negative. We can't do this, because we don't know what operations in T will throw an exception, nor do you know what exception will be thrown.
It should be noted that some operations of some Container (for example, count ()) are just simply returns a value, so we can conclude that it will not throw an exception. Although we can use throw () to declare this type of operation, it is best not to do this; the reasons are two: first, if you do this, then you want to modify the implementation details so you can throw an exception At the time, it will find that there is a big limit; second, whether it is thrown, the exception spec will bring additional performance overhead. Therefore, for those frequently used operations, it is best not to perform an exception specification to avoid this performance overhead. A answer to challenge the limit:
Question 5: Since use TRY and CATCH in many compilers bring some additional loads, it is best to avoid using them in the Reusable Container. Can you implement all member functions of the Stack without using try and catch?
Yes, this is feasible, because we only need to capture the "..." section (see the code below). Generally, the shape is like
Try {trycode ();} catch (...) {catchcode (PARMS); throw;}
The code can be rewritten into this:
Struct janitor {
Janitor (PARMS P): PA (P) {}
~ Janitor () {if uncaught_exception () catchcode (pa);
Parms Pa;
}
{
Janitor J (PARMS); // J Is Destroyed Both if Trycode ()
// succeeds and if it thrtows
Trycode ();
}
We only use TRY and CATCH in the Newcopy function. Below is the rewritten Newcopy function, it is used to reflect the above-mentioned rewriting technique:
Template
T * NewCopy (Const T * SRC, Unsigned Srcsize, Unsigned Destsize)
{
Destsize = max (srcsize, destsize); // Basic Parm Check
Struct janitor {
Janitor (t * p): pa (p) {}
~ Janitor () {if (uncaught_exception ()) delete [] Pa;}
T * pa;
}
T * DEST = New T [destsize];
// if we got here, the allocation / ctors bere okay
Janitor J (DEST);
Copy (SRC, SRC SRCSIZE, DEST);
// if we got here, The copy is okay ... OtherWise, J
// Was Destroyed During Stack Unwinding and Will Handle
// the Cleanup of Dest to Avoid Leaking Memory
Return DEST;
}