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

📄 mi29.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 4 页
字号:
RCPtr<T>::RCPtr(T* realPtr): pointee(realPtr){  init();}template<class T>RCPtr<T>::RCPtr(const RCPtr& rhs): pointee(rhs.pointee){  init();}template<class T>void RCPtr<T>::init(){  if (pointee == 0) {                // if the dumb pointer is    return;                          // null, so is the smart one  }if (pointee->isShareable() == false) {           // if the value    pointee = new T(*pointee);                   // isn't shareable,  }                                              // copy itpointee->addReference();             // note that there is now a}                                    // new reference to the valueMoving common code into a separate function like init is exemplary software engineering, but its luster dims when, as in this case, the function doesn't behave correctly.The problem is this. When init needs to create a new copy of a value (because the existing copy isn't shareable), it executes the following code: pointee = new T(*pointee);The type of pointee is pointer-to-T, so this statement creates a new T object and initializes it by calling T's copy constructor. In the case of an RCPtr in the String class, T will be String::StringValue, so the statement above will call String::StringValue's copy constructor. We haven't declared a copy constructor for that class, however, so our compilers will generate one for us. The copy constructor so generated will, in accordance with the rules for automatically generated copy constructors in C++, copy only StringValue's data pointer; it will not copy the char* string data points to. Such behavior is disastrous in nearly any class (not just reference-counted classes), and that's why you should get into the habit of writing a copy constructor (and an assignment operator) for all your classes that contain pointers (see Item E11).The correct behavior of the RCPtr<T> template depends on T containing a copy constructor that makes a truly independent copy (i.e., a deep copy) of the value represented by T. We must augment StringValue with such a constructor before we can use it with the RCPtr class: class String {private:  struct StringValue: public RCObject {    StringValue(const StringValue& rhs);    ...  };  ...};String::StringValue::StringValue(const StringValue& rhs){  data = new char[strlen(rhs.data) + 1];  strcpy(data, rhs.data);}The existence of a deep-copying copy constructor is not the only assumption RCPtr<T> makes about T. It also requires that T inherit from RCObject, or at least that T provide all the functionality that RCObject does. In view of the fact that RCPtr objects are designed to point only to reference-counted objects, this is hardly an unreasonable assumption. Nevertheless, the assumption must be documented.A final assumption in RCPtr<T> is that the type of the object pointed to is T. This seems obvious enough. After all, pointee is declared to be of type T*. But pointee might really point to a class derived from T. For example, if we had a class SpecialStringValue that inherited from String::StringValue, class String {private:  struct StringValue: public RCObject { ... };  struct SpecialStringValue: public StringValue { ... };  ...};we could end up with a String containing a RCPtr<StringValue> pointing to a SpecialStringValue object. In that case, we'd want this part of init, pointee = new T(*pointee);                // T is StringValue, but                                          // pointee really points to                                          // a SpecialStringValueto call SpecialStringValue's copy constructor, not StringValue's. We can arrange for this to happen by using a virtual copy constructor (see Item 25). In the case of our String class, we don't expect classes to derive from StringValue, so we'll disregard this issue.With RCPtr's constructors out of the way, the rest of the class's functions can be dispatched with considerably greater alacrity. Assignment of an RCPtr is straightforward, though the need to test whether the newly assigned value is shareable complicates matters slightly. Fortunately, such complications have already been handled by the init function that was created for RCPtr's constructors. We take advantage of that fact by using it again here: template<class T>RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs){  if (pointee != rhs.pointee) {          // skip assignments                                         // where the value                                         // doesn't change    if (pointee) {      pointee->removeReference();        // remove reference to    }                                    // current value    pointee = rhs.pointee;               // point to new value    init();                              // if possible, share it  }                                      // else make own copy  return *this;}The destructor is easier. When an RCPtr is destroyed, it simply removes its reference to the reference-counted object: template<class T>RCPtr<T>::~RCPtr(){  if (pointee)pointee->removeReference();}If the RCPtr that just expired was the last reference to the object, that object will be destroyed inside RCObject's removeReference member function. Hence RCPtr objects never need to worry about destroying the values they point to.Finally, RCPtr's pointer-emulating operators are part of the smart pointer boilerplate you can read about in Item 28: template<class T>T* RCPtr<T>::operator->() const { return pointee; }template<class T>T& RCPtr<T>::operator*() const { return *pointee; }Putting it All TogetherEnough! Finis! At long last we are in a position to put all the pieces together and build a reference-counted String class based on the reusable RCObject and RCPtr classes. With luck, you haven't forgotten that that was our original goal.Each reference-counted string is implemented via this data structure:The classes making up this data structure are defined like this: template<class T>                       // template class for smartclass RCPtr {                           // pointers-to-T objects; Tpublic:                                 // must inherit from RCObject  RCPtr(T* realPtr = 0);  RCPtr(const RCPtr& rhs);  ~RCPtr();  RCPtr& operator=(const RCPtr& rhs);  T* operator->() const;  T& operator*() const;private:  T *pointee;  void init();};class RCObject {                       // base class for reference-public:                                // counted objects  void addReference();  void removeReference();  void markUnshareable();  bool isShareable() const;  bool isShared() const;protected:  RCObject();  RCObject(const RCObject& rhs);  RCObject& operator=(const RCObject& rhs);  virtual ~RCObject() = 0;private:  int refCount;  bool shareable;};class String {                           // class to be used bypublic:                                  // application developers  String(const char *value = "");  const char& operator[](int index) const;  char& operator[](int index);private:  // class representing string values  struct StringValue: public RCObject {    char *data;    StringValue(const char *initValue);    StringValue(const StringValue& rhs);    void init(const char *initValue);    ~StringValue();  };  RCPtr<StringValue> value;};For the most part, this is just a recap of what we've already developed, so nothing should be much of a surprise. Close examination reveals we've added an init function to String::StringValue, but, as we'll see below, that serves the same purpose as the corresponding function in RCPtr: it prevents code duplication in the constructors.There is a significant difference between the public interface of this String class and the one we used at the beginning of this Item. Where is the copy constructor? Where is the assignment operator? Where is the destructor? Something is definitely amiss here.Actually, no. Nothing is amiss. In fact, some things are working perfectly. If you don't see what they are, prepare yourself for a C++ epiphany.We don't need those functions anymore. Sure, copying of String objects is still supported, and yes, the copying will correctly handle the underlying reference-counted StringValue objects, but the String class doesn't have to provide a single line of code to make this happen. That's because the compiler-generated copy constructor for String will automatically call the copy constructor for String's RCPtr member, and the copy constructor for that class will perform all the necessary manipulations of the StringValue object, including its reference count. An RCPtr is a smart pointer, remember? We designed it to take care of the details of reference counting, so that's what it does. It also handles assignment and destruction, and that's why String doesn't need to write those functions, either. Our original goal was to move the unreusable reference-counting code out of our hand-written String class and into context-independent classes where it would be available for use with any class. Now we've done it (in the form of the RCObject and RCPtr classes), so don't be so surprised when it suddenly starts working. It's supposed to work.Just so you have everything in one place, here's the implementation of RCObject: RCObject::RCObject(): refCount(0), shareable(true) {}RCObject::RCObject(const RCObject&): refCount(0), shareable(true) {}RCObject& RCObject::operator=(const RCObject&){ return *this; }RCObject::~RCObject() {}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; }And here's the implementation of RCPtr: template<class T>void RCPtr<T>::init(){  if (pointee == 0) return;  if (pointee->isShareable() == false) {    pointee = new T(*pointee);  }

⌨️ 快捷键说明

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