Effective C ++ 2e Item29

zhaozj2021-02-11  232

Class and function: implementation

C is a highly type language, so the definition of the appropriate class and template and the appropriate function declaration is the largest part of the entire design. By reasonable, as long as this is done, the class, templates, and functions are not easy to issue problems. However, people often make mistakes.

The reason for making mistakes is that it is accidentally violating the abstract principles: allowing the implementation details to extract data within the class and functions. Some errors are unclear length of the object life cycle. There is also an unreasonable pre-optimization, especially the inline keyword. The last situation is that some implementation strategies will result in the mutual linkage between the source files, which may be very suitable within a small scale, but it will bring unacceptable costs when rebuilding large systems.

All of these issues, as well as similar problems, as long as you know what aspects of this. The following provisions indicate several cases that should be paid to it.

Terms 29: Avoid returning the handle of internal data

Please see a scene that happens in the object of the object:

Object A: Dear, never change! Object B: Don't worry, dear, I am const.

However, like real life, A will suspect, "Can I believe B?" The same, as in real life, the answer depends on B's nature: The composition of its member function.

Suppose B is a Const String object:

Class string {public: string (const char * value); // Specific implementation See Terms 11 ~ String (); // Construction Function Note See Terms M5

Operator char * () const; // Convert String -> char *; // See Terms M5 ...

PRIVATE: CHAR * DATA;

Const string b ("Hello World"); // B is a const object

Since B is const, the best situation is of course, no matter whether it is still still, B is always "Hello World". This hopes that other programmers can use B in a reasonable way. In particular, don't have any people to convert B to CONST (see Terms 21):

String & alSob = // makes AlSob become another name of B, const_cast (b); //, but does not have const attributes

However, even if there is no one to do this cruel, can B will never change? Take a look at the situation below:

Char * str = b; // Call B. Operator char * ()

STRCPY (STR, "Hi Mom"); / / Modify the value of the Str pointing

B value is still "Hello World"? Or, whether it has become a greeting for the mother? The answer is exactly the implementation of String :: Operator char *.

Below is an uncomprivable implementation, which leads to the result of the error. However, it works very efficient, so many programmers fall into its wrong trap:

// A very fast but incorrect implementation inline string :: Operator char * () const {return data;}

The defect of this function is that it returns a "handle" (in this case, it is a pointer), and the information pointed to this handle is originally hidden in the inside of the String object where the function is called. In this way, this handle gives the caller free access to the private data referred to Data. In other words, with the following statement: char * str = B;

The situation will become like this:

STR -------------------------> "Hello WORLD / 0" / / / / / / / / / / / / / / / / / /

Obviously, any modification of the memory pointed to by the STR changes the values ​​of B. So, even if B is declared as const, and even if it is just calling a const member function of B, B will also get different values ​​during the program run. In particular, if the Str modifies the value it refers to, B can also change.

String :: Operator char * It is not a mistake written in itself, and it is troublesome that it can be used for Const objects. If this function does not declares that there will be no problem, because it cannot be used in the const object like B.

However, the form of converting a String object into its corresponding char * is a reasonable thing, regardless of whether this object is const. So, you should still keep the function as const. In this way, you have to override this function, so that it does not return a handle pointing to the internal data of the object:

// A slow but very safe implementation inline string :: operator char * () const {char * copy = new char [strlen (data) 1]; strcpy (copy, data);

Return Copy;

}

This implementation is safe because the data indicated by the pointer it returns is just a copy of the String object points to the data; the pointer returned by the function cannot modify the value of the String object. Of course, safety is the price: this version of String :: operator char * runs slower than the simple version of the previous; In addition, the caller of the function also remembers the pointer returned by Delete.

If you can't endure this version of the speed, or worry about memory leaks, you can come to a little change: Make the function returns a pointer pointing to Const Char:

Class string {public: Operator const char * () const;

...

}

Inline string :: operator const char * () const {return data;}

This function is fast and safe. Although it is not the same as that of the function given, it can meet the needs of most programs. This approach is also consistent with the String / Char * puzzle of the C standard organization: the standard string type contains a member function c_str, and its return value is a string const char * version. See Terms 49 for more information on standard String types.

The pointer is not the only way to return the internal data handle. Quote is also easily abused. Here is a common usage, or take the String class:

Class string {public:

...

Char & Operator [] (int index) const {return data [index];

PRIVATE: CHAR * DATA;

String s = "i'm not constant";

s [0] = 'x'; // correct, S is not constant

Const string cs = "I'm constant"; cs [0] = 'x'; // Modify const string, / / ​​but compiler will not notify

Note that string :: operator [] is the result of the return by reference. This means that the caller of the function is another name of internal data Data [index], and this name can be used to modify the internal data of the const object. This issue is the same as before, but this culprit is a reference, not a pointer.

The general solution for this type of question is the same as the previous discussion of the pointer: or make functions are non-const, or rewrite the function, so that the handle is returned. If you want String :: Operator [], you can see Terms 21 for both Const objects for non-Const objects.

Not only the Const member function needs to worry about the problem of returning the handle, even if the non-Const member function is recognized: the time of the legality of the handle is completely identical, the object it corresponds to the object. This time may be much better than the user expectations, especially when the object involved is a temporary object generated by the compiler.

For example, look at this function, it returns a String object:

String somefamousauthor () // Randomly select a writer name {// and return it

Switch (Rand ()% 3) {// rand () in / / (also . See Terms 49) Case 0: Return "Margaret Mitchell"; // This writer has written "Floating", // A absolute classic works Case 1: return "Stephen King"; // His novel makes many people // completely sleepless case 2: return "scott meyers"; // um ... ... 竽数One }

Return "" "; // The program will not perform it here, // But for a function with a return value, there is a return value on any execution path}

I hope that your attention should not focus on how the random number is from the issue of rand, and don't laugh at me to contact these writers. What truly, the return value of SomeFamousauthor is a String object, a temporary String object (see Terms M19). Such objects are temporary, and their life cycle is typically terminated at the end of the function call expression. For example, in the above case, when the expression of the SomeFamousAuthor function call is included, the life cycle of the return value object is over.

Specifically, look at this example of SomeFamousauthor, assuming String declares an Operator const char * member function:

Const char * pc = somefamousauthor ();

COUT << PC;

No matter whether you believe, no one can predict what this code will do, at least you can't determine what it will do. Because when you want to print the string fingered by the PC, the value of the string is uncertain. The reason for this result is that the following events occur when PC initialization: 1. Generate a temporary String object to save the return value of SomeFamousauthor's return. 2. Translate the temporary String object to a const char * pointer with this pointer to initialize the PC with this pointer. 3. Temporary String object is destroyed, and its destructor is called. In the destructor, the DATA pointer is deleted (the code is detailed in terms 11). However, Data and PC refer to the same block, so the PC now points to the deleted memory -------- its content is unsure. Because the PC is initialized by a handle pointing to the temporary object, the temporary object is immediately destroyed after being created, so the handle before the PC is already illegal. That is, no matter what I want to do, the PC actually named the PC. This is the hazard that points to the handle of the temporary object.

Therefore, for the Const member function, the return handle is unwise because it disrupts the data abstraction. For non-Const member functions, the return handle will bother, especially when it is involved in temporary objects. The handle is like a pointer, can be a dangle. So you must avoid the suspended handle as you should like to avoid suspended pointers.

It is also not possible to absolute this article. It is unrealistic to eliminate all possible suspension pointers in a large program. It is not realistic. However, as long as you don't have to have already, you must avoid returning the handle, so that you will benefit from the program, but users will trust you.

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

New Post(0)