⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 sutter.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 4 页
字号:
    delete[] v_;                    // this can't throw    v_ = v_new;                      // take ownership    vsize_ = vsize_new;  }  v_[vused_] = t;  ++vused_;}If we have no more space, we first pick a new size for the buffer and make a larger copy using NewCopy. Again, if NewCopy throws then our own Stack's state is unchanged and the exception propagates through cleanly. Deleting the original buffer and taking ownership of the new one involves only operations that are known not to throw, so the entire if block is exception-safe.After any required grow operation, we attempt to copy the new value before incrementing our vused_ count. This way, if the assignment throws, the increment is not performed and our Stack's state is unchanged. If the assignment succeeds, the Stack's state is changed to recognize the presence of the new value, and all is well.Only one function left... that wasn't so hard, was it? Well, don't get too happy just yet, because it turns out that Pop is the most problematic of these functions to write with complete exception safety. Our initial attempt might look something like this: template<class T>T Stack<T>::Pop() {  if( vused_ == 0) {    throw "pop from empty stack";  } else {    T result = v_[vused_-1];    --vused_;    return result;  }}If the stack is empty, we throw an appropriate exception. Otherwise, we create a copy of the T object to be returned, update our state, and return the T object. If the initial copy from v_[vused_-1] fails, the exception is propagated and the state of the Stack is unchanged, which is what we want. If the initial copy succeeds, our state is updated and the Stack is in its new consistent state, which is also what we want.So this works, right? Well, kind of. There is a subtle flaw here that's completely outside the purview of Stack::Pop. Consider the following client code: int i(s.Pop());int j;j = s.Pop();Note that above we talked about "the initial copy" (from v_[vused_-1]). That's because there is another copy to worry about in either of the above cases, namely the copy of the returned temporary into the destination. (For you experienced readers, yes, it's actually "zero or one copies" because the compiler is free to optimize away the second copy if the return value optimization applies (see Item M20). The point is that there can be a copy, so you have to be ready for it.) If that copy construction or copy assignment fails, then the Stack has completed its side effect (the top element has been popped off) but the popped value is now lost forever because it never reached its destination (oops). This is bad news. In effect, it means that any version of Pop that is written to return a temporary like this cannot be made completely exception-safe, because even though the function's implementation itself may look technically exception-safe, it forces clients of Stack to write exception-unsafe code. More generally, mutator functions should not return T objects by value.The bottom line and it's significant is this: Exception safety affects your class's design! In other words, you must design for exception safety from the outset, and exception safety is never "just an implementation detail." One alternative is to respecify Pop as follows: template<class T>void Stack<T>::Pop( Tresult ) {  if( vused_ == 0) {    throw "pop from empty stack";  } else {    result = v_[vused_-1];    --vused_;  }}A potentially tempting alternative is to simply change the original version to return T instead of T (this would be a reference to the popped T object, since for the time being the popped object happens to still physically exist in your internal representation) and then the caller could still write exception-safe code. But this business of returning references to "I no longer consider it there" resources is just purely evil (see Item E23). If you change your implementation in the future, this may no longer be possible! Don't go there.The modified Pop ensures that the Stack's state is not changed unless the copy safely arrives in the caller's hands. Another option (and preferable, in my opinion) is to separate the functions of "querying the topmost value" and "popping the topmost value off the stack." We do this by having one function for each: template<class T>TStack<T>::Top() {  if( vused_ == 0) {    throw "empty stack";  }  return v_[vused_-1];}template<class T>void Stack<T>::Pop() {  if( vused_ == 0) {    throw "pop from empty stack";  } else {    --vused_;  }}Incidentally, have you ever grumbled at the way the standard library containers' (see Item E49) pop functions (e.g., list::pop_back, stack::pop, etc.) don't return the popped value? Well, here's one reason to do this: It avoids weakening exception safety. In fact, you've probably noticed that the above separated Top and Pop now match the signatures of the top and pop members of the standard library's stack<> adapter. That's no coincidence! We're actually only two public member functions away from the stack<> adapter's full public interface, namely: template<class T>const TStack<T>::Top() const {  if( vused_ == 0) {    throw "empty stack";  } else {    return v_[vused_-1];  }}to provide Top for const Stack objects, and: template<class T>  bool Stack<T>::Empty() const {  return( vused_ == 0 );}Of course, the standard stack<> is actually a container adapter that's implemented in terms of another container, but the public interface is the same and the rest is just an implementation detail.Levels of Safety: The Basic and Strong GuaranteesJust as there's more than one way to skin a cat (somehow I have a feeling I'm going to get enraged email from animal lovers), there's more than one way to write exception-safe code. In fact, there are two main alternatives we can choose from when it comes to guaranteeing exception safety. These guarantees were first set out in this form by Dave Abrahams: Basic Guarantee: Even in the presence of T or other exceptions, Stack objects don't leak resources. Note that this also implies that the container will be destructible and usable even if an exception is thrown while performing some container operation. However, if an exception is thrown, the container will be in a consistent but not necessarily predictable state. Containers that support the basic guarantee can work safely in some settings. (This is similar to every Stack member function leaving the object in what Jack Reeves terms a good but never a bad or an undefined state. For details, consult Reeves' article, Coping with Exceptions.)Strong Guarantee: If an operation terminates because of an exception, program state will remain unchanged. This always implies commit-or-rollback semantics, including that no references or iterators into the container be invalidated if an operation fails. For example, if a Stack client calls Top and then attempts a Push which fails because of an exception, then the state of the Stack object must be unchanged, and the reference returned from the prior call to Top must still be valid. For more information on these guarantees, see Dave Abrahams' documentation of the SGI exception-safe standard library adaptation.Probably the most interesting point here is that when you implement the basic guarantee, the strong guarantee often comes along for free. (Note that I said "often," not "always." In the standard library, for example, vector is a well-known counterexample where satisfying the basic guarantee does not cause the strong guarantee to come along for free.) For example, in our Stack implementation, almost everything we did was needed to satisfy just the basic guarantee... and what's presented above very nearly satisfies the strong guarantee, with little or no extra work. Not half bad, considering all the trouble we went to.(There is one subtle way in which this version of Stack still falls short of the strong guarantee: If Push() is called and has to grow its internal buffer, but then its final v_[vused_] = t; assignment throws, the Stack is still in a consistent state and all, but its internal memory buffer has moved which invalidates any previously valid references returned from Top(). This last flaw in Stack::Push() can be fixed fairly easily by moving some code and adding a try block. For a better solution, however, see the Stack presented below that Stack does not have this problem, and it does satisfy the strong commit-or-rollback guarantee.)Points to PonderNote that we've been able to implement Stack to be not only exception-safe but fully exception-neutral, yet we've used only a single try/catch. As we'll see below, using better encapsulation techniques can get rid of even this try block. That means we can write a fully exception-safe and exception-neutral generic container without using try or catch... which really is pretty cool.As originally defined, Stack requires its instantiation type to have a: default constructor (to construct the v_ buffers) copy constructor (if Pop returns by value) nonthrowing destructor (to be able to guarantee exception safety) exception-safe copy assignment (to set the values in v_, and if the copy assignment throws then it must guarantee that the target object is unchanged; note that this is the only T member function which must be exception-safe in order for our Stack to be exception-safe)Next, we'll see how to reduce even these requirements without compromising exception safety, and along the way we'll get an even more detailed look at the standard operation of the statement delete[] x;.Delving DeeperNow I'll delve a little deeper into the Stack example, and write not just one but two new-and-improved versions of the template. Not only is it possible to write exception-safe generic containers, but between the last approach and this one I'll have demonstrated no less than three different complete solutions to the exception-safe Stack problem.Along the way, I'll also answer several more interesting questions: How can we use more advanced techniques to simplify the way we manage resources, and get rid of the last try/catch into the bargain? How can we improve Stack by reducing the requirements on T, the contained type? Should generic containers use exception specifications? What do new[] and delete[] really do?The answer to the last may be quite different than you expect. Writing exception-safe containers in C++ isn't rocket science; it just requires significant care and a good understanding of how the language works. In particular, it helps to develop a habit of eyeing with mild suspicion anything that might turn out to be a function call including user-defined operators, user-defined conversions (see Item M5), and silent temporary objects (see Item M19) among the more subtle culprits because any function call might throw (except for functions declared with an exception specification of throw(), or certain functions in the standard library that are documented to never throw).An Improved StackOne way to greatly simplify an exception-safe container like Stack is to use better encapsulation. Specifically, we'd like to encapsulate the basic memory management work. Most of the care we had to take while writing our original exception-safe Stack was needed just to get the basic memory allocation right, so let's introduce a simple helper class to put all of that work in one place: template <class T> class StackImpl {/*????*/:  StackImpl(size_t size=0)  : v_( static_cast<T*>               // see Item M2 for info on static_cast    ( size == 0    ? 0    : ::operator new(sizeof(T)*size) ) ),    vsize_(size),    vused_(0)  { }  ~StackImpl() {    destroy( v_, v_+vused_ );         // this can't throw    ::operator delete( v_ );  }  void Swap(StackImplother) throw() {    swap(v_, other.v_);    swap(vsize_, other.vsize_);    swap(vused_, other.vused_);  }  T*     v_;                          // ptr to a memory area big  size_t vsize_;                      //  enough for `vsize_' T's  size_t vused_;                      // # of T's actually in use};There's nothing magical going on here: StackImpl is responsible for simple raw memory management and final cleanup, so any class that uses it won't have to worry about those details. We won't spend much time analyzing why this class is fully exception-safe (works properly in the presence of exceptions) and exception- neutral (propagates all exceptions to the caller), because the reasons are pretty much the same as those we dissected in detail above.Note that StackImpl has all of the original Stack's data members, so that we've essentially moved the original Stack's representation entirely into StackImpl. StackImpl also has a helper function named Swap, which exchanges the guts of our StackImpl object with those of another StackImpl.

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -