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

📄 mi27.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 2 页
字号:
Asset *pa = new Asset;Clearly *pa (including its member value) is on the heap. Equally clearly, it's not safe to invoke delete on a pointer to pa->value, because no such pointer was ever returned from new.As luck would have it, it's easier to determine whether it's safe to delete a pointer than to determine whether a pointer points to something on the heap, because all we need to answer the former question is a collection of addresses that have been returned by operator new. Since we can write operator new ourselves (see Items E8-E10), it's easy to construct such a collection. Here's how we might approach the problem: void *operator new(size_t size){  void *p = getMemory(size);         // call some function to                                     // allocate memory and                                     // handle out-of-memory                                     // conditions  add p to the collection of allocated addresses;  return p;}void operator delete(void *ptr){  releaseMemory(ptr);                // return memory to                                     // free store  remove ptr from the collection of allocated addresses;}bool isSafeToDelete(const void *address){  return whether address is in collection of  allocated addresses;}This is about as simple as it gets. operator new adds entries to a collection of allocated addresses, operator delete removes entries, and isSafeToDelete does a lookup in the collection to see if a particular address is there. If the operator new and operator delete functions are at global scope, this should work for all types, even the built-ins.In practice, three things are likely to dampen our enthusiasm for this design. The first is our extreme reluctance to define anything at global scope, especially functions with predefined meanings like operator new and operator delete. Knowing as we do that there is but one global scope and but a single version of operator new and operator delete with the "normal" signatures (i.e., sets of parameter types) within that scope (see Item E9), the last thing we want to do is seize those function signatures for ourselves. Doing so would render our software incompatible with any other software that also implements global versions of operator new and operator delete (such as many object-oriented database systems).Our second consideration is one of efficiency: why burden all heap allocations with the bookkeeping overhead necessary to keep track of returned addresses if we don't need to?Our final concern is pedestrian, but important. It turns out to be essentially impossible to implement isSafeToDelete so that it always works. The difficulty has to do with the fact that objects with multiple or virtual base classes have multiple addresses, so there's no guarantee that the address passed to isSafeToDelete is the same as the one returned from operator new, even if the object in question was allocated on the heap. For details, see Items 24 and 31.What we'd like is the functionality provided by these functions without the concomitant pollution of the global namespace, the mandatory overhead, and the correctness problems. Fortunately, C++ gives us exactly what we need in the form of an abstract mixin base class.An abstract base class is a base class that can't be instantiated, i.e., one with at least one pure virtual function. A mixin ("mix in") class is one that provides a single well-defined capability and is designed to be compatible with any other capabilities an inheriting class might provide (see Item E7). Such classes are nearly always abstract. We can therefore come up with an abstract mixin base class that offers derived classes the ability to determine whether a pointer was allocated from operator new. Here's such a class: class HeapTracked {                  // mixin class; keeps track ofpublic:                              // ptrs returned from op. new  class MissingAddress{};            // exception class; see below  virtual ~HeapTracked() = 0;  static void *operator new(size_t size);  static void operator delete(void *ptr);  bool isOnHeap() const;private:  typedef const void* RawAddress;  static list<RawAddress> addresses;};This class uses the list data structure that's part of the standard C++ library (see Item E49 and Item 35) to keep track of all pointers returned from operator new. That function allocates memory and adds entries to the list; operator delete deallocates memory and removes entries from the list; and isOnHeap returns whether an object's address is in the list.Implementation of the HeapTracked class is simple, because the global operator new and operator delete functions are called to perform the real memory allocation and deallocation, and the list class has functions to make insertion, removal, and lookup single-statement operations. Here's the full implementation of HeapTracked: // mandatory definition of static class memberlist<RawAddress> HeapTracked::addresses;// HeapTracked's destructor is pure virtual to make the// class abstract (see Item E14). The destructor must still// be defined, however, so we provide this empty definition.HeapTracked::~HeapTracked() {}void * HeapTracked::operator new(size_t size){  void *memPtr = ::operator new(size);  // get the memory  addresses.push_front(memPtr);         // put its address at                                        // the front of the list  return memPtr;}void HeapTracked::operator delete(void *ptr){  // get an "iterator" that identifies the list  // entry containing ptr; see Item 35 for details  list<RawAddress>::iterator it =    find(addresses.begin(), addresses.end(), ptr);  if (it != addresses.end()) {       // if an entry was found    addresses.erase(it);             // remove the entry    ::operator delete(ptr);          // deallocate the memory  } else {                           // otherwise    throw MissingAddress();          // ptr wasn't allocated by  }                                  // op. new, so throw an}                                    // exceptionbool HeapTracked::isOnHeap() const{  // get a pointer to the beginning of the memory  // occupied by *this; see below for details  const void *rawAddress = dynamic_cast<const void*>(this);  // look up the pointer in the list of addresses  // returned by operator new  list<RawAddress>::iterator it =    find(addresses.begin(), addresses.end(), rawAddress);  return it != addresses.end();      // return whether it was}                                    // foundThis code is straightforward, though it may not look that way if you are unfamiliar with the list class and the other components of the Standard Template Library. Item 35 explains everything, but the comments in the code above should be sufficient to explain what's happening in this example.The only other thing that may confound you is this statement (in isOnHeap): const void *rawAddress = dynamic_cast<const void*>(this);I mentioned earlier that writing the global function isSafeToDelete is complicated by the fact that objects with multiple or virtual base classes have several addresses. That problem plagues us in isOnHeap, too, but because isOnHeap applies only to HeapTracked objects, we can exploit a special feature of the dynamic_cast operator (see Item 2) to eliminate the problem. Simply put, dynamic_casting a pointer to void* (or const void* or volatile void* or, for those who can't get enough modifiers in their usual diet, const volatile void*) yields a pointer to the beginning of the memory for the object pointed to by the pointer. But dynamic_cast is applicable only to pointers to objects that have at least one virtual function. Our ill-fated isSafeToDelete function had to work with any type of pointer, so dynamic_cast wouldn't help it. isOnHeap is more selective (it tests only pointers to HeapTracked objects), so dynamic_casting this to const void* gives us a pointer to the beginning of the memory for the current object. That's the pointer that HeapTracked::operator new must have returned if the memory for the current object was allocated by HeapTracked::operator new in the first place. Provided your compilers support the dynamic_cast operator, this technique is completely portable.Given this class, even BASIC programmers could add to a class the ability to track pointers to heap allocations. All they'd need to do is have the class inherit from HeapTracked. If, for example, we want to be able to determine whether a pointer to an Asset object points to a heap-based object, we'd modify Asset's class definition to specify HeapTracked as a base class: class Asset: public HeapTracked {private:  UPNumber value;  ...};We could then query Asset* pointers as follows: void inventoryAsset(const Asset *ap){  if (ap->isOnHeap()) {    ap is a heap-based asset  inventory it as such;  }  else {    ap is a non-heap-based asset  record it that way;  }}A disadvantage of a mixin class like HeapTracked is that it can't be used with the built-in types, because types like int and char can't inherit from anything. Still, the most common reason for wanting to use a class like HeapTracked is to determine whether it's okay to "delete this," and you'll never want to do that with a built-in type because such types have no this pointer.Prohibiting Heap-Based ObjectsThus ends our examination of determining whether an object is on the heap. At the opposite end of the spectrum is preventing objects from being allocated on the heap. Here the outlook is a bit brighter. There are, as usual, three cases: objects that are directly instantiated, objects instantiated as base class parts of derived class objects, and objects embedded inside other objects. We'll consider each in turn.Preventing clients from directly instantiating objects on the heap is easy, because such objects are always created by calls to new and you can make it impossible for clients to call new. Now, you can't affect the availability of the new operator (that's built into the language), but you can take advantage of the fact that the new operator always calls operator new (see Item 8), and that function is one you can declare yourself. In particular, it is one you can declare private. If, for example, you want to keep clients from creating UPNumber objects on the heap, you could do it this way: class UPNumber {private:  static void *operator new(size_t size);  static void operator delete(void *ptr);  ...};Clients can now do only what they're supposed to be able to do: UPNumber n1;                         // okaystatic UPNumber n2;                  // also okayUPNumber *p = new UPNumber;          // error! attempt to call                                     // private operator newIt suffices to declare operator new private, but it looks strange to have operator new be private and operator delete be public, so unless there's a compelling reason to split up the pair, it's best to declare them in the same part of a class. If you'd like to prohibit heap-based arrays of UPNumber objects, too, you could declare operator new[] and operator delete[] (see Item 8) private as well. (The bond between operator new and operator delete is stronger than many people think. For information on a rarely-understood aspect of their relationship, turn to the sidebar in my article on counting objects.)Interestingly, declaring operator new private often also prevents UPNumber objects from being instantiated as base class parts of heap-based derived class objects. That's because operator new and operator delete are inherited, so if these functions aren't declared public in a derived class, that class inherits the private versions declared in its base(s): class UPNumber { ... };             // as aboveclass NonNegativeUPNumber:          // assume this class  public UPNumber {                 // declares no operator new  ...};NonNegativeUPNumber n1;             // okaystatic NonNegativeUPNumber n2;      // also okayNonNegativeUPNumber *p =            // error! attempt to call  new NonNegativeUPNumber;          // private operator newIf the derived class declares an operator new of its own, that function will be called when allocating derived class objects on the heap, and a different way will have to be found to prevent UPNumber base class parts from winding up there. Similarly, the fact that UPNumber's operator new is private has no effect on attempts to allocate objects containing UPNumber objects as members: class Asset {public:  Asset(int initValue);  ...private:  UPNumber value;};Asset *pa = new Asset(100);          // fine, calls                                     // Asset::operator new or                                     // ::operator new, not                                     // UPNumber::operator newFor all practical purposes, this brings us back to where we were when we wanted to throw an exception in the UPNumber constructors if a UPNumber object was being constructed in memory that wasn't on the heap. This time, of course, we want to throw an exception if the object in question is on the heap. Just as there is no portable way to determine if an address is on the heap, however, there is no portable way to determine that it is not on the heap, so we're out of luck. This should be no surprise. After all, if we could tell when an address is on the heap, we could surely tell when an address is not on the heap. But we can't, so we can't. Oh well. Back to Item 26: Limiting the number of objects of a classContinue to Item 28: Smart pointers 

⌨️ 快捷键说明

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