Guru of the week # 8 Challenging topic: Exception Safety

zhaozj2021-02-08  291

# 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 // T Must Have Default Ctor and Copy Assignment Class Stack {public: stack (); ~ stack (); stack (const stack); stack & operator = (const stack);

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 Stack :: Stack (): v_ (0), vsize_ (10), vused_ (0) {v_ = new T [vsize_]; // Initial Allocation} Template t stack :: pop () {t result; // if Empty, return default-constructed Tiff (vused_> 0) {result = V _ [- vused_];} Return Result;}

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 // T Must Have Default Ctor and Copy Assignment Class Stack {public: stack (); ~ stack (); stack (const stack&); stack & operator = Const stack &;

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 Stack :: Stack (): v_ (New T [10]), // Default memory allocation vsize_ (10), VUSED_ (0) // has not been used Any memory {// If the program executes, the constructor is successful}

// ----- Copy CTOR ----------------------------------------- -------- Template Stack :: Stack (Const Stack & Other): V_ (0), // Nothing Allocated Or Used Yet vsize_ (Other.vsize_), Vused_ (Other.vused _) {v_ = newcopy (other.v_, other.vsize_, other.vsize_); // If the program executes, the copy constructor is successful}

// ----- Copy Assignment -------------------------------------------------------------------------------------------------- --Template Stack & Stack :: Operator = (Const Stack & Other) {if (this! = & Other) {t * v_new = newcopy (other.v_, osther. vsize_, other.vsize_); // If the program executes, the memory allocation and copy is successful.

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 Stack :: ~ stack () {delete [] v_; // will not throw an exception}

// ----- Count ---------------------------------------------------------------------------------------------------- ----------- Template Unsigned Stack :: count () {return vused_; // This is a native type, not caused by exception} // ----- Push ----------------------------------------------------------------- ---- Template Void Stack :: Push (Const T & T) {if (vused_ == vsize_) // Grow if Necessary {Unsigned vsize_new = (vsize_ 1) * 2; // Grow Factor T * v_new = newcopy (v_, vsize_, vsize_new); file: // If the program executes, memory allocation and copy are successful.

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 t stack :: pop () {t result; if (vused_> 0) {result = v_ [vused_-1]; //// If the copy operation throws an exception, because Vused_ has not decreased, --Vused_; //, so the status has not changed} returnrate;}

//// 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 :: POP (T & Result) ". In this way,

// 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 void stack :: POP (t & result) {r (vused_> 0) {result = v_ [vused_-1] If the copy operation throws an exception, vused_ does not decrease, -vuse_; //, so the status is not changed}} /// The other solution is to return POP to Void and provide a Front () Member function // access stack top element //// ----- Auxiliary function ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------- // We want to copy the memory buffer of the TE element (this buffer may be bigger than the original buffer), // This function will be responsible for allocating new buffers, // Copy all the original elements. If you encounter any exception, this function releases all temporary resources // and transmits an exception, so there will be no leaks. // Template t * newcopy (const t * src, unsigned srcsize, unsigned destsize) {destsize = max (srcsize, destsize); // Basic Parm Check T * DEST = New T [destsize]; // If In this case, memory allocation and various constructors have been successful.

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 T * Newcopy (Const T * SRC, Unsigned Srcsize, Unsigned Destsize) {destsize = max (srcsize, destsize); // Basic Parm Check

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.

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

New Post(0)