Heavy rain.
The dark clouds are pressed down as low as the lead blocks, and the glass windows of the big raindrops burst. The rare abnormal weather is trying to show its annoying side. However, it seems that solmyr is not affected. He is still half-lying in a big chair in his comfortable posture, holding a cup of hot juice in his hand, behind him, what is zero hitting on the keyboard.
"Alas, how does the stack in the standard library look like this? The design is terrible ." Zero stops working and turns around to face solmyr. It looks a bit confusing.
"The Members who arbitrarily criticize the inclusion of the sacred standards will be sent by heaven ." Solmyr bowed his head and replied in a fortune teller tone.
I don't know if the sky is going to strengthen solmyr's persuasiveness. At this time, the sky is shining through lightning, and the blue-white electro-optic light is struggling to throw on the ground, then there is a bang of "Karla"-This ray is very close.
A second ago, the zero expression that was still thinking "This is too difficult and too difficult" suddenly became odd, and it took a long time to recover to normal. He marked two lines of code and said, "Okay, well, solmyr. Please explain why the stack interface looks like this ."
STD: Stack <int> Si;
......
Int I = Si. Top ();
Si. Pop ();
"As long as pop () returns the top element of the stack, the above two rows can be merged into one row, which is more intuitive. Why is it like this ?"
Solmyr, which witnessed zero expression changes, was forced to endure the impulse to laugh. The old man knew how hard he had endured. He slowly put the cup on the table and turned around to explain the problem:
"The reason is an exception ."
"Exception ?"
"Yes, a lot of code works very well when there is no exception, but once an exception occurs, it becomes unmanageable, just like a thatch house, it usually looks okay, today's weather ...... ", Solmyr refers to the window,"... It will crash immediately. Consider how to implement the elements at the top of the stack if Pop () is returned. Suppose the stack is implemented using arrays internally, and whether the stack is empty is not considered ."
"It's easy .", Zero opened the editor and wrote down:
Template <typename T>
T stack <t>: Pop ()
{
......
Return data [top --]; // assume that the data is stored in the array data, and top indicates the top position of the stack.
}
Solmyr shook his head: "This is the thatch house. You must know that stack is a template class, and the elements t it stores may be user-defined classes. Let me ask you, what will happen if the copy constructor of type T throws an exception ?"
"Well ...... Return by value. The returned value is a temporary object, which is constructed by copying data [Top ...... In this way, an exception may be thrown when the function returns, and the customer cannot obtain this element at this time ."
"What else ?"
"And ?"
"What happened to your top ?"
"…… Oh! Bad! At this time, the top element of the stack is lost! In this case ...... A function must be implemented to allow customers to modify ......", Zero cannot be said. After thinking for a while, he shook his head and admitted the failure: "No, here the copy structure occurs after the function is returned, which cannot be avoided in any case. It can only be stated in the document: the copy constructor that requires t does not throw an exception ." Zero stopped, and cautiously asked solmyr: "Isn't this an excessive requirement ?"
Solmyr's answer is short: "new"
"Oh yes, new will throw STD: bad_alloc ...... I did not say so. Solmyr, I understand that to handle exceptions, adjusting the stack top position must be done after all data copies are completed, so return by value is unacceptable ."
"Correct. Therefore, it is unacceptable for a standard library member whose design goal is to maximize reusability ." Solmyr paused and continued: "and the impact of exceptions is far greater than that. I just said, 'Suppose the stack is implemented using arrays internally. 'But if you fully consider the possibility of throwing an exception, you will find that array implementation is a bad idea ."
"…… ...... ...... ...... ...... Why? When no value is returned, we can always catch and handle exceptions ?", Zero cautiously asks questions.
Solmyr looked at zero with approval. "You should think on your own before asking questions. It's a good habit .", Solmyr thought, but his face was not shown at all: "Yes, but capturing exceptions doesn't mean you can always handle it correctly. Consider the stack assignment operator. If we use Arrays for implementation, there will certainly be a loop similar to this when copying data :"
// The meanings of each variable are the same as those above
Template <typename T>
Stack <t> & stack <t>: Operator = (const stack <t> & RHs)
{
......
For (INT I = 0; I <RHS. Top; I ++)
Data [I] = RHS. Data [I];
......
}
"Now, considering that the assignment operator of type T may throw an exception, how can we modify the above code ." Solmyr stopped and held the cup again.
"Try ...... Oh ...... ...... ...... ...... ...... ......", Zero seems to have discovered the problem. After a long silence, it went on to say, "This loop may throw an exception when it is halfway through. This will cause some data to be successfully assigned values, the other part is still old. Unless we catch all exceptions with catch (...), ignore them and assign values ."
"......", Solmyr consciously guides zero to continue to think deeply.
"…… But in this way, the exception thrown by the value assignment operator is 'taobao', and the exception always indicates that something should not happen, so the customer should receive this exception ." Zero frowned and looked very hard.
"Correct. As a common standard library Member, stack must implement two points in the face of exceptions. 1. Exceptional security, that is, exceptions do not cause itself to be in a wrong state, data loss, or resource leakage. 2. Exceptions are transparent, that is to say, the customer code -- here refers to the implementation of the type T it stores -- any exception thrown should not be 'taobao' or changed, and should be transparently transmitted to the customer. As soon as possible, the above Code is unlikely to achieve both of these two points at the same time ."
"Well, I understand. This is probably the main reason why the stack in the standard library does not use arrays." Zero showed a very confident look.
"Of course not! It's a bit common sense. If array is used, the stack size is fixed. How can this be accepted ?!"
Again, solmyr witnessed a dramatic change in Zero's expression that was unspeakable. This time he failed to hold back the urge to laugh, and even the juice in the cup spilled out. For a while, laughter filled the whole office-not just him, it also includes (Should I guess the audience ?) Watching the laughter of colleagues.
After the onlookers were dispelled, zero sat down with a dark color: "Is it so funny ?"
"Sorry, sorry, I ...... Haha ...... I ...... Haha ...... I just couldn't help it ...... Hahahaha ...... "Solmyr easily calmed down and laughed, sat down straight, put down the juice, and said:" The key lies in the two principles introduced above, that is, abnormal security, and exception transparency. Now, if the data in the stack is stored as pointers, how can we ensure the above two points in the value assignment operator ?"
"…… Hmm ...... There will still be a loop like above ...... Er ...... ", Zero faces are difficult.
"NOTE: you do not have to copy the data directly to the memory where the stack stores the data ."
"…… Hmm ...... If it is not directly copied, it is ...... Is to copy ...... Ah! I understand !", Zero grasped the key and quickly wrote down:
// Pdata indicates the pointer pointing to the memory for storing data, and top indicates the offset of the element at the top of the stack.
Template <typename T>
Stack <t> & stack <t>: Operator = (const stack <t> & RHs)
{
......
T * ptemp = new T [RHS. Top];
Try
{
For (INT I = 0; I <RHS. Top; I ++)
* (Ptemp + I) = * (RHS. pdata + I );
}
Catch (...) // catch possible exceptions
{
Delete [] ptemp;
Throw; // throw again
}
Delete [] pdata; // release the current Memory
Pdata = ptemp; // point pdata to a successfully assigned memory block
......
}
"As long as this is the case," zero inputs and says, "You only need to copy the data to a temporarily allocated buffer, and handle exceptions in this process, then let pdata point to the successfully allocated memory. The key here is to make the copy action ...... Er ...... It can be safely canceled. The remaining value assignment is a simple pointer assignment, and no exception will be thrown ."
"Very good. It is worth noting that this is a very common method. Its name is copy & swap. It can not only be used to cope with exceptions, but also effectively implement some other features. OK, this is probably the case ."
The problem seems to have come to an end. solmyr is planning to end this topic. Zero's confused expression stops him.
"What's the problem? Zero ?"
"Ah ...... Nothing. I was just thinking that exceptions caused so much trouble. This time, there was another thread deadlock problem (see the previous article in the "small text series", "appeared in pairs ") the exception is complicated. Why does C ++ support it? You can report errors in the returned values ."
"Well, this is indeed a common doubt, but the answer is also very simple. The existence of exceptions has its own value. I. Using exception reporting errors can avoid contamination of the function interface; II. If you want to report a wealth of error information, using an exception object is much more effective than simply returning a value, it also avoids the overhead caused by returning complex objects. 3. I think it is important to report some errors that do not apply to returned values. For example, dynamic memory allocation. How do I report dynamic memory allocation errors in C language ?", Solmyr looked at zero.
"The malloc function returns a null value that indicates an error in dynamic memory allocation ."
"But how many c programmers have seen checking the return value every time they use malloc ?"
"…… "
"No, right? This is normal. It is painful to check the return value after each use of malloc, so even if there is Steve Maguire (Note: Author of writing clean code) there are still tens of thousands of C Programs in which such an old programmer is eager to teach and give his or her ears: ", solmyr easily typed:
/* Traditional C Programs */
Int * P = malloc (sizeof (INT ));
* P = 10;
"Once malloc fails, return NULL, and the program will crash. However, if it is a C ++ program, use new ...... ", Solmyr typed the corresponding code:
// C ++ Program
Int * P = new int;
* P = 10;
"There is no such problem. I ask you, why ?"
Zero quickly found the answer: "If new fails, it will throw the STD: bad_alloc exception, so the function is interrupted and exited, and the following line will not be called ."
"Correct. And you don't have to handle this exception in every place. As long as your program is transparent to the exception, you can write a try in the main function... catch pair to catch all uncaptured exceptions. For example, you can capture STD: bad_alloc in the main function, output the 'memory insufficiency 'error message, save all unsaved data, complete all cleanup, and end the program. In a word, exit in a decent way ."
Zero nodded his head and muttered: "Yes, decent exit ."
According to Zero's understanding of his meaning, solmyr continues to start the next topic: "the existence of exceptions has the last important value-one of the original intentions of the original design-to provide a common means for the constructor to easily report errors: Because the constructor does not return values."
"There are also destructor ." After solmyr is finished, zero adds this sentence.
Solmyr shook his head at the clever zero: "Don't take it for granted. There is a very important principle about exceptions: Never let your destructor throw an exception. Do you know why ?"
"…… I don't know ." Zero decided to honestly admit this time.
"Throwing an exception destructor will cause the simplest program to run incorrectly, for example, the following two sentences:" this time on the screen, it seems that there are no flaws in the two lines of code:
Edevil P = new edevil [10];
Delete [] P;
"Doesn't it look like a problem at all, does it? After carefully analyzing the Delete [] P statement, it will call the destructor of the edevil class for 10 times. Assume that the destructor of the edevil class throw an exception for 5th times, what will happen?"
Zero is immersed in meditation, staring at the screen, and looks like a program that executes complex operations without output. However, it wasn't long before zero changed an expression, which is often described as "I know solmyr. In this case, delete [] faces a dilemma. Choose not to capture this exception and let it spread to the caller, but then delete [] is interrupted, and the memory occupied by the next five edrop objects will not be released, cause resource leakage. The second option is to capture this exception to prevent resource leakage, but this exception is eaten by Delete [], which violates the principle of 'transparent to exception. Therefore, no matter how you do it, you cannot properly handle exceptions thrown by the destructor ."
Solmyr nodded with approval: "Very good. Next, your task is ......"
"I know, I know. Can I organize these discussions into documents? ."
Zero turned around and began to bury himself in his documents. Solmyr resumes its comfortable half-lying half-sitting posture, holds his juice, and slightly discovers something unexpected ---
The weather is fine.
============================================
This small article provides a lot of reference to the following two articles:
"Exception Handling: a false sense of security" by Tom Cargill
Exception-safe generic containers by Herb Sutter