More Effective C ++ Item M29: Quote

zhaozj2021-02-16  41

1.1 Item M29: Quote

The reference count is such a trick that allows multiple objects with the same value to share this value implementation. This trick has two common motives. The first is the process of simplifying objects in tracking stacks. Once an object is assigned by calling New, the most tight is to record who has this object because of its owner - and only its owner - is responsible for calling DELETE to this object. However, ownership can be passed from an object to another object (eg, by transmitting a pointer type parameter), so it is difficult to track ownership of an object. A class such as Auto_PTR (see Item M9) can help us, but experience shows that most of the programs have not been properly obtained. The reference count can exempt the burden of tracking object ownership, because after using the reference count, the object yourself has yourself. When no one uses it, it automatically destroy himself. Therefore, the reference count is a simple garbage collection system.

The second motive is due to a simple common sense. If many objects have the same value, it is very boring to store this value. A better way is to let all objects share this value. Do not only save memory, but can make the program run faster because there is no need to construct and destructure the copy of this value.

Like most of the seemingly simple ideas, this motive has a twisted and interesting detail. There must be a reference counting system that must be implemented. Let us master some foundations before starting the details of the research. A good idea is to first focus on how we will have the same value as multiple objects. There is one here:

Class string {// The Standard String Type MAY

Public: // Employ The Techniques in this TECHNIQUES IN THIS

// item, but it is not required

String (const char * value = ");

String & Operator = (const string & rhs);

...

Private:

CHAR * DATA;

}

String A, B, C, D, E;

A = b = c = d = e = "hello";

It seems that the objects A to E have the same value "Hello". The morphology of its value depends on how the String class is implemented, but the usual implementation is a copy of this value every String object. For example, String's assignment operations may be implemented as this:

String & string :: Operator = (const string & rhs)

{

IF (this == & r Hs) Return * this; // See Item E17

DELETE [] DATA;

Data = new char [strlen (rhs.data) 1];

STRCPY (DATA, RHS.DATA);

Return * this; // See Item E15

}

According to this implementation, we can speculate that these five objects and their values ​​are as follows:

a ------> Hello

B ------> Hello

C ------> Hello

D ------> Hello

e ------> Hello

Its redundancy is obvious. In an ideal world, we hope to change the above to this:

a ------ |

b --------

C ------ | ----- Hello

D --------

e ------ | Here, only a "Hello" copy is stored, all String objects with this value share its implementation.

In the actual world, it is impossible to achieve this idea because we need to track how many objects share the same value. If the object A above is assigned to another value other than "Hello", we cannot destroy the value "Hello" because there are four objects that need it. On the other hand, if there is only one object with "hello" value, when it exceeds the living space, there is no object with this value, we must destroy this value to avoid resource leakage.

Save the number of objects that currently share / reference the same value means that our map must add a count value (reference count):

a ------ |

b --------

C ------ | 5 ----- Hello

D --------

e --------

(Some people call them as use count, but I am not one of them. C has a lot of its own characteristics, the final need is a dispute between the professional noun.)

* Implement reference count

Creating a String class with reference coupons is not difficult, but you need to pay attention to some details, so we will screamently implement the implementation of most of the common member functions of such classes. However, it is important to recognize that "we need a place to store this count value" is important. This place cannot be inside the String object, since each string value is required, not a reference count for each String object. This means that the string value and reference count are one-to-one correspondence, so we will create a class to save the reference count and its tracking value. We call this class StringValue, but because of its unique use is to help us implement the String class, so we nested it in the private area of ​​the String class. In addition, in order to read its data area for all member functions of STING, we declare StringValue as struct. What you need to know is that an Struct is embedded embedded in the private area of ​​the class, which can facilitate all members of this class to access this structure, but preventing other people from accessing it (of course, except for friends).

The basic design is like this:

Class string {

PUBLIC:

... // The Usual String MEMBER

// Functions Go Here

Private:

Struct stringvalue {...}; // Holds a Reference Count

// and a string value

StringValue * value; // value of this string

}

We can give this class to other names (such as rcstring) to emphasize that it uses a reference count, but the implementation of the class should not be something that the class must care about, the user only cares about the public interface of the class. And our String version of the reference count is exactly the same as the version without the reference count, so why do you want to mix the problem with the name of the class? Really needed? So we didn't do this.

This is the implementation of StringValue:

Class string {

Private:

Struct stringvalue {

Int refcount;

CHAR * DATA;

StringValue; Const Char * initValue;

~ StringValue ();

}

...

}

String :: StringValue :: stringvalue (const char * initvalue)

: Refcount (1)

{

Data = new char [strlen 1];

STRCPY (data, initvalue);

}

String :: stringvalue :: ~ StringValue ()

{

DELETE [] DATA;

}

This is everything else, it is clear, which is not enough to implement the String class with reference to the count. One, there is no copy constructor and assignment (see Item E11); two, no operations for Refcount. Don't worry, less features will be provided by the String class. The main purpose of StringValue is to provide a space to link a special value and the number of objects of shared this value. StringValue gave us this, this is enough.

We now start processing String member functions. The first is constructor:

Class string {

PUBLIC:

String (const char * initvalue = ");

String (const string & rhs);

...

}

The first constructor is implemented as simple as possible. We created a new StringValue object with an incoming Char * string and pointing the String object we are constructing to this new StringValue:

String :: string (const char * initvalue)

: Value (New StringValue (initValue)

{}

Such user code:

String S ("More Effective C ");

The generated data structure is like this:

s -----> 1 ------> More Effective C

String objects are independently constructed, and objects with the same initialization value do not share data, so this user code:

String S1 ("More Effective C ");

String S2 ("More Effective C ");

Generate such a data structure:

S1 -----> 1 ------> More Effective C

S2 -----> 1 ------> More Effective C

Eliminating such a copy is possible: to track the existing StringValue object by allowing the String (or StringValue), and only a new object is created only when it is a different string. But such improvements have some deviations from the target. So, I left it as an exercise to the reader.

String's copy constructor is very efficient: new String objects share the same StringValue object with the copy object:

String :: string (const string & rhs)

: Value (rhs.value)

{

Value-> refcount;

}

Such code:

String S1 ("More Effective C ");

String S2 = S1;

Generate such a data structure:

S1 ----- |

| 2 ------> More Effective C

S2 ----- |

This is definitely more efficient than usual (without reference count) String class, because you don't need to assign memory, release memory, and copy content into this memory for new String values. Now, we are just copying a pointer and adds a reference count.

The destructor of the String class is equally easy to implement, as most situations don't need anything. As long as the reference count value is not 0, there is at least one String object to use this value, this value cannot be destroyed. Only when the unique user is destructed (i.e., the reference count is 1 when entering the function), String's destructive function destroys the StringValue object: Class string {

PUBLIC:

~ String ();

...

}

String :: ~ String ()

{

IF (--Value-> refcount == 0) Delete Value;

}

Compare the version without the reference count. The same function is generally called Delete, of course, there will be a considerable amount of runtime. The String objects now provide actually have the same value, and this implementation above is only necessary to make a reducing reference count and compare with 0.

If the reference count on this issue is not expressed in the outside, you don't need to pay attention.

This is the construction and destructuring of String, we now go to assignment operations:

Class string {

PUBLIC:

String & Operator = (const string & rhs);

...

}

When the user wrote this code:

S1 = S2; // s1 and s2 Are Both String Objects

The result should be S1 and S2 points to the same StringValue object. The reference to the object should be increased when assigning. Also, the reference count of S1 points to the StringValue object should be reduced because S1 no longer has this value. If S1 is a unique object with the original value, this value should be destroyed. In C , it seems like this:

String & string :: Operator = (const string & rhs)

{

IF (value == rhs.value) {// do nothing means

Return * this; // is already the same; tHIS

} // Subsumes The Usual Test of

// this Against & RHS (See Item E17)

IF (--Value-> Refcount == 0) {// destroy * this's value

Delete Value; // no one else is using it

}

Value = rhs.value; // Have * this Share rhs's

Value-> Refcount; // Value

Return * this;

}

* Write copy

Consider the array subscript operation ([]), which allows a single character in the string to be read or written in the array subscript operation ([]).

Class string {

PUBLIC:

Const Char &

Operator [】 (int index) const; // for const strings

Char & Operator []/); // for non-const strings

...

}

The implementation of this function is easy because it is a read-only operation, and the value of the String object is not affected:

Const Char & String :: Operator [] (int index) Const

{

Return Value-> Data [index];

}

(This function achieves the subscript index in the traditional C traditional sense (not "not"). If you want to add parameter check, it is very easy.)

Non-Const's Operator [] version is a completely different story. It may be called to read a character, or it may be called to write a character:

String S;

...

Cout << s [3]; // this is a read

s [5] = 'x'; // this is a write

We want to handle read and write in a different way. Simple read operations can be implemented in a manner similar to const port, while the write operation must be implemented in a completely different way.

When we modify the value of a String object, we must carefully prevent the value of other String objects that share the same StringValue object with it. Unfortunately, the C compiler has no way to tell us that a specific Operator [] is used as a read or written, so we must conserve "all" call non-const operator [] behavior is for writing. (The Proxy class can help us distinguish between reading or writing, see Item M30.)

In order to securely implement non-Const's Operator [], we must ensure that there is no StringValue object that can be modified without other String objects. Briefly, when we return a reference to a character in the StringValue object, you must make sure this StringValue reference count is 1. Here is our implementation:

Char & string :: Operator [] (int index)

{

// if We're Sharing a value with other string objects,

// Break Off a Separate Copy of The Value for Ourslves

IF (Value-> Refcount> 1) {

--Value-> refcount; // Decrement Current Value's

// Refcount, Because We Worn't

// be used this value any more

Value = // make a copy of the

NEW STRINGVALUE (Value-> Data); // Value for Ourslves

}

// Return A Reference to a Character Inside Our

// unshared stringValue Object

Return Value-> Data [index];

}

This "Sharing a value with other objects until the write operation is written", there is already a long and famous history in computer science, especially in the operating system: the process shared memory page until they want to be in herself This copy is modified in the page. This skill is so common, so there is a name: written when writing. It is a more general way to improve efficiency - the special example of thelazy principle.

* Pointers, reference to write, copy, write, copy, copy, can ensure efficiency and correctness. There is only one problem that can't play. Look at this code:

String S1 = "Hello";

Char * p = & S1 [1];

The data structure is like this:

S1 -----> 1 -------> Hello

|

p

Now look at an additional statement:

String S2 = S1;

String copy constructor makes S2 shares S1's StringValue object, so the data structure will be:

S1 ----- | 2 -------> Hello

S2 ----- | |

p

The following statement will have unpopular results:

* p = 'x'; // modifies Both S1 and S2!

String's copy constructor has no way to detect such problems because it does not know the presence of pointers to the STRINGVALUE object pointing to S1. And, this problem is not limited to the pointer: it also exists in the case where someone has saved a reference to the return value of the String Non-Const Operator [].

There are at least three methods to cope with this problem. The first is to ignore it and pretend that it does not exist. This is a painful common problem in achieving the class library of String classes with references. If you have a String class with reference to a reference, try the above example, see if you are very painful. Even if you can't determine if you are a string class with reference count, you should try this example. Due to packaging, you may use a type of type without self-knowledge.

Not, the implementation ignores this problem. A slightly better way is to express its existence. It is usually written to a document, or more or less, "Don't do this. If you do this, the result is undefined." No matter which way you do this (intentionally or unintentionally), and complain When the result, they defended: "Well, we told you to do this." This implementation is usually very convenient, but they have left too much expectations in terms of availability.

The third method is to exclude this problem. It is not difficult to implement, but it will reduce a value of a value to the number of objects. Its essence is this: add a flag in each StringValue object to indicate if it is shared. Open the flag in the initial (when the object can be shared), turn it off when the non-constant Operator [] is called. Once the logo is set to false, it will always remain in this state (Note 10).

This is a modified version of the shared logo:

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 // Add THIS

{

Data = new char [strlen 1];

STRCPY (data, initvalue);

}

String :: stringvalue :: ~ StringValue ()

{

DELETE [] DATA;

}

If you see, don't need too much change; two rows that need to be modified have a comment. Of course, String member functions must also be modified to process this sharing flag. Here is the implementation of copy constructor: String :: String (const string & rhs)

{

IF (rhs.value-> shareable) {

Value = rhs.value;

Value-> refcount;

}

Else {

Value = new stringvalue (rhs.value-> data);

}

}

All other member functions must also check this sharing flag with similar methods. Non-Const's Operator [] Version is the only place to set the shared 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 skill of the Proxy class in Item M30 to distinguish between read / write operations, you can usually reduce the number of StringValue objects that must be set to unable to share.

* Base class with reference count

The reference count is not only used on a string class, as long as multiple objects have the same value, the reference count can be used. Rewinding a class to get a quotation of the reference count requires a lot of work, and we already have too much work needs to be done. Is this not good: If we write the code of the reference count and the running environment is independent, and can you marry other classes when needed? Of course it is very good. Fortunately, there is a way to achieve it (at least the most must work).

The first step is to build a base class RCOBJECT, and any classes that need to be referenced must be inherited from it. RcObject encapsulates the reference counting function, such as increasing and reducing a function of the reference count. It also contains code when this value is not destroyed when it is required (that is, the reference count is 0). Finally, it contains a field to track whether this value object can be shared, and provides this value and a function set to false. You don't need to set the shared flag to true, because all value objects are shared by default. As mentioned above, once an object becomes unable to share, there will be no way to make it a shared again.

The definition of RCOBJECT is as follows:

Class rcObject {

PUBLIC:

RcObject ();

RcObject (Const RcObject & r Hs);

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;

}

RCOBJCET can be constructed (as a class part of the derived class) and a sector; there is a new reference to the above and remove the current reference; its sharedness can be queried and prohibited; they can report whether it is currently shared. . This is the function it provides. This is really what we expect them to complete for classes who want to have reference counts. Note that the false patterned function, which explicitly indicates that this class is designed for use as a base class (see Item E14). At the same time, pay attention to this destructor is pure, it clearly shows that this class can only be used as a base class. RcOject's implementation code:

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, eveniff

// they is Pure Virtual

// and do nothing (see Also

// Item M33 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;}

It may be strange that we will set REFCOUNT for 0 in all constructor. This looks violates intuition. Indeed, least, constructing the object of this RCObject object to reference it! After it is constructed, just construct it to simply set the Refcount to 1, so we didn't put this work inside the RCObject. This makes the final code look very short.

Another strange thing is that the copy constructor is also set to 0, regardless of the value of the RefCount of the RCObject object. This is because we are constructing a new value object, and this new value object is always not shared, only by its constructor. Third, the constructor is responsible for setting RefCount to the correct value.

RcObject's assignment seems to be completely unexpected: it didn't do anything. This function is unlikely to be called. RcObject is based on the base class of the value object shared by the reference count, which should not be assigned to another, but should be an object with this value to be given from one. In our design, we don't expect the StringValue object to be given from one to another, we expect only String objects in the assignment process. In the assignment statement participating in String, the value of StringValue has not changed, but its reference count is modified.

However, you can imagine that some classes that have not been written may be derived from RCOBJECT in the future, and hoped that the value of the reference count is assigned (see Item M23 and Item E16). If this is, RcObject's assignment operation should do the right thing, and this correct thing is nothing. Want to clear? Suppose we hope to be assigned between StringValue objects. What happened to the reference count value for a given StringValue object SV1 and SV2, what happened to the reference count value during assignment? SV1 = SV2; // How are sv1's and sv2's reference

// counts affected?

Before assigning the value, there is already a number of String objects to point to SV1. This value is not changed during assignment, because only the value of SV1 is changed. Similarly, a certain number of String objects are forward V2 before assignment, and after assignment, the same number of object points to SV2. The reference count of SV2 is also no change. When RcObject is involved in the assignment, point to its number of objects is not affected, so rcObject :: Operator = should not change the reference count value. The above implementation is correct. Violate intuition? Maybe, but it is correct.

RcObject :: RemoveReference's code is not only responsible for reducing the object's REFCOUNT value, but also responsible for the REFCOUNT value falls to 0. The latter is implemented by delete this, as explained in Item M27, this is only safe when we know * this is a heap object. To make this class correctly, we must ensure that rcObject can only be built in the heap. The common method of achieving this is shown in Item M27, but we use a special method this time, which will be discussed in this Territor.

In order to use our new reference count base class, we modified StringValue to be inherited from RCObject to get the reference count function:

Class string {

Private:

Struct stringvalue: public rcobject {

CHAR * DATA;

StringValue; Const Char * initValue;

~ StringValue ();

}

...

}

String :: StringValue :: stringvalue (const char * initvalue)

{

Data = new char [strlen 1];

STRCPY (data, initvalue);

}

String :: stringvalue :: ~ StringValue ()

{

DELETE [] DATA;

}

This version of StringValue is almost almost, the only change is the member function of StringValue no longer processes the refcount field. RcObject is now taken over.

Don't feel uncomfortable, if you pay attention to the nested class (StringValue) inherits from a class-independent class (RCOBJECT) that is unrelated to the inclusive class. It looks a little quirky, but it is very reasonable. Nested classes and other classes are exactly the same, so it has freedom from any other class that it likes. In the future, you don't have to think about this inheritance.

* Automatic reference counting

The RCObject class gives us a place where a store reference count is set, and the member function is provided for our operational reference count, but the action to call these functions must also be manually added in other classes. Still need to call the StringValue's AddReference and RemoveReference functions in the String's copy constructor and assignment. This is awkward. We want to move these calls into a reusable class so that the author of the class such as String does not have to worry about any detail of the reference count. Can you realize it? Is C support such reuse? can. None a simple method removes all of the reference counts from all classes; however, there is a method to move most of the work from most classes. (In some classes, you can eliminate all the code for all reference counts, but our String class is not one of them. There is a member function to get rid of this matter, I hope you don't surprise, it is our old head: Non-Const version of Operator [] Don't worry, we finally uniform this guy.)

Each String object contains a pointer to the StringValue object:

Class string {

Private:

Struct stringvalue: public rcObject {...};

StringValue * value; // value of this string

...

}

We must operate the REFCOUNT field of the StringValue object, as long as any of the pointers that point to its pointers have any interesting events. "Interesting Events" include copy pointers, assigning values ​​and destroy pointers. If we can make the pointer yourself detect these events and automatically perform the must-operate to the Refcount field, then we are free. Unfortunately, the pointer is very weak, and it is impossible to detect anything and respond. Fortunately, there is a way to enhance them: replace them with objects like pointers, but do more work more.

Such an object is called a smart pointer, you can see it more details at Item M28. As soon as we do this, just know that these are enough: Delicate pointer to support member selection (->) and reverse reference (*), just like a real pointer, and is the same as the built-in pointer Type: You cannot point a smart pointer pointing to T to a non-T type object.

Here is a smart pointer template for reference count objects:

// Template Class for Smart Pointers-TO-T Objects. T MUST

// support the rcObject interface, Typically by inheriting

// from rcObject

Template

Class rptr {

PUBLIC:

RCPTR (T *RPTR = 0);

RCPTR (Const Rcptr & RHS);

~ RCPTR ();

RCPTR & Operator = (const rcptr & rhs);

T * Operator -> () const; // see item 28

T & Operator * () const; // see itemograph

Private:

T * pointee; // Dumb Pointer this

// Object Is Emulating

void init (); // Common Initialization

}

This template makes the smart nuts to control what operations during construction, assignment, and destructors. When these events occur, these objects can automatically perform the correct operation to process the REFCOUNT fields of the objects they point to. For example, when an RCPTR is built, it points to the object needs to increase the reference count value. It is now not required to handle these details because the constructor of RCPTR handles itself. The two constructors are almost the same, in addition to the list of initialized lists, in order to not write two times, we put it into the private member function named init for both:

Template

RCPTR :: RCPTR (T *RPTR): Pointee (Realptr)

{

INIT ();

}

Template

RCPTR :: RCPTR (const rptr & rhs): Pointee (rhs.pointee)

{

INIT ();

}

Template

Void Rcptr :: 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 IT

Pointee-> addReference (); // Note That there is now a

} // new reason to the value

Mushing the same code into a stand-alone function such as init, but it is now dim, because here, this function is incorrect.

The problem is this: When INIT needs to create a new copy of Value (because the existing copy is in a non-shared state), it performs the following code:

Pointee = New T (* Pointee);

The type of Pointee is a pointer to T, so this statement constructs a new T object and initializes with a copy constructor. Since rcptr is inside the String class, T will be string :: stringValue, so the above statement will call the String :: StringValue copy constructor. We did not declare copy constructor for this class, so the compiler will generate one for us. This generated copy constructor complies with the principle of C automatically generated copy constructor, only copying StringValue's data Pointer, without copying the CHAR * string pointed to. Such behavior is a disaster for almost any class (not only the reference), why you should develop habits that provide copy constructor (and assignment) with classes containing pointers (see Item E11).

The correct behavior of the RCPTR template depends on the copy constructor that contains the correct value copy behavior (such as deep copy). We must add such a constructor in StringValue:

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 presence of a deep copy constructor is not only the only false setting of RCPTR . It also requires T from RCOBJECT, or at least provides the functions provided by RCObject. In fact, because the RCPTR object is only designed to point to reference count objects, this hypothesis is not too much. However, this hypothesis must be explicitly written to a document.

The last hypothesis of RCPTR is that the type of object it is T. This seems to be obvious. After all, the type of Pointee is declared as T *. But Pointee may actually point to a derived class of T. For example, if we have a class specialStringValue inherited from string :: stringValue:

Class string {

Private:

Struct stringvalue: public rcObject {...};

Struct SpecialstringValue: Public StringValue {...};

...

}

We can generate a string, inclusive RCPTR points to a SpecialStringValue object. At this time, we hope that this sentence in INIT:

Pointee = New T (* Pointee); // T is stringvalue, but BUT

// Pointee Really Points to

// a SpecialstringValue

Calling the copy constructor of SpecialStringValue, not the copy constructor of StringValue. We can provide this with a virtual copy constructor (see Item M25) to achieve this. For our String class, we don't expect from StringValue to derive subclasses, so we ignore this problem.

After using this manner, the other functions of the class have been implemented very fast after the RCPTR constructor is implemented. The assignment is very simple, although "the need to test the source of the test source" is slightly complicated. Fortunately, the same problem has been handled in the init function we wrote in the constructor. We can use it again:

Template

Rcptr & RCPTR :: Operator = (const rptr & rhs)

{

IF (Pointee! = rhs.pointee) {// Skip Assignments

// Where the value

// Doesn't change

IF (Pointee) {

Pointee-> removerecEnder (); // Remove Reference to

} // Current Value

Pointee = rhs.pointee; // point to new value

Init (); // if Possible, Share IT

} // else make o copyreturn * this;

}

The destructor is easy. When an RCPTR is destructed, it simply removes its reference to reference count objects:

Template

RCPTR :: ~ RCPTR ()

{

IF (Pointee) Pointee-> RemoveReference ();

}

If this RCPTR is an object that references it, this object will be destructed in REMOVEREFERENCE's member function RemoveReference. Therefore, the RCPTR object does not need to care about the problem that destroys the values ​​they point to.

Finally, the operation of RCPTR's simulation pointer is part of the dexterity pointer you see in Item M28:

Template

T * RCPTR :: Operator -> () const {return pointee;}

Template

T & RCPTR :: Operator * () const {return * pointee

* put them together

enough! end! Finally, we put together each part to construct a String class based on reusable RCOBJECT and RCPTR classes. Perhaps, you haven't forgot this is our initial goal.

The Sting object each with reference count is implemented as such a data structure:

RcObject

Class

________ |

| String | | Public Inheritance

| Objcet | |

| _______ | -----------> StringValue -------------> HEAP MEMORY

|| rcptr || Pointer Object Pointer

|| Object ||

=========

The definition of the class is:

Template // Template Class for SMART

Class Rcptr {// Pointers-TO-T Objects; T

Public: // must inherit from rcObject

RCPTR (T *RPTR = 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 reasonnce-

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 by

Public: // 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 Value;

}

Most of them are the renovation of the code written earlier, there is no strange place. After careful inspection, we have added an init function in String :: StringValue, but as we will see below, its purpose and RCPTR in the same: eliminate repetitions in the constructor.

There is a major difference here: this String class's public interface and the version we use is different from this terms. Where is the copy constructor? Where is the assignment? Where is the destructor? It's obvious here.

In fact, no problem. It works very well. If you haven't seen why, you need to graduate C (Prepre YourSelf for A C Epiphany).

We no longer need those functions! Indeed, the copy of the String object is still supported, and this copy will correctly handle the StringValue object hidden behind the referenced count, but the String class does not need to write even one line of code to make it happen. Because the compiler automatically generated the copy constructor automatically calls the copy constructor of its RCPTR member, and this copy constructor completes all the necessary to operate the StringValue object, including its reference count. Rcptr is a dexterous pointer, so this is the work that it will be completed. It depends on assignment and destructuring, so the String class does not need to write these functions. Our initial purpose is to move unrecounted reference counting code from our own String class to a class that is unrelated to the runtime environment for any other class. Now, we have completed this (two classes with rcObject and rcptr), so don't be surprised when it suddenly starts working. It should be able to work.

Will put things together, here is 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;}

This is the implementation of RCPTR:

Template

Void Rcptr :: init ()

{

IF (Pointee == 0) Return;

IF (Pointee-> isshareable () == false) {

Pointee = New T (* Pointee);

}

Pointee-> addReference ();

}

Template

RCPTR :: RCPTR (T * Realptr)

: Pointee (Realptr)

{INIT ();

Template

RCPTR :: RCPTR (Const Rcptr & RHS)

: Pointee (rhs.pointee)

{INIT ();

Template

RCPTR :: ~ RCPTR ()

{IF (Pointee) Pointee-> RemoveReference ();

Template

Rcptr & RCPTR :: Operator = (const rptr & rhs)

{

IF (Pointee! = rhs.pointee) {

IF (Pointee) Pointee-> RemoveReference ();

Pointee = rhs.pointee;

INIT ();

}

Return * this;

}

Template

T * RCPTR :: Operator -> () const {return pointee;}

Template

T & RCPTR :: Operator * () const {return * pointee

This is the implementation of String :: StringValue:

Void string :: stringvalue :: init (const char * initvalue)

{

Data = new char [strlen 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;}

Finally, attribute to String, its implementation is:

String :: string (const char * initValue): value (new stringvalue) {}

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 it to our version of the built pointer, you will be hit by two things. First, the code has a lot of reduction. Because RCPTR has completed a large number of burden counts for processing references completed within String. Second, the remaining code has almost no change: the smart pointer seamlessly replaces the built-in pointer. In fact, the only change is in Operator [], we use the calling questhared function instead of the value directly checking the REFCOUNT, and use the smart pointer RCPTR object to eliminate the writing time to write the writing time.

This is all very beautiful. Who can oppose the reduction of code? Who can oppose successful packages? However, this new String class itself is far better than its implementation details, which is the real flash point. If there is no message is good news, this is the best news. String interface has not changed! We have added a reference count, we increase the ability to mark a String value to be unable to share, we will transfer the reference count function into a new class, we add the dexterous pointer to move the reference count, but the user's line code does not need modify. Of course, we have changed the definition of the String class, so users need to recompile and link, but they have been fully protected on their own code. Did you see it? The package is really a good thing.

* Add a reference count on the existing class

Up to now, we have discussed that we can access the source code about class. But if we want to get a reference to the reference count for classes in the support library? It is impossible to let them inherit from rcObject, so they cannot use the smart pointer RCPTR. Is our luck?

no. As long as we make small modifications to our design, we can add the reference count to any type.

First consider if our design looks like from RCOBJECT. In this case, we need to add a class RCWIDGET for users, while all things are the same as the String / StringValue, the same, the RCWIDGET and String are the same, Widget and StringValue are the same. Design looks like this:

RcObject

Class

________ |

| RCWIDGET | | Public Inheritance

| Objcet | |

| _______ | -----------> COUNTHOLDER

|| rcptr || Pointer Objcet

|| Object ||

=========

We can now apply this sentence: Most of the issues in computer science can be solved by adding an intermediate level. We add a new COUNTHOLDER to handle references, which inherits from RCObject. We let CountHold contain a pointer to widget. Then replace the RCPTR template with the equivalent smart pointer RCIPter template, it knows the existence of the COUNTHOLDER class. ("I" in the name indicates indirect "Indirect".) Modified Design is: RcObject

Class

________ |

| RCWIDGET | | Public Inheritance

| Objcet | |

| _______ | -----------> COUNTHOLDER ----------> Widget

|| RCIPTR || Pointer Object Object

|| Object ||

=========

Like StringValue, CountHolder is the implementation details of the RCWIDGET for users. In fact, it is the implementation details of RCIPTR, so it is nested in this class. The implementation of RCIPTR is as follows:

Template

Class rciptr {

PUBLIC:

RCIPTR (T *RPTR = 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; // Thase Functions Are

T & operator * (); // Declared this Way

Private:

Struct County: public rcobject {

~ Countholder () {delete pointee;}

T * pointee;

}

Counture * counter;

Void init ();

Void makecopy (); // see below

}

Template

Void Rciptr :: init ()

{

IF (counter-> isshareable () == false) {

T * OldValue = Counter-> Pointee;

Counter = new countholder;

Counter-> Pointee = New T (* OldValue);

}

Counter-> addReference ();

}

Template

RCIPTR :: RCIPTR (T * Realptr)

: Counter (New Countholder)

{

Counter-> Pointee = Realptr;

INIT ();

}

Template

RCIPTR :: RCIPTR (Const Rciptr & RHS): Counter (rhs.counter)

{INIT ();

Template

RCIPTR :: ~ RCIPTR ()

{Counter-> RemoveReference ();

Template

RCIPTR & RCiptr :: Operator = (Const Rciptr & RHS)

{

IF (counter! = rhs.counter) {

Counter-> removereference ();

Counter = rhs.counter;

INIT ();

}

Return * this;

}

Template // Implement The Copy

Void Rciptr :: 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 // Const Access;

Const T * RCIPTR :: Operator -> () const // no cow needed

{Return Counter-> Pointee;}

Template // Non-const

T * RCIPTR :: Operator -> () // Access; COW

{makecopy (); return counter-> pointee;} // needed

Template // Const Access;

Const T & RCiptr :: Operator * () const // no cow needed

{Return * (counter-> pointee);

Template // Non-const

T & RCIPTR :: Operator * () // Access; do the

{Makecopy (); return * (counter-> pointee);} // cow thing

RCIPTR is different from RCPTR. First, the RCPTR object directly points to the value object, and the RCIPTR object points to the value object through the intermediate layer of COUNTHOLDER object. Second, RCIPTR is overloaded with Operator-> and Operator *, when there is a non-Const operation of the object being directed, the copy is automatically executed.

With RCIPTR, it is easy to implement RCWIDGET, because each function of the RCWIDGET is passed to RCIPTR to operate the Widget object. For example, if widget is like this: Class Widget {

PUBLIC:

Widget (int size);

Widget (Const Widget & RHS);

~ Widget ();

Widget & Operator = (Const Widget & RHS);

Void Dothis ();

INT ShowtHat () Const;

}

Then rcwidget will be defined as this:

Class rcwidget {

PUBLIC:

RcWidget (int size): value (new widget (size)) {}

Void Dothis () {value-> dothis ();

Int Showt () const {return value-> showthat ();

Private:

RCIPTR Value;

}

Note How is the constructor of the RCWidget to call the Widget's constructor (via the new operator, see Item M8); RcWidget's Dothis calls the widget's Dothis function; how to return to Widget ShowtHat's return value. Also pay attention to the RCWIDGET does not have a copy constructor and assignment operation function, and there is no destructive function. Like the String class, it does not need these functions. Thanks to RCIPTR behavior, the default version of RCWIDGET will complete the correct thing.

If the behavior of generating rcwidget is very mechanical, it should be automated, then you are right. It is not difficult to write a small program to accept classes such as widgets and output the RCWIDGET. If you write a such program, please let me know.

* Review

Let us get rid of widget, string, value, delegation pointer and reference count base class. Give a chance to review, look at the reference count in a broader environment. In a bigger environment, we must handle a higher level of problem, but when is the reference count?

Implementing the reference count is not available. Each referenced value counts a reference count, and most of its operations need to be checked or an operation reference count in some form. The value of the object requires more memory, and we need more code when handling them. In addition, in the internal source code, the complexity of the class with reference count is high. String classes without reference counts are only dependent on themselves, and our final String class does not use three auxiliary class (StringValue, RcObject, and RCPTR) cannot be used. Indeed, our more complex design ensures higher efficiency at the value of values; except for the need to track object ownership, improve the reusability of the reference counting and implementation. However, these four classes must be written, tested, documentated, and maintained, more work more than a single class. Even managers can also see this.

The reference count is an optimization technique based on the assumptions of the object usually shared the same value (see Item M18). If it is assumed to be incorporated, the reference count will use more memory than the usual method and perform more code. On the other hand, if your object does have a trend of the same value, the reference count will save time and space. The larger the shared value is, the more the number of shared objects, the greater the memory, the greater the memory. The greater the cost of creating and destroying this value, the more you save. In summary, the reference count is useful in the following cases.

A small amount of value is shared by a large number of objects. Such sharing is usually happened by calling assignment operations and copying construction. The higher the proportion of objects / values, the more appropriately use the reference count. The value of the object is highly created and destroyed, or they take up a lot of memory. Even so, if you don't share the same value, the reference count still can't help anything.

There is only one way to confirm whether these conditions are met, and this method is not guess or relying on intuition (see Item M16). This method is to use Profiler or other tools to analyze. Using this method, you can find whether the behavior of creating and selling is a performance bottleneck, and can get the proportion of objects / values. Only when you have this data in your hands, you can get the benefits of whether you get from the reference count exceeds its shortcomings.

Even if the above conditions are satisfied, the use of reference counts may still be inappropriate. Some data structures (such as moving pictures) will result in self-reference or cyclic structures. Such data structures may result in isolated self-cited objects, which are not used by others, and their reference count will never fall to zero. Because each object in this useless structure is referenced by at least one object in the same structure. Commercialized garbage collection systems use special techniques to find such structures and eliminate them, but this simple reference counting technology we use is not so easy to expand this feature.

Even if the efficiency is not the main problem, the reference count is still very attractive. If you don't feel relieved who should perform the delete action, the reference count is this skill that lets you put down the burden. Many programmers use the reference count only because of this reason.

Let us end the discussion with the last problem. When RCOBject :: RemoveReference reduces the reference count of the object, it checks if the new value is 0. If so, RemoveReference destroys the object by calling Delete this. This operation is only safe when the object is generated by calling New, so we need some way to ensure that rcObject can only be generated in this way.

Here, we are solved by habit. RcObject is designed to use the base class of the value object that is referenced to the referenced count, and these value objects should only be referenced by the smart pointer RCPTR. In addition, value objects should only be instantiated by objects shared by values; they cannot be used in a usual method. In our example, the class of the value object is StringValue, and we limit it to String private and limit it. Only String can create a StringValue object, so the author of the String class should ensure that these value objects are made through the New operation.

Thus, we restrict the method that rcObject can only create on the pile is to specify a class that meets this requirements and make sure that only these classes can create RCObject objects. Users cannot create RCOBJECT objects unintentionally (or intentionally) with an inappropriate way. We limits the power to create the referenced count object. When we share this power, it must be clarified that the accompanying condition is to satisfy the restrictions of the creation object.

* Note 10

The String type in the standard C Runturser (see Item E49 and ITEM M35) simultaneously use method 2 and method 3. Quote returned from non-Const's Operator [] is valid until the next possible modification of this String function is valid. After this, use this reference (or the character it pointing), the result is not defined. This allows it to be reset to true when calling a function that may modify the String.

转载请注明原文地址:https://www.9cbs.com/read-26736.html

New Post(0)