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

📄 mi29.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 4 页
字号:
This idea that of sharing a value with other objects until we have to write on our own copy of the value has a long and distinguished history in Computer Science, especially in operating systems, where processes are routinely allowed to share pages until they want to modify data on their own copy of a page. The technique is common enough to have a name: copy-on-write. It's a specific example of a more general approach to efficiency, that of lazy evaluation (see Item 17).Pointers, References, and Copy-on-WriteThis implementation of copy-on-write allows us to preserve both efficiency and correctness almost. There is one lingering problem. Consider this code: String s1 = "Hello";char *p = &s1[1];Our data structure at this point looks like this:Now consider an additional statement: String s2 = s1;The String copy constructor will make s2 share s1's StringValue, so the resulting data structure will be this one:The implications of a statement such as the following, then, are not pleasant to contemplate: *p = 'x';                     // modifies both s1 and s2!There is no way the String copy constructor can detect this problem, because it has no way to know that a pointer into s1's StringValue object exists. And this problem isn't limited to pointers: it would exist if someone had saved a reference to the result of a call to String's non-const operator[].There are at least three ways of dealing with this problem. The first is to ignore it, to pretend it doesn't exist. This approach turns out to be distressingly common in class libraries that implement reference-counted strings. If you have access to a reference-counted string, try the above example and see if you're distressed, too. If you're not sure if you have access to a reference-counted string, try the example anyway. Through the wonder of encapsulation, you may be using such a type without knowing it.Not all implementations ignore such problems. A slightly more sophisticated way of dealing with such difficulties is to define them out of existence. Implementations adopting this strategy typically put something in their documentation that says, more or less, "Don't do that. If you do, results are undefined." If you then do it anyway wittingly or no and complain about the results, they respond, "Well, we told you not to do that." Such implementations are often efficient, but they leave much to be desired in the usability department.There is a third solution, and that's to eliminate the problem. It's not difficult to implement, but it can reduce the amount of value sharing between objects. Its essence is this: add a flag to each StringValue object indicating whether that object is shareable. Turn the flag on initially (the object is shareable), but turn it off whenever the non-const operator[] is invoked on the value represented by that object. Once the flag is set to false, it stays that way forever.10Here's a modified version of StringValue that includes a shareability flag: class String {private:  struct StringValue {    int refCount;    bool shareable;                // add this    char *data;    StringValue(const char *initValue);    ~StringValue();  };...};String::StringValue::StringValue(const char *initValue):   refCount(1),    shareable(true)                // add this{  data = new char[strlen(initValue) + 1];  strcpy(data, initValue);}String::StringValue::~StringValue(){  delete [] data;}As you can see, not much needs to change; the two lines that require modification are flagged with comments. Of course, String's member functions must be updated to take the shareable field into account. Here's how the copy constructor would do that: String::String(const String& rhs){  if (rhs.value->shareable) {    value = rhs.value;    ++value->refCount;  }  else {    value = new StringValue(rhs.value->data);  }}All the other String member functions would have to check the shareable field in an analogous fashion. The non-const version of operator[] would be the only function to set the shareable flag to false: char& String::operator[](int index){  if (value->refCount > 1) {    --value->refCount;    value = new StringValue(value->data);  }  value->shareable = false;           // add this  return value->data[index];}If you use the proxy class technique of Item 30 to distinguish read usage from write usage in operator[], you can usually reduce the number of StringValue objects that must be marked unshareable.A Reference-Counting Base ClassReference counting is useful for more than just strings. Any class in which different objects may have values in common is a legitimate candidate for reference counting. Rewriting a class to take advantage of reference counting can be a lot of work, however, and most of us already have more than enough to do. Wouldn't it be nice if we could somehow write (and test and document) the reference counting code in a context-independent manner, then just graft it onto classes when needed? Of course it would. In a curious twist of fate, there's a way to do it (or at least to do most of it).The first step is to create a base class, RCObject, for reference-counted objects. Any class wishing to take advantage of automatic reference counting must inherit from this class. RCObject encapsulates the reference count itself, as well as functions for incrementing and decrementing that count. It also contains the code for destroying a value when it is no longer in use, i.e., when its reference count becomes 0. Finally, it contains a field that keeps track of whether this value is shareable, and it provides functions to query this value and set it to false. There is no need for a function to set the shareability field to true, because all values are shareable by default. As noted above, once an object has been tagged unshareable, there is no way to make it shareable again.RCObject's class definition looks like this: class RCObject {public:  RCObject();  RCObject(const RCObject& rhs);  RCObject& operator=(const RCObject& rhs);  virtual ~RCObject() = 0;  void addReference();  void removeReference();void markUnshareable();bool isShareable() const;bool isShared() const;private:int refCount;bool shareable;};RCObjects can be created (as the base class parts of more derived objects) and destroyed; they can have new references added to them and can have current references removed; their shareability status can be queried and can be disabled; and they can report whether they are currently being shared. That's all they offer. As a class encapsulating the notion of being reference-countable, that's really all we have a right to expect them to do. Note the tell-tale virtual destructor, a sure sign this class is designed for use as a base class (see Item E14). Note also how the destructor is a pure virtual function, a sure sign this class is designed to be used only as a base class.The code to implement RCObject is, if nothing else, brief: RCObject::RCObject(): refCount(0), shareable(true) {}RCObject::RCObject(const RCObject&): refCount(0), shareable(true) {}RCObject& RCObject::operator=(const RCObject&){ return *this; }RCObject::~RCObject() {}               // virtual dtors must always                                       // be implemented, even if                                       // they are pure virtual                                       // and do nothing (see also                                       // Item 33 and Item E14)void RCObject::addReference() { ++refCount; }void RCObject::removeReference(){   if (--refCount == 0) delete this; }void RCObject::markUnshareable(){ shareable = false; }bool RCObject::isShareable() const{ return shareable; }bool RCObject::isShared() const{ return refCount > 1; }Curiously, we set refCount to 0 inside both constructors. This seems counterintuitive. Surely at least the creator of the new RCObject is referring to it! As it turns out, it simplifies things for the creators of RCObjects to set refCount to 1 themselves, so we oblige them here by not getting in their way. We'll get a chance to see the resulting code simplification shortly.Another curious thing is that the copy constructor always sets refCount to 0, regardless of the value of refCount for the RCObject we're copying. That's because we're creating a new object representing a value, and new values are always unshared and referenced only by their creator. Again, the creator is responsible for setting the refCount to its proper value.The RCObject assignment operator looks downright subversive: it does nothing. Frankly, it's unlikely this operator will ever be called. RCObject is a base class for a shared value object, and in a system based on reference counting, such objects are not assigned to one another, objects pointing to them are. In our case, we don't expect StringValue objects to be assigned to one another, we expect only String objects to be involved in assignments. In such assignments, no change is made to the value of a StringValue only the StringValue reference count is modified.Nevertheless, it is conceivable that some as-yet-unwritten class might someday inherit from RCObject and might wish to allow assignment of reference-counted values (see Item 32 and Item E16). If so, RCObject's assignment operator should do the right thing, and the right thing is to do nothing. To see why, imagine that we wished to allow assignments between StringValue objects. Given StringValue objects sv1 and sv2, what should happen to sv1's and sv2's reference counts in an assignment? sv1 = sv2;                    // how are sv1's and sv2's reference                              // counts affected?Before the assignment, some number of String objects are pointing to sv1. That number is unchanged by the assignment, because only sv1's value changes. Similarly, some number of String objects are pointing to sv2 prior to the assignment, and after the assignment, exactly the same String objects point to sv2. sv2's reference count is also unchanged. When RCObjects are involved in an assignment, then, the number of objects pointing to those objects is unaffected, hence RCObject::operator= should change no reference counts. That's exactly what the implementation above does. Counterintuitive? Perhaps, but it's still correct.The code for RCObject::removeReference is responsible not only for decrementing the object's refCount, but also for destroying the object if the new value of refCount is 0. It accomplishes this latter task by deleteing this, which, as Item 27 explains, is safe only if we know that *this is a heap object. For this class to be successful, we must engineer things so that RCObjects can be created only on the heap. General approaches to achieving that end are discussed in Item 27, but the specific measures we'll employ in this case are described at the conclusion of this Item.To take advantage of our new reference-counting base class, we modify StringValue to inherit its reference counting capabilities from RCObject: class String {private:  struct StringValue: public RCObject {    char *data;    StringValue(const char *initValue);    ~StringValue();  };...};String::StringValue::StringValue(const char *initValue){  data = new char[strlen(initValue) + 1];  strcpy(data, initValue);}String::StringValue::~StringValue(){  delete [] data;}This version of StringValue is almost identical to the one we saw earlier. The only thing that's changed is that StringValue's member functions no longer manipulate the refCount field. RCObject now handles what they used to do.Don't feel bad if you blanched at the sight of a nested class (StringValue) inheriting from a class (RCObject) that's unrelated to the nesting class (String). It looks weird to everybody at first, but it's perfectly kosher. A nested class is just as much a class as any other, so it has the freedom to inherit from whatever other classes it likes. In time, you won't think twice about such inheritance relationships.Automating Reference Count ManipulationsThe RCObject class gives us a place to store a reference count, and it gives us member functions through which that reference count can be manipulated, but the calls to those functions must still be manually inserted in other classes. It is still up to the String copy constructor and the String assignment operator to call addReference and removeReference on StringValue objects. This is clumsy. We'd like to move those calls out into a reusable class, too, thus freeing authors of classes like String from worrying about any of the details of reference counting. Can it be done? Isn't C++ supposed to support reuse?It can, and it does. There's no easy way to arrange things so that all reference-counting considerations can be moved out of application classes, but there is a way to eliminate most of them for most classes. (In some application classes, you can eliminate all reference-counting code, but our String class, alas, isn't one of them. One member function spoils the party, and I suspect you won't be too surprised to hear it's our old nemesis, the non-const version of operator[]. Take heart, however; we'll tame that miscreant in the end.)Notice that each String object contains a pointer to the StringValue object representing that String's value: class String {private:  struct StringValue: public RCObject { ... };  StringValue *value;                // value of this String  ...};We have to manipulate the refCount field of the StringValue object anytime anything interesting happens to one of the pointers pointing to it. "Interesting happenings" include copying a pointer, reassigning one, and destroying one. If we could somehow make the pointer itself detect these happenings and automatically perform the necessary manipulations of the refCount field, we'd be home free. Unfortunately, pointers are rather dense creatures, and the chances of them detecting anything, much less automatically reacting to things they detect, are pretty slim. Fortunately, there's a way to smarten them up: replace them with objects that act like pointers, but that do more.Such objects are called smart pointers, and you can read about them in more detail than you probably care to in Item 28. For our purposes here, it's enough to know that smart pointer objects support the member selection (->) and dereferencing (*) operations, just like real pointers (which, in this context, are generally referred to as dumb pointers), and, like dumb pointers, they are strongly typed: you can't make a smart pointer-to-T point to an object that isn't of type T.Here's a template for objects that act as smart pointers to reference-counted objects: // template class for smart pointers-to-T objects. T must// support the RCObject interface, typically by inheriting// from RCObjecttemplate<class T>class RCPtr {public:  RCPtr(T* realPtr = 0);  RCPtr(const RCPtr& rhs);  ~RCPtr();  RCPtr& operator=(const RCPtr& rhs);  T* operator->() const;            // see Item 28  T& operator*() const;             // see Item 28private:  T *pointee;                       // dumb pointer this                                    // object is emulating  void init();                      // common initialization};                                  // codeThis template gives smart pointer objects control over what happens during their construction, assignment, and destruction. When such events occur, these objects can automatically perform the appropriate manipulations of the refCount field in the objects to which they point.For example, when an RCPtr is created, the object it points to needs to have its reference count increased. There's no need to burden application developers with the requirement to tend to this irksome detail manually, because RCPtr constructors can handle it themselves. The code in the two constructors is all but identical only the member initialization lists differ so rather than write it twice, we put it in a private member function called init and have both constructors call that: template<class T>

⌨️ 快捷键说明

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