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

📄 mc3.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 5 页
字号:
 More Effective C++ | Chapter 3: Exceptions Back to Item 8: Understand the different meanings of new and deleteContinue to Item 9: Use destructors to prevent resource leaks.ExceptionsThe addition of exceptions to C++ changes things. Profoundly. Radically. Possibly uncomfortably. The use of raw, unadorned pointers, for example, becomes risky. Opportunities for resource leaks increase in number. It becomes more difficult to write constructors and destructors that behave the way we want them to. Special care must be taken to prevent program execution from abruptly halting. Executables and libraries typically increase in size and decrease in speed.And these are just the things we know. There is much the C++ community does not know about writing programs using exceptions, including, for the most part, how to do it correctly. There is as yet no agreement on a body of techniques that, when applied routinely, leads to software that behaves predictably and reliably when exceptions are thrown. (For insight into some of the issues involved, see the article by Tom Cargill. For information on recent progress in dealing with these issues, see the articles by Jack Reeves and Herb Sutter.)We do know this much: programs that behave well in the presence of exceptions do so because they were designed to, not because they happen to. Exception-safe programs are not created by accident. The chances of a program behaving well in the presence of exceptions when it was not designed for exceptions are about the same as the chances of a program behaving well in the presence of multiple threads of control when it was not designed for multi-threaded execution: about zero.That being the case, why use exceptions? Error codes have sufficed for C programmers ever since C was invented, so why mess with exceptions, especially if they're as problematic as I say? The answer is simple: exceptions cannot be ignored. If a function signals an exceptional condition by setting a status variable or returning an error code, there is no way to guarantee the function's caller will check the variable or examine the code. As a result, execution may continue long past the point where the condition was encountered. If the function signals the condition by throwing an exception, however, and that exception is not caught, program execution immediately ceases.This is behavior that C programmers can approach only by using setjmp and longjmp. But longjmp exhibits a serious deficiency when used with C++: it fails to call destructors for local objects when it adjusts the stack. Most C++ programs depend on such destructors being called, so setjmp and longjmp make a poor substitute for true exceptions. If you need a way of signaling exceptional conditions that cannot be ignored, and if you must ensure that local destructors are called when searching the stack for code that can handle exceptional conditions, you need C++ exceptions. It's as simple as that.Because we have much to learn about programming with exceptions, the Items that follow comprise an incomplete guide to writing exception-safe software. Nevertheless, they introduce important considerations for anyone using exceptions in C++. By heeding the guidance in the material below (and in the magazine articles on this CD), you'll improve the correctness, robustness, and efficiency of the software you write, and you'll sidestep many problems that commonly arise when working with exceptions. Back to ExceptionsContinue to Item 10: Prevent resource leaks in constructorsItem 9: Use destructors to prevent resource leaks.Say good-bye to pointers. Admit it: you never really liked them that much anyway.Okay, you don't have to say good-bye to all pointers, but you do need to say sayonara to pointers that are used to manipulate local resources. Suppose, for example, you're writing software at the Shelter for Adorable Little Animals, an organization that finds homes for puppies and kittens. Each day the shelter creates a file containing information on the adoptions it arranged that day, and your job is to write a program to read these files and do the appropriate processing for each adoption.A reasonable approach to this task is to define an abstract base class, ALA ("Adorable Little Animal"), plus concrete derived classes for puppies and kittens. A virtual function, processAdoption, handles the necessary species-specific processing: class ALA {public:  virtual void processAdoption() = 0;  ...};class Puppy: public ALA {public:  virtual void processAdoption();  ...};class Kitten: public ALA {public:  virtual void processAdoption();  ...};You'll need a function that can read information from a file and produce either a Puppy object or a Kitten object, depending on the information in the file. This is a perfect job for a virtual constructor, a kind of function described in Item 25. For our purposes here, the function's declaration is all we need: // read animal information from s, then return a pointer// to a newly allocated object of the appropriate typeALA * readALA(istream& s);The heart of your program is likely to be a function that looks something like this: void processAdoptions(istream& dataSource){  while (dataSource) {                  // while there's data    ALA *pa = readALA(dataSource);      // get next animal    pa->processAdoption();              // process adoption    delete pa;                          // delete object that  }                                     // readALA returned}This function loops through the information in dataSource, processing each entry as it goes. The only mildly tricky thing is the need to remember to delete pa at the end of each iteration. This is necessary because readALA creates a new heap object each time it's called. Without the call to delete, the loop would contain a resource leak.Now consider what would happen if pa->processAdoption threw an exception. processAdoptions fails to catch exceptions, so the exception would propagate to processAdoptions's caller. In doing so, all statements in processAdoptions after the call to pa->processAdoption would be skipped, and that means pa would never be deleted. As a result, anytime pa->processAdoption throws an exception, processAdoptions contains a resource leak.Plugging the leak is easy enough, void processAdoptions(istream& dataSource){  while (dataSource) {    ALA *pa = readALA(dataSource);  try {      pa->processAdoption();  }  catch (...) {              // catch all exceptions    delete pa;               // avoid resource leak when an                             // exception is thrown    throw;                   // propagate exception to caller  }  delete pa;                 // avoid resource leak when no }                           // exception is thrown}but then you have to litter your code with try and catch blocks. More importantly, you are forced to duplicate cleanup code that is common to both normal and exceptional paths of control. In this case, the call to delete must be duplicated. Like all replicated code, this is annoying to write and difficult to maintain, but it also feels wrong. Regardless of whether we leave processAdoptions by a normal return or by throwing an exception, we need to delete pa, so why should we have to say that in more than one place?We don't have to if we can somehow move the cleanup code that must always be executed into the destructor for an object local to processAdoptions. That's because local objects are always destroyed when leaving a function, regardless of how that function is exited. (The only exception to this rule is when you call longjmp, and this shortcoming of longjmp is the primary reason why C++ has support for exceptions in the first place.) Our real concern, then, is moving the delete from processAdoptions into a destructor for an object local to processAdoptions.The solution is to replace the pointer pa with an object that acts like a pointer. That way, when the pointer-like object is (automatically) destroyed, we can have its destructor call delete. Objects that act like pointers, but do more, are called smart pointers, and, as Item 28 explains, you can make pointer-like objects very smart indeed. In this case, we don't need a particularly brainy pointer, we just need a pointer-like object that knows enough to delete what it points to when the pointer-like object goes out of scope.It's not difficult to write a class for such objects, but we don't need to. The standard C++ library (see Item E49) contains a class template called auto_ptr that does just what we want. Each auto_ptr class takes a pointer to a heap object in its constructor and deletes that object in its destructor. Boiled down to these essential functions, auto_ptr looks like this: template<class T>class auto_ptr {public:  auto_ptr(T *p = 0): ptr(p) {}        // save ptr to object  ~auto_ptr() { delete ptr; }          // delete ptr to objectprivate:  T *ptr;                              // raw ptr to object};The standard version of auto_ptr is much fancier, and this stripped-down implementation isn't suitable for real use3 (we must add at least the copy constructor, assignment operator, and pointer-emulating functions discussed in Item 28), but the concept behind it should be clear: use auto_ptr objects instead of raw pointers, and you won't have to worry about heap objects not being deleted, not even when exceptions are thrown. (Because the auto_ptr destructor uses the single-object form of delete, auto_ptr is not suitable for use with pointers to arrays of objects. If you'd like an auto_ptr-like template for arrays, you'll have to write your own. In such cases, however, it's often a better design decision to use a vector instead of an array, anyway.)Using an auto_ptr object instead of a raw pointer, processAdoptions looks like this: void processAdoptions(istream& dataSource){  while (dataSource) {    auto_ptr<ALA> pa(readALA(dataSource));    pa->processAdoption();  }}This version of processAdoptions differs from the original in only two ways. First, pa is declared to be an auto_ptr<ALA> object, not a raw ALA* pointer. Second, there is no delete statement at the end of the loop. That's it. Everything else is identical, because, except for destruction, auto_ptr objects act just like normal pointers. Easy, huh?The idea behind auto_ptr using an object to store a resource that needs to be automatically released and relying on that object's destructor to release it applies to more than just pointer-based resources. Consider a function in a GUI application that needs to create a window to display some information: // this function may leak resources if an exception// is thrownvoid displayInfo(const Information& info){  WINDOW_HANDLE w(createWindow());  display info in window corresponding to w;  destroyWindow(w);}Many window systems have C-like interfaces that use functions like createWindow and destroyWindow to acquire and release window resources. If an exception is thrown during the process of displaying info in w, the window for which w is a handle will be lost just as surely as any other dynamically allocated resource.The solution is the same as it was before. Create a class whose constructor and destructor acquire and release the resource: // class for acquiring and releasing a window handleclass WindowHandle {public:   WindowHandle(WINDOW_HANDLE handle): w(handle) {}  ~WindowHandle() { destroyWindow(w); }   operator WINDOW_HANDLE() { return w; }        // see belowprivate:  WINDOW_HANDLE w;  // The following functions are declared private to prevent  // multiple copies of a WINDOW_HANDLE from being created.  // See Item 28 for a discussion of a more flexible approach.  WindowHandle(const WindowHandle&);  WindowHandle& operator=(const WindowHandle&);};This looks just like the auto_ptr template, except that assignment and copying are explicitly prohibited (see Item E27), and there is an implicit conversion operator that can be used to turn a WindowHandle into a WINDOW_HANDLE. This capability is essential to the practical application of a WindowHandle object, because it means you can use a WindowHandle just about anywhere you would normally use a raw WINDOW_HANDLE. (See Item 5, however, for why you should generally be leery of implicit type conversion operators.)Given the WindowHandle class, we can rewrite displayInfo as follows: // this function avoids leaking resources if an// exception is thrownvoid displayInfo(const Information& info){  WindowHandle w(createWindow());  display info in window corresponding to w;}Even if an exception is thrown within displayInfo, the window created by createWindow will always be destroyed.By adhering to the rule that resources should be encapsulated inside objects, you can usually avoid resource leaks in the presence of exceptions. But what happens if an exception is thrown while you're in the process of acquiring a resource, e.g., while you're in the constructor of a resource-acquiring class? What happens if an exception is thrown during the automatic destruction of such resources? Don't constructors and destructors call for special techniques? They do, and you can read about them in Items 10 and 11. Back to Item 9: Use destructors to prevent resource leaksContinue to Item 11: Prevent exceptions from leaving destructorsItem 10: Prevent resource leaks in constructors.Imagine you're developing software for a multimedia address book. Such an address book might hold, in addition to the usual textual information of a person's name, address, and phone numbers, a picture of the person and the sound of their voice (possibly giving the proper pronunciation of their name).To implement the book, you might come up with a design like this: class Image {                        // for image datapublic:  Image(const string& imageDataFileName);  ...};class AudioClip {                    // for audio datapublic:  AudioClip(const string& audioDataFileName);  ...};class PhoneNumber {         ... };   // for holding phone numbersclass BookEntry {                    // for each entry in thepublic:                              // address bookBookEntry(const string& name,          const string& address = "",          const string& imageFileName = "",          const string& audioClipFileName = "");~BookEntry();// phone numbers are added via this functionvoid addPhoneNumber(const PhoneNumber& number);...private:  string theName;                 // person's name  string theAddress;              // their address  list<PhoneNumber> thePhones;    // their phone numbers  Image *theImage;                // their image  AudioClip *theAudioClip;        // an audio clip from them};Each BookEntry must have name data, so you require that as a constructor argument (see Item 3), but the other fields the person's address and the names of files containing image and audio data are optional. Note the use of the list class to hold the person's phone numbers. This is one of several container classes that are part of the standard C++ library (see Item E49 and Item 35).A straightforward way to write the BookEntry constructor and destructor is as follows: BookEntry::BookEntry(const string& name,                     const string& address,

⌨️ 快捷键说明

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