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

📄 mi29.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 4 页
字号:
  pointee->addReference();}template<class T>RCPtr<T>::RCPtr(T* realPtr): pointee(realPtr){ init(); }template<class T>RCPtr<T>::RCPtr(const RCPtr& rhs): pointee(rhs.pointee){ init(); }template<class T>RCPtr<T>::~RCPtr(){ if (pointee)pointee->removeReference(); }template<class T>RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs){  if (pointee != rhs.pointee) {    if (pointee) pointee->removeReference();    pointee = rhs.pointee;    init();  }return *this;}template<class T>T* RCPtr<T>::operator->() const { return pointee; }template<class T>T& RCPtr<T>::operator*() const { return *pointee; }The implementation of String::StringValue looks like this: void String::StringValue::init(const char *initValue){  data = new char[strlen(initValue) + 1];  strcpy(data, initValue);}String::StringValue::StringValue(const char *initValue){ init(initValue); }String::StringValue::StringValue(const StringValue& rhs){ init(rhs.data); }String::StringValue::~StringValue(){ delete [] data; }Ultimately, all roads lead to String, and that class is implemented this way: String::String(const char *initValue): value(new StringValue(initValue)) {}const char& String::operator[](int index) const{ return value->data[index]; }char& String::operator[](int index){  if (value->isShared()) {    value = new StringValue(value->data);  }  value->markUnshareable();  return value->data[index];}If you compare the code for this String class with that we developed for the String class using dumb pointers, you'll be struck by two things. First, there's a lot less of it here than there. That's because RCPtr has assumed much of the reference-counting burden that used to fall on String. Second, the code that remains in String is nearly unchanged: the smart pointer replaced the dumb pointer essentially seamlessly. In fact, the only changes are in operator[], where we call isShared instead of checking the value of refCount directly and where our use of the smart RCPtr object eliminates the need to manually manipulate the reference count during a copy-on-write.This is all very nice, of course. Who can object to less code? Who can oppose encapsulation success stories? The bottom line, however, is determined more by the impact of this newfangled String class on its clients than by any of its implementation details, and it is here that things really shine. If no news is good news, the news here is very good indeed. The String interface has not changed. We added reference counting, we added the ability to mark individual string values as unshareable, we moved the notion of reference countability into a new base class, we added smart pointers to automate the manipulation of reference counts, yet not one line of client code needs to be changed. Sure, we changed the String class definition, so clients who want to take advantage of reference-counted strings must recompile and relink, but their investment in code is completely and utterly preserved. You see? Encapsulation really is a wonderful thing.Adding Reference Counting to Existing ClassesEverything we've discussed so far assumes we have access to the source code of the classes we're interested in. But what if we'd like to apply the benefits of reference counting to some class Widget that's in a library we can't modify? There's no way to make Widget inherit from RCObject, so we can't use smart RCPtrs with it. Are we out of luck?We're not. With some minor modifications to our design, we can add reference counting to any type.First, let's consider what our design would look like if we could have Widget inherit from RCObject. In that case, we'd have to add a class, RCWidget, for clients to use, but everything would then be analogous to our String/StringValue example, with RCWidget playing the role of String and Widget playing the role of StringValue. The design would look like this:We can now apply the maxim that most problems in Computer Science can be solved with an additional level of indirection. We add a new class, CountHolder, to hold the reference count, and we have CountHolder inherit from RCObject. We also have CountHolder contain a pointer to a Widget. We then replace the smart RCPtr template with an equally smart RCIPtr template that knows about the existence of the CountHolder class. (The "I" in RCIPtr stands for "indirect.") The modified design looks like this:Just as StringValue was an implementation detail hidden from clients of String, CountHolder is an implementation detail hidden from clients of RCWidget. In fact, it's an implementation detail of RCIPtr, so it's nested inside that class. RCIPtr is implemented this way: template<class T>class RCIPtr {public:  RCIPtr(T* realPtr = 0);  RCIPtr(const RCIPtr& rhs);  ~RCIPtr();  RCIPtr& operator=(const RCIPtr& rhs);  const T* operator->() const;               // see below for an  T* operator->();                           // explanation of why  const T& operator*() const;                // these functions are  T& operator*();                            // declared this wayprivate:  struct CountHolder: public RCObject {    ~CountHolder() { delete pointee; }    T *pointee;  };  CountHolder *counter;  void init();  void makeCopy();                                // see below};template<class T>void RCIPtr<T>::init(){  if (counter->isShareable() == false) {    T *oldValue = counter->pointee;    counter = new CountHolder;    counter->pointee = new T(*oldValue);  }  counter->addReference();}template<class T>RCIPtr<T>::RCIPtr(T* realPtr): counter(new CountHolder){  counter->pointee = realPtr;  init();}template<class T>RCIPtr<T>::RCIPtr(const RCIPtr& rhs): counter(rhs.counter){ init(); }template<class T>RCIPtr<T>::~RCIPtr(){ counter->removeReference(); }template<class T>RCIPtr<T>& RCIPtr<T>::operator=(const RCIPtr& rhs){  if (counter != rhs.counter) {    counter->removeReference();    counter = rhs.counter;    init();  }  return *this;}template<class T>                          // implement the copyvoid RCIPtr<T>::makeCopy()                 // part of copy-on-{                                          // write (COW)  if (counter->isShared()) {    T *oldValue = counter->pointee;    counter->removeReference();    counter = new CountHolder;    counter->pointee = new T(*oldValue);    counter->addReference();  }}template<class T>                           // const access;const T* RCIPtr<T>::operator->() const      // no COW needed{ return counter->pointee; }template<class T>                           // non-constT* RCIPtr<T>::operator->()                  // access; COW{ makeCopy(); return counter->pointee; }    // neededtemplate<class T>                           // const access;const T& RCIPtr<T>::operator*() const       // no COW needed{ return *(counter->pointee); }template<class T>                           // non-constT& RCIPtr<T>::operator*()                   // access; do the{ makeCopy(); return *(counter->pointee); } // COW thingRCIPtr differs from RCPtr in only two ways. First, RCPtr objects point to values directly, while RCIPtr objects point to values through intervening CountHolder objects. Second, RCIPtr overloads operator-> and operator* so that a copy-on-write is automatically performed whenever a non-const access is made to a pointed-to object.Given RCIPtr, it's easy to implement RCWidget, because each function in RCWidget is implemented by forwarding the call through the underlying RCIPtr to a Widget object. For example, if Widget looks like this, class Widget {public:  Widget(int size);  Widget(const Widget& rhs);  ~Widget();  Widget& operator=(const Widget& rhs);  void doThis();  int showThat() const;};RCWidget will be defined this way: class RCWidget {public:  RCWidget(int size): value(new Widget(size)) {}  void doThis() { value->doThis(); }  int showThat() const { return value->showThat(); }private:  RCIPtr<Widget> value;};Note how the RCWidget constructor calls the Widget constructor (via the new operator see Item 8) with the argument it was passed; how RCWidget's doThis calls doThis in the Widget class; and how RCWidget::showThat returns whatever its Widget counterpart returns. Notice also how RCWidget declares no copy constructor, no assignment operator, and no destructor. As with the String class, there is no need to write these functions. Thanks to the behavior of the RCIPtr class, the default versions do the right things.If the thought occurs to you that creation of RCWidget is so mechanical, it could be automated, you're right. It would not be difficult to write a program that takes a class like Widget as input and produces a class like RCWidget as output. If you write such a program, please let me know.EvaluationLet us disentangle ourselves from the details of widgets, strings, values, smart pointers, and reference-counting base classes. That gives us an opportunity to step back and view reference counting in a broader context. In that more general context, we must address a higher-level question, namely, when is reference counting an appropriate technique?Reference-counting implementations are not without cost. Each reference-counted value carries a reference count with it, and most operations require that this reference count be examined or manipulated in some way. Object values therefore require more memory, and we sometimes execute more code when we work with them. Furthermore, the underlying source code is considerably more complex for a reference-counted class than for a less elaborate implementation. An un-reference-counted string class typically stands on its own, while our final String class is useless unless it's augmented with three auxiliary classes (StringValue, RCObject, and RCPtr). True, our more complicated design holds out the promise of greater efficiency when values can be shared, it eliminates the need to track object ownership, and it promotes reusability of the reference counting idea and implementation. Nevertheless, that quartet of classes has to be written, tested, documented, and maintained, and that's going to be more work than writing, testing, documenting, and maintaining a single class. Even a manager can see that.Reference counting is an optimization technique predicated on the assumption that objects will commonly share values (see also Item 18). If this assumption fails to hold, reference counting will use more memory than a more conventional implementation and it will execute more code. On the other hand, if your objects do tend to have common values, reference counting should save you both time and space. The bigger your object values and the more objects that can simultaneously share values, the more memory you'll save. The more you copy and assign values between objects, the more time you'll save. The more expensive it is to create and destroy a value, the more time you'll save there, too. In short, reference counting is most useful for improving efficiency under the following conditions: Relatively few values are shared by relatively many objects. Such sharing typically arises through calls to assignment operators and copy constructors. The higher the objects/values ratio, the better the case for reference counting. Object values are expensive to create or destroy, or they use lots of memory. Even when this is the case, reference counting still buys you nothing unless these values can be shared by multiple objects.There is only one sure way to tell whether these conditions are satisfied, and that way is not to guess or rely on your programmer's intuition (see Item 16). The reliable way to find out whether your program can benefit from reference counting is to profile or instrument it. That way you can find out if creating and destroying values is a performance bottleneck, and you can measure the objects/values ratio. Only when you have such data in hand are you in a position to determine whether the benefits of reference counting (of which there are many) outweigh the disadvantages (of which there are also many).Even when the conditions above are satisfied, a design employing reference counting may still be inappropriate. Some data structures (e.g., directed graphs) lead to self-referential or circular dependency structures. Such data structures have a tendency to spawn isolated collections of objects, used by no one, whose reference counts never drop to zero. That's because each object in the unused structure is pointed to by at least one other object in the same structure. Industrial-strength garbage collectors use special techniques to find such structures and eliminate them, but the simple reference-counting approach we've examined here is not easily extended to include such techniques.Reference counting can be attractive even if efficiency is not your primary concern. If you find yourself weighed down with uncertainty over who's allowed to delete what, reference counting could be just the technique you need to ease your burden. Many programmers are devoted to reference counting for this reason alone.Let us close this discussion on a technical note by tying up one remaining loose end. When RCObject::removeReference decrements an object's reference count, it checks to see if the new count is 0. If it is, removeReference destroys the object by deleteing this. This is a safe operation only if the object was allocated by calling new, so we need some way of ensuring that RCObjects are created only in that manner.In this case we do it by convention. RCObject is designed for use as a base class of reference-counted value objects, and those value objects should be referred to only by smart RCPtr pointers. Furthermore, the value objects should be instantiated only by application objects that realize values are being shared; the classes describing the value objects should never be available for general use. In our example, the class for value objects is StringValue, and we limit its use by making it private in String. Only String can create StringValue objects, so it is up to the author of the String class to ensure that all such objects are allocated via new.Our approach to the constraint that RCObjects be created only on the heap, then, is to assign responsibility for conformance to this constraint to a well-defined set of classes and to ensure that only that set of classes can create RCObjects. There is no possibility that random clients can accidently (or maliciously) create RCObjects in an inappropriate manner. We limit the right to create reference-counted objects, and when we do hand out the right, we make it clear that it's accompanied by the concomitant responsibility to follow the rules governing object creation. Back to Item 28: Smart pointersContinue to Item 30: Proxy classes 10 The string type in the standard C++ library (see Item E49 and Item 35) uses a combination of solutions two and three. The reference returned from the non-const operator[] is guaranteed to be valid until the next function call that might modify the string. After that, use of the reference (or the character to which it refers) yields undefined results. This allows the string's shareability flag to be reset to true whenever a function is called that might modify the string.Return 

⌨️ 快捷键说明

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