C ++ proverbs: exception safety is a bit like pregnancy )...... However, please control this idea for a while. We can't really talk about fertility until we have exclusive to courtship ). (The three words used by the author in this section have dual meanings. Pregnancy can also be understood as rich and meaningful, reproduction can also be understood as reproduction and regeneration, and courtship can also be understood as struggle and seeking. In order to match the subsequent translations, follow the current translation method. -- Translator's note)
Suppose we have a class that represents a GUI menu with a background image. This class is designed to be used in a multi-threaded environment, so it has a mutex used for concurrency control ):
Class prettymenu { Public: ... Void changebackground (STD: istream & imgsrc); // Change Background ... // ImagePRIVATE: Mutex; // mutex for this object Image * bgimage; // current background image Int imagechanges; // # Of times image has been changed }; |
Consider the possible implementation of the changebackground function of the prettymenu:
Void prettymenu: changebackground (STD: istream & imgsrc) { Lock (& mutex); // acquire mutex (as in item 14)Delete bgimage; // get rid of old background ++ Imagechanges; // update image change count Bgimage = new image (imgsrc); // install new background Unlock (& mutex); // release mutex } |
From the perspective of exceptional security, this function is terrible. Exception security has two requirements, but none of them are met here.
When an exception is thrown, the function for exception security should:
·No resource leakage.The above Code did not pass this test, because if the "new image (imgsrc)" expression produces an exception, the unlock call will never be executed, the mutex will also be suspended forever.
·Data structure deterioration not allowed. If "new image (imgsrc)" throws an exception, bgimage is left behind and points to a deleted object. In addition, imagechanges has been added even though a new image has not been set in place. (On the other hand, the old image is clearly deleted, so I expect you to argue that the image has been "changed .)
It is easier to avoid resource leaks. We previously explained how to use objects to manage resources and discussed how to introduce the lock class as a fashionable method to ensure the release of mutex:
Void prettymenu: changebackground (STD: istream & imgsrc) { Lock ml (& mutex); // from Item 14: acquire mutex and // Ensure its later release Delete bgimage; ++ Imagechanges; Bgimage = new image (imgsrc ); } |
One of the best things about resource management such as lock is that they usually shorten functions. Do you see that the unlock call is no longer needed? As a general rule, less code is better code. This is because it can be less likely to go astray and produce less misunderstandings during changes.
As resource leaks leave us behind, we can focus our attention on deteriorating data structures. Here we have a choice, but before we can choose, we must first define our selection terms. The exception security function provides one of the following three guarantees:
· The function provides the basic guarantee, which promises that if an exception is thrown, everything left in the program is legal. No object or data structure is damaged, and all objects are in an internal harmonic state (all class constants are satisfied ). However, the precise state of the program may be unpredictable. For example, we can rewrite changebackground so that if an exception is thrown, The prettymenu object can retain the original background image, or it can hold some Default background images, however, the customer cannot predict which one it is. (To find out this, they probably have to call a member function that tells them what the current background image looks like .)
· The function provides the powerful guarantee (the strong guarantee), promising that the state of the program will not change if an exception is thrown. The calling of such functions is very weak. If they are successful, they will be completely successful. If they fail, the state of the program will be the same as they have never been called.
· Working with a function that provides strong assurance is easier than working with a function that only provides basic assurance, because after calling a function that provides strong assurance, there are only two possible Program states: the function is successfully executed as expected, or the status of the function is maintained when it is called. In comparison, if an exception is thrown by calling a function that only provides the basic guarantee, the program may exist in any legal state.
The function provides a non-throw (the nothrow guarantee) and promises not to throw an exception because they only do what they promise to do. All internal creation operations (such as ints, pointers, and so on) do not throw (nothrow) (that is, the guarantee of not throw ). This is an essential basic component in exceptional security code.
It is reasonable to assume that a function with an empty exception specification does not throw, but this is not necessarily true. For example, consider this function:
Int dosomething () Throw (); // note empty exception spec. |
This does not mean that dosomething will never throw an exception. Instead, if dosomething throws an exception, it is a serious error and should call the unexpected function [1]. In fact, dosomething may not provide any exception guarantee at all. The declaration of a function (if any, including its exception Specification) cannot tell you whether a function is correct, portable, or efficient. Moreover, even if it does, it cannot tell you which exception security guarantee it provides. All these features are determined by the implementation of the function, rather than its declaration.
[1] about the unexpected function, you can turn to your favorite search engine or all-encompassing C ++ textbooks. (You may be lucky to find set_unexpected, which is used to specify the unexpected function .)
The exception security function must provide one of the above three guarantees. If it is not provided, it is not exceptionally secure. Therefore, the choice is to decide which guarantee each function you write must provide. Unless you need to handle the legacy non-exception Security Code (we will discuss this issue later ), only when your most sophisticated demand analysis team identifies a need for your applications to leak resources and run on damaged data structures, exception security guarantee is not provided to become an option.
As a general rule, you should provide the most powerful guarantee that can be achieved. From the perspective of exception security, nothrow functions are excellent, but it is hard to call functions that may throw exceptions outside of C ++ C. Anything that uses dynamic memory allocation (for example, all STL containers) will throw a bad_alloc exception if enough memory cannot be found to satisfy a request. As long as you can do it, it provides no throw guarantee. However, for most functions, the choice is between the basic guarantee and the powerful guarantee.
In the case of changebackground, it is not difficult to provide almost powerful guarantees. First, we change the bgimage data member type of the prettymenu from a built-in image * pointer to one of the SMART Resource Management pointers described in item 13. Frankly speaking, it is a good idea to prevent resource leaks. The fact that it helps us provide robust exceptional security assurance further strengthens this view-using objects (such as smart pointers) to manage resources is the basis of a sound design. In the following code, I show the use of tr1: shared_ptr, because it is more intuitive when performing a normal copy, making it more desirable than auto_ptr.
Second, we rearrange the statements in changebackground so that imagechanges is not added until the image changes. This is a good strategy-until something actually happens, and then changes the state of an object to indicate that something has happened.
This is the modified Code:
Class prettymenu { ... STD: tr1: shared_ptr <image> bgimage; ... };Void prettymenu: changebackground (STD: istream & imgsrc) { Lock ml (& mutex ); Bgimage. Reset (new image (imgsrc); // replace bgimage's internal // Pointer with the result of // "New Image" expression ++ Imagechanges; } |
Note that you do not need to manually delete the old image because it has been processed inside the smart pointer. In addition, a new image is deleted only when it is successfully created. More accurately, this function is called only when the tr1: shared_ptr: reset function parameter ("new image (imgsrc)" result) is successfully created. Delete is used only within the Reset Call. Therefore, if this function never enters, delete is never used. Note that the use of a resource (dynamically allocated image) object (tr1: shared_ptr) shortens the length of changebackground.
As I said, these two changes have almost the ability to enable changebackground to provide robust exception security. What are the shortcomings in the United States? The imgsrc parameter. If the image constructor throws an exception, the read marker of the input stream may have been moved, this movement becomes a state change visible to other parts of the program. Until changebackground solves this problem, it can only provide basic exception security assurance.
In any case, let's put it aside and fake changebackground can provide strong assurance. (I believe you can do this in at least one way, or you can change its parameters from an istream to the file name that contains image data .) There is a general design strategy that can generate a strong guarantee representative, and it is very necessary to be familiar with it. This policy is called "copy and swap ". The principle is simple. First, make a copy of the object you want to change, and then make all the necessary changes on this copy. If some operations during the change throw an exception, the initial object remains unchanged. After all the changes are successful, the changed object and the initial object are exchanged in an operation that does not throw an exception. This is usually achieved through the following method: put all the data in each object from the "real" object into a separate implementation object, then, a pointer pointing to the implemented object is handed over to the real object. This is often called "pimpl idiom", and item 31 describes some of its details. For prettymenu, it is generally like this:
Struct pmimpl {// pmimpl = "prettymenu STD: tr1: shared_ptr <image> bgimage; // impl. "; see below Int imagechanges; // Why It's a struct };Class prettymenu { ... PRIVATE: Mutex; STD: tr1: shared_ptr <pmimpl> pimpl; }; Void prettymenu: changebackground (STD: istream & imgsrc) { Using STD: swap; // see item 25 Lock ml (& mutex); // acquire the mutex STD: tr1: shared_ptr <pmimpl> // copy obj. Data Pnew (New pmimpl (* pimpl )); Pnew-> bgimage. Reset (new image (imgsrc); // modify the copy ++ Pnew-> imagechanges; Swap (pimpl, pnew); // swap the new // Data into place } // Release the mutex |
In this example, I chose to make pmimpl a struct instead of a class, because the prettymenu Data encapsulation can be ensured by making pimpl private. Although it is not so convenient to make pmimpl into a class, it does not add any benefits. (This also makes it impossible for target scammers .) If you want to, pmimpl can be nested inside the prettymenu. packaging problems like this have nothing to do with the issues we are concerned about writing exceptional security code here.
The copy-and-swap policy is an excellent method for completely changing or changing the state of an object. However, in general, it cannot ensure that all functions are strongly abnormal and safe. To find out the cause, consider the abstract embodiment of changebackground-somefunc, which uses copy-and-swap, but contains calls to the other two functions (F1 and F2:
Void somefunc () { ... // Make copy of local state F1 (); F2 (); ... // Swap modified state into place } |
Obviously, if F1 or F2 is less secure than a strong exception, somefunc is hard to become a strong exception. For example, if F1 only provides the basic guarantee. To enable somefunc to provide strong assurance, it must write code to determine the state of the entire program before calling F1, capture all exceptions from F1, and then restore to the initial state.
Even if both F1. If F1 is completed, the state of the program has changed without a doubt, so if F2 throws an exception later, even if F2 does not change anything, the program status is different from that when somefunc is called.
The problem lies in side effects. As long as a function only works for a local State (for example, somefunc only affects the State of the object that calls it), it is relatively easy to provide strong assurance. When the side effects of a function affect non-local data, it is much more difficult. For example, if the side effect of calling F1 is to change the database, it is very difficult to make somefunc powerful and abnormal security. Generally, there is no way to cancel submitted database changes. Other database customers may have seen the new status of the database.
Similar issues will prevent you from providing powerful guarantees for functions, even if you want to do so. Another problem is efficiency. The main point of copy-and-swap is the idea of changing the copy of an object's data and exchanging the changed data with the original data in an operation that does not throw an exception. This requires copying every object to be changed, which may use time and space that you cannot or are reluctant to use. Strong assurance is very worthwhile. You should provide it when it is available, unless it is not 100% available.
When it is unavailable, you must provide basic guarantees. In practice, you may find that you can provide powerful guarantees for some functions, but the cost of efficiency and complexity makes it difficult to support a large number of other functions. At any time, as long as you have made a reasonable result that provides a strong guarantee, no one will criticize you for providing only the basic guarantee. For many functions, the basic guarantee is a reasonable choice.
If you write a function that does not provide exception security guarantee at all, things are different, because the assumption of guilt is reasonable until you prove that you are innocent. You should write out exceptional security code. Unless you can make a convincing answer. Consider the implementation of somefunc again. It calls the F1 and F2 functions. Assume that F2 does not provide exception security or even basic security. This means that if an exception occurs in F2, the program may leak resources in F2. This also means that F2 may deteriorate the data structure. For example, sorted arrays may not be sorted, and objects that are being transferred from one data structure to another may be lost. There is no way for somefunc to fix these problems. If the function called by somefunc does not provide exception security guarantee, somefunc itself cannot provide any guarantee.
Please allow me back to the subject of pregnancy. A female or pregnant or absent. Local pregnancy is impossible. Similarly, a software is either exceptionally secure or not. There is no such thing as a local exception security system. Even if a system has only one function that is not exceptionally secure, the system as a whole is not exceptionally secure, because calling that function may leak resources and deteriorate the data structure. Unfortunately, many legacy code in C ++ do not pay attention to exception security when writing, so many systems are not so secure. They are a mix of code written in a non-exception-unsafe manner.
There is no reason to keep things in this state forever. When writing new code or changing existing code, you should carefully consider how to make it exceptionally safe. To use objects to manage resources. This prevents resource leakage. Next, decide which of the three exception security guarantees is the strongest guarantee that you can actually provide for every function you write. Only when you do not call the legacy code will you have no choice, in order to meet the non-guarantee. Document your decisions for your function customers and future maintenance personnel. The exception security guarantee of a function is the visible part of its interface, so you should select it specifically, just as you choose other aspects of a function interface.
Forty years ago, Goto code was everywhere regarded as a best practice. Now we are struggling to write a structured control process. 20 years ago, globally accessible data was honored as a best practice. Now we strive to encapsulate data. Ten years ago, when writing a function, we do not have to consider the impact of exceptions as the best practice. Now we are struggling to write exceptional security code.
Time is passing. We live. We are learning.
Things to remember
· Even if an exception is thrown, functions with exceptional security will not leak resources and the data structure cannot be deteriorated. Such functions provide basic, powerful, or do not throw a guarantee.
· Strong assurance is often implemented through copy-and-swap, but strong assurance is not available to all functions.
· The guarantee provided by a function is not weaker than that of the function called by a function.
Apsara stack traffic Alliance for freeI want to pin it down I want to pick up the wrongCopy links from favorites to favorites and send them to friends to add to favorites. Print them off.
Comments0 comments in total
Welcome to comments!
Post commentsYou can also enter 300 words. Congratulations, the information is submitted successfully!
Related search:
How to Write c ++ code with exceptional security
Add to favorites:
Release date: 15:25:28 features. Fortunately, with the accumulation of C ++ Community experience, we have enough knowledge to easily write exceptional security code today, in addition, writing exceptional and Secure Code generally does not affect performance.
Is the error code returned? This is an endless topic. You must have heard that an exception is used only when an exception occurs. So what is "real exception "? Before answering this question, let's take a look at the variant principle in programming.
An object is an attribute aggregation method. How can we determine whether the attribute aggregation of an object is in a logically correct state? Through a series of assertions, we can conclude that the attribute aggregation logic of this object is correct or problematic. These assertions are non-variant measures of object attribute aggregation.
We usually perform a non-variant check in function calls. There are three types of non-variant conditions: pre-condition, post-condition, and non-variant. A prerequisite is a logical condition that must be met before a function is called. A prerequisite is a logical condition that must be met after a function is called. A non-variant condition is a condition that must be met during function execution. In our discussion, the non-variant is both a prerequisite and a post condition. The preceding conditions must be met. If the preceding conditions are not met, the program logic is incorrect, and the latter conditions are not necessarily met. Now, we can strictly define the exception condition using the non-variant formula: the condition is met, but the condition cannot be met, that is, the exception condition. An exception is thrown only when an exception occurs.
In the response to when an exception is thrown, the returned value is not excluded and the two are orthogonal. However, from our experience, we can choose between the two. Why? In fact, when we make this kind of choice, it will inevitably mean that the interface semantics changes. without changing the interface, it is actually impossible to choose (give it a try, use the return value to handle errors in the constructor ). The normal and abnormal conditions are differentiated by the non-variant mode, and the interface can be further refined.
The evaluation of exceptional security can be divided into three levels: basic assurance, strong assurance, and no failure.
Basic Guarantee: Make sure that the Program (object) is in an unknown but valid State when an exception occurs. Effective means that all the unchanged object checks pass.
Strong guarantee: ensure that the operation is transactional, either successful, the program is in the target State, or does not change.
No failure: This is hard to guarantee for most functions. For C ++ programs, at least the destructor, release function, and swap function must ensure that they do not fail. This is the basis for writing exceptional security code.
First, we start with resource management issues in exceptional circumstances. Many people may have done this:
Type * OBJ = new type;
Try {do_something ...}
Catch (...) {Delete OBJ; throw ;}
Don't do this! This will only make your code look messy and reduce efficiency. This is also one of the reasons for the long-standing reputation for exceptions. Please use raiI technology to do this:
Auto_ptr obj_ptr (new type );
Do_something...
This code is concise, secure, and cost-effective. When you do not care about or cannot handle exceptions, do not try to catch them. You do not need to use try... catch to write exception-safe code. Most of the exception-safe code does not need try... catch. I admit that the real world is not always as simple as the above example, but this example can indeed represent a lot of abnormal security code practices. In this example, boost: scoped_ptr is a more suitable alternative to auto_ptr.
Let's consider this constructor:
Type (): M_a (New typea), m_ B (New typeB ){}
Assume that the member variables M_a and m_ B are of the original pointer type and are consistent with the declarative sequence in the type. Such a code is insecure, and there is a resource leakage problem. The failure rollback mechanism of the constructor cannot cope with this problem. If the new typeB throws an exception, the resources returned by the new typea cannot be released. In the past, many people used this method to avoid exceptions:
Type (): M_a (null), m_ B (null ){
Auto_ptr tmp_a (New typea );
Auto_ptr tmp_ B (New typeB );
M_a = tmp_a.release ();
M_ B = tmp_ B .release ();
}
Of course, such a method can indeed implement code with exceptional security, and the implementation idea is very important. This idea will be used in how to implement the exception security code with strong guarantee. however, this approach is not thorough enough. At least the Destructor should be done manually. We can still use raiI technology to make it more thorough: shared_ptr M_a; shared_ptr m_ B; in this way, we can easily write exceptional security code:
Type (): M_a (New typea), m_ B (New typeB ){}
If you think the performance of shared_ptr cannot meet the requirements, you can write an intelligent pointer class with an interface similar to scoped_ptr and release resources in the destructor. If the class is designed to be non-reproducible, scoped_ptr can also be used directly. We strongly recommend that you do not use auto_ptr as a data member. Although scoped_ptr is not a good name, it is at least safe and will not cause confusion.
RaiI technology is not only used in the above example. All operations that must appear in pairs can be completed through this technology without the need to try... catch. The following code is also common:
A_lock.lock ();
Try {...} catch (...) {a_lock.unlock (); throw ;}
A_lock.unlock ();
We can solve this problem by providing a pair of helper classes:
Struct scoped_lock {
Explicit scoped_lock (lock): M_L (LOCK) {m_l.lock ();}
~ Scoped_lock () {m_l.unlock ();}
PRIVATE:
Lock M_L;
};
Then, write the code as follows:
Scoped_lock guard (a_lock );
Do_something...
Clear and elegant! In this example, we do not need to operate in pairs. Obviously, the problem can be solved by modifying the scoped_lock constructor. However, method names and parameters are often not so fixed. What should I do? You can use this helper class:
Template
Struct pair_guard {
Pair_guard (fend Fe, fbegin FB): m_fe (FE) {If (FB) FB ();}
~ Pair_guard () {m_fe ();}
PRIVATE:
Fend m_fe;
... // Prohibit Replication
};
Typedef pair_guard, function> simple_pair_guard;
Okay. With the boost library, we can write the code like this:
Simple_pair_guard guard (BIND (LOCK: Unlock, a_lock), BIND (LOCK: Lock, a_lock ));
Do_something...
I admit that such code is not as concise and easy to understand as it is before, but it is more flexible and can be used for pairing no matter what the function name is. We can enhance the use of BIND, combined with the placeholder character and reference_wrapper, to process function parameters and dynamically bind variables. All the same work inside and outside the catch can be done with pair_guard.