# 8 Challenging topic: Exception Safety Difficulty: 9/10
The exception mechanism of C is a good way to solve certain problems, but it introduces many hidden control flows, difficult to use. Try yourself to achieve a very simple container (a Stack that can be PUSH and POP) to see which problems need to be involved in Exception-Safe and Exception-Neutral.
problem:
1. Realize the following containers, requires exception-neutral. The Stack object must always be consistent and the destructive is also monodies even when the internal operation is thrown, and the abnormality thus thus throwing to the caller is allowed. Template
Unsigned count (); // Returns # of t's in the stack void push (const t &); t pop (); // if Empty, returns default- // constructed t
Private: T * v_; // Pointer to a memory area big // enough for 'vsize_' t Objects unsigned vsize_; // the size of the 'v_' area unsigned vused_; // the number of t's actually // @ used in The 'v_' area};
Additional questions:
2. According to the current standard draft, the container in the standard library is an exception-safe or an exception-neutral? [Translator Note: This period GotW issued in April 1997]
3. Is the container should be an exception-neutral? why? What is the compromise?
4. Does the container should use an abnormal statement? For example, should we declare "Stack :: Stack () throw (Bad_alloc);"?
Challenge: For many current compilers, use "try" and "catch" to add additional burdes to your program, which best avoids use in this low-level reusable container. Can you implement all STACK member functions to meet the above requirements, do not use "try" and "catch"?
******************************************************************************************************************************************* Fully meet the above requirements) can be referenced:
Template
Solution [This answer is now not completely correct, updated and made a lot of expansion, please see the article published on the C Report magazine from September 1997 and 10 / November, more in-depth discussion. See my "Exceptional C "] Important: I don't claim that the following answers meet all the above requirements. In fact, I haven't found the compiler to compile. Although I have put all the various interactions and implications I can think of, the main purpose of this exercise is to explain that the programmer is very careful when writing an exceptionally safe code. Also, you can see a very good article in Tom Cargill: "Exception Handling: A False Sense of Security" (C Report, Vol.9 No.6, Nov-Dec 1994). He demonstrated that exception handling is very difficult , But please note that his article is not advocating complete abnormality, just telling people that it takes very careful to use an abnormal mechanism. The last description: In order to make the answer more simple, I decided not to use the basic class method to solve the resource ownership problem. I will invite Dave Abrahams (or others) to continue our answer and demonstrate this very effective way. Review our problem, the following is the interface that needs to be implemented: Template
Unsigned count (); // Returns # of t's in the stack void push (const t &); t pop (); // if Empty, returns default- // constructed t
Private: T * v_; // Pointer to a memory area big // enough for 'vsize_' t Objects unsigned vsize_; // the size of the 'v_' area unsigned vused_; // the number of t's actually // @ used in The 'v_' area}; now discusses the implementation. First we have a request for T: that the destructor of T must not throw an exception. If the destructive function can throw an exception, you have a safe implementation that many operations will be very difficult or even impossible. // ----- Default CTOR ----------------------------------------- ---- Template
// ----- Copy CTOR ----------------------------------------- -------- Template
// ----- Copy Assignment -------------------------------------------------------------------------------------------------- --Template
Delete [] v_; // Note that this statement cannot throw an exception, because the destructor does not throw an exception // // and the delete operator declares for throw ()
v_ = v_new; vsize_ = other.vsize_; vused_ = other.vused_;}
Return * this; // safe, no copy}
// ----- DTOR ---------------------------------------------------------------------------------------------------- ------------ Template
// ----- Count ---------------------------------------------------------------------------------------------------- ----------- Template
DELETE [] V_; // Will not throw an exception v_ = v_new; vsize_ = vsize_new;}
v_ [vused_] = t; // If the copy operation throws an abnormality, vused_; // Because Vused_ does not grow, the status has not changed}
/ / ----- POP ------------------------------------------------------------------------------------------------------ ------------- Template
//// Note: Reader Wil Evers The first pointing, // "You can know if the interface definition from the POP () in the question, // The code of the user is definitely not unusually safe, // It first creates one Side effects (one element pops up from the stack) [Translator Note: That is, the status of the Stack object is changed] // then makes an abnormal place (copy the return value to the caller target object) omissions
// Outside an exception capture code. "// This reflects that the code written safe abnormality is difficult, not only because it affects the implementation, and // affects the definition of the interface itself! Some interfaces, such as this interface (translator: that is, the Pop defined in the problem) () Interfaces), // Unable to achieve complete exception security.. // / / Correct this problem is to redefine the interface of this function is // "Void Stack
// We can know that the copy element to the results // has been successful before changing the state of the stack. For example, the following is another version of the more secure POP () // template
Try {Copy (SRC, SRC SRCSIZE, DEST);} catch (...) {delete [] dest; throw; // Rethrow the Original Exception} // If it is performed, the copy is successfully completed
Return Dest;}
Additional questions 2. According to the draft standard, the container in the standard library is unusually safe or unusually neutralized? The answer to this problem is currently uncertain. Recently, there are some discussions within the C Standards Committee, and the content is that the standard library container should provide weak abnormal safety ("Container is always conventional") or strong unusual security ("all container operations have submitted or rollback semantics" ). Dave Abrahams pointed out in the discussion through the email, usually if you achieve a weak abnormal security guarantee, simultaneously reach a strong unusual security. The implementation of several operations is to belong to this situation.
3. Is the container should be unusually neutral? why? What is the compromise?
For some containers, if you want to achieve an exception-neutral, some operations produce inevitable space costs. Abnormal neutral itself is a good thing, but the cost of achieving the space and time required for strong abnormal security is far greater than that only realizes weak abnormalities, it is not very practical. A usual compromise is to explain what operations in the document should not throw an exception, and then ensure abnormal neutrality in the case of compliance with this default condition. 4. Does the container should use an abnormal statement? For example, should we declare "Stack :: Stack () throw (Bad_alloc);"?
You should not use an exception declaration, because in advance, don't know which operations will throw an exception, and don't know what exception will be thrown.
Note that some containers are operated (such as count ()) just simply returns a value, we can conclude that it will not throw an exception. Although we can declare "throw ()", there are two reasons to do not do the same: First, do so, we will limit the implementation of the internal implementation as a possibility to throw an exception; Whether it is abnormal, the abnormal statement will bring extra overhead performance. For frequent use, it is best not to use an exception declaration to avoid additional burden on this performance. Challenge: For many current compilers, use "try" and "catch" to add additional burdes to your program, which best avoids use in this low-level reusable container. Can you implement all STACK member functions to meet the above requirements, do not use "try" and "catch"?
Yes, because we just need to capture "...", generally, similar to the following code
Try {trycode (); catch (...) {catchcode (PARMS); throw;} can be written as struct janitor {Janitor (PARMS P): PA (p) {} ~ janitor () {if uncaught_exception () catchcode (PA); PARMS Pa;};
{Janitor J (PARMS); // No matter how the trycode is successfully implemented or throwing an exception, J will be destructed with TRYCODE ();
We only apply TRY / CATCH in the NewCopy function, so we can rewrite newcopy as follows:
Template
Struct Janitor {Janitor (T * P): Pa (p) {} ~ janitor () {i (uncaught_exception ()) delete [] pa;} t * pa;};
T * dest = new t [destsize]; // If it is performed, the memory allocation and each constructor have been successful.
Janitor J (DEST); COPY (SRC, SRC SRCSIZE, DEST); // If the program is executed, the copy has been successful ... Otherwise, // J is destructed when stack-unwinding, The destructor is responsible for // Recycling Dest to avoid memory leakage
Return Dest;}
I have said in front, I have talked with a few people who have tested experimental speed. When there is no abnormality, Try / Catch is usually relatively fast and is expected to be faster. However, understanding this method is still very important because it often brings us a more beautiful and easier-to-maintain code, and some current compilers are not very efficient for Try / Catch generated when there is abnormal and unusually generated code. of.