Effective C ++ 2e: Class and Functions: Design and Notice

xiaoxiao2021-03-06  70

Categories and functions: Design and Notification Declare a new class in the program will result in a new type: Class design is the type design. Maybe you don't have much experience on type design, because most languages ​​have no opportunities to provide you. In C , this is very basic, not because you want to do this, but because you declare a class, you are actually doing, no matter what you want to do. Design a good class is very challenging because the type of design is very challenging. Good types have natural grammar, intuitive semantics and efficient implementation. In C , the definition of a bad class is unable to implement these goals. Even if the performance of a member function of a class is also determined by the declarations and definitions of these member functions. So how do you start designing efficient classes? First, you must know what you face. In fact, it will encounter the following problems when designing each class. Its answers will affect your design. · How will objects created and destroyed? It will greatly affect the design of the constructor and the designer function, as well as the custom Operator New, Operator New [], Operator Delete, and Operator Delete []. (Terms M8 describes the differences of these terms) · What is the difference between object initialization and object assignment? The answer determines the behavior of the constructor and the assignment operator and the differences between them. · What does it mean by value to pass a new type of object? Remember, the copy function is responsible for answering this. · What is the limit on the new type of legitimate value? These restrictions determine the types of errors inside the member function (especially constructor and assignment operators). It may also affect the exceptions of the exceptions thrown and exception specification for functions (see Terms M14), if you use them. · Is the new type in line with inheritance relationship? If it is inherited from existing classes, then the design of new categories is limited to these classes, especially those that are limited to inherited are virtual or non-virtual. If the new class allows for inheritance of other classes, this will affect whether the function is to be declared as virtual. · Which type of conversion is allowed? If the object implicitly converted to type B is implicitly converted to type B, it is necessary to write a type conversion function in class A, or write a non-Explicit constructor that can be called with a single parameter in class B. If you only allow explicit conversion, you want to write a function to perform a conversion function, but you don't have to write them into a type conversion operator and a non-Explicit constructor of the single parameter. (Terms M5 discuss the advantages and disadvantages of user custom conversion functions) · What operators and functions make sense to new types? The answer determines what function will be declared in the class interface. · Which operators and functions should be explicitly banned? They need to be declared as private. · Who has the right to access a new type of member? This problem helps to decide which members are public, which is protection, which private. It also helps to determine which classes and / or functions must be friends, and whether to nested a class to another class. · How is the versatility of new types? Maybe you don't actually define a new type, but define a set of types. If so, don't define a new class, to define a new class template. These are difficult to answer questions, so defining a efficient class is far less simple in C . But if you do it well, the type generated by the user-defined classes in C will almost no difference in the fixed type. If you can achieve this effect, its value is reflected. Each question is to be monitored if you want to discuss with a detailed discussion. Therefore, the guidelines described in the following clauses will never be part of it. However, they emphasize some important precautions in the design, remind some frequent mistakes, and provide solutions to some problems that designers often meet.

Many recommendations apply to non-member functions and member functions, so I also consider the design and statements of the function in the global function and the namespace. Terms 18: The user interface that strives to make the interface is complete and the smallest type of user interface refers to the interface you can access by the programmer using this class. Only functions exist in a typical interface, because there is a lot of shortcomings in the user interface (see clause 20). Which functions should be placed in the interface of the class? Sometimes this problem will make you crazy, because there are two distant goals to be done. On the one hand, the class is designed to be easy to understand, easy to use, and easy to implement. This means that the number of functions should be as small as possible, and each function completes different tasks. On the other hand, the functionality of the class is powerful, it is convenient to use, which means adding functions from time to time to provide support for various general functions. How would you decide which functions should be put into the class, what do you not put? Try this suggestion: The target of class interface is complete and minimal. A complete interface refers to an interface that allows users to do any reasonable things they want to do. That is to say, there is a reasonable way to achieve any reasonable tasks that the user want to complete, even if this method is even more convenient to the user. Instead, a minimal interface means that the function is as small as possible, and each two functions do not have an overlapping function. If you can provide a complete, minimum interface, users can do anything they want, but the interface of the class does not have to be more complicated. The complete look of the pursuit of interface is natural, but why should the interface minimize? Why don't you let users do anything they want, add more functions, so that everyone is happy? Factors in the principle of the world do not talk - do you really do your users really correct? - The interface that is full of a large number of functions has a lot of shortcomings. First, the more functions in the interface, the more difficult to understand the potential users. The more disadvantageous understanding, the more I don't want to learn how to use it. A class with 10 functions is easy to use for most people, but a class with 100 functions is difficult to control many of the programmers. When the functionality of the extended class makes it as much as possible, be careful not to combat user learning to use their enthusiasm. The big interface will also be confused. Assuming a class that supports identification functions in a manual smart program. One of the member functions called Think, and some people wanted to call the function name to Ponder (thoughtfully), and some people like to call Ruminate (meditation). In order to meet the needs of everyone, you have three functions, although they do the same thing. So think about it, what will I think about using this class in the future? This user will face three different functions, each function seems to be the same thing. really? What is the subtle difference in these three functions, efficient, versatility, or reliability? If there is no different, why do you have three functions? In this case, this user will not only grateful you provide the flexibility, will you wonder if you want (or think deeply, or meditating)? The second disadvantage of a large class interface is difficult to maintain (see Terms M32). The class containing a large number of functions is more difficult to maintain and upgrade the class containing a small amount of functions, which is more difficult to avoid duplication of code (and repeated bugs), and it is difficult to maintain the consistency of the interface. At the same time, it is difficult to establish a document. Finally, long class definitions will cause long header files. Because the program reads the header file in each compile (see Terms 34), the definition of the class will lead to a lot of compile time during the project development process.

In summary, it is said that there is no price in the interface, so it is necessary to consider it carefully when adding a new function: the convenience it brings (only a new function should be considered if the interface is complete. Whether to provide convenience exceeding the additional cost it brings, such as complexity, readability, maintainability, and compilation. But there is no need to be too embarrassed. Add some functions on the smallest interface is sometimes reasonable. If a universal function is implemented more efficient, this will add a good reason to add it to the interface. (However, sometimes it will not, see the Terms M16) If a member function is added, it is easy to use, or the user error can be prevented from adding it into an interface. Look at a specific example: a class template, implements the array function of the user-defined subscript upper limit, and provides the upper and lower limit check options. Beginning portion of the template is as follows: template class Array {public: enum BoundsCheckingStatus {NO_CHECK_BOUNDS = 0, CHECK_BOUNDS = 1}; Array (int lowBound, int highBound, BoundsCheckingStatus check = NO_CHECK_BOUNDS); Array (const Array & rhs) ; ~ Array (); array & operator = (const array); private: int lbound, hbound; // lower limit, upper limit vector data; // array content; about vector, // See Terms 49 Boundscheckingstatus CheckingBounds; }; The membership function that is currently declared is basically no need to think (or think deeply, meditation). A constructor allows the user to determine the constructor, a copy constructor, an assignment operator, and a destructor. The designer function is declared as non-virtual, meaning that this class will not be used as a base class (see clause 14). For the declaration of assignment operators, the first look will feel that the purpose is not so clear. After all, the array of fixed types in C is not allowed to assign values, so it should not be allowed to assign a value (see Terms 27). But on the other hand, the array of vector templates (existing in the standard library - see Terms 49) allows the VECTOR object assignment. In this example, it is decided to follow the provisions of the Vector, as will be seen below, this decision will affect other parts of the interface of the class. Old C programmer saw this interface to be scared: How can I not support fixed-size array declarations? It is easy to add a constructor to achieve: Array (int size, bitchcheckingstatus check = no_check_bounds); but this cannot be a minimum interface, because the constructor with the upper limit parameter can complete the same thing. Despite this, it is possible for some purposes to cater to those long-term needs, especially for considerations consistent with the basic language (C language).

What functions do you still need? For a complete interface, of course, the index of the array is also required: // Returns the element T & Operator [] (int index) that can be read / written; // Returns the read-only element const t & operator [] (int index) const; By declaring the same function twice, a CONST has no const at once, providing support for Const and non-const array objects. The return value is very important, and the terms 21 will be described. Now, the Array Template supports constructor, destructuring functions, pass values, assignments, indexes, you may think of this is already a complete interface. But look at it again. If a user wants to traverse an integer array, print each element, as shown below: Array a (10, 20); // The lower limit of the subscript is: 10 to 20 ... for (INT i = The lower limit of the subscript; I <= A. The upper limit of the subscript; i) cout << "a [" << i << "] =" << a [i] << '/ n'; user Get the lower limit of a subscript? The answer depends on what is the assignment operation of the Array object, that is, what is done in Array :: Operator =. In particular, if the assignment can change the upper limit of the Array object, it must be provided with a member function that returns the current upper limit value because the user cannot always launch the upper and lower limits in some places of the program. For example, as the above example, a is assigned to the time period before the cycle is defined, and the user cannot know the current up and down limit in the cycle statement. If the up and down limit of the Array object cannot be changed, it is fixed when A is defined, and the user may have a way (although it is very troublesome) track it. In this case, it is convenient to provide a function to return the current upper and lower limit, but the interface cannot be minimized. Continue the previous assignment operation to change the hypothesis of the upper limit of the object, the upper and lower limit functions can be declared: int lowbound () const; int highbound () const; because these two functions do not modify the objects, and follow "Can be used to try to use const" (see Terms 21), they are declared as a Const member function. With these two functions, the loop statement can be written below: for (int i = a.lowbound (); i <= a.highbound (); i) cout << "a [" << i < <"] =" << a [i] << '/ n'; of course, to work such an object array of an operation type T works, it is necessary to define an Operator << function for the object of type T. (It is not very accurate. It should be, there must be a type T of Operator <<, or Type T can implicitly convert (see Terms M5), other types of Operator <<) some people will argue, and the Array class should provide A function returns the number of elements in the Array object. The number of elements can be simply obtained: highbound () - lowbound () 1, so this function is not so really necessary.

But considering that many people often forget " 1", increasing this function is not a bad idea. There are also other functions that can be added to the class, including those operations in the input and output, as well as various relational operators (for example, <,>, ==, etc.). However, these functions are not part of the minimum interface, as they can be implemented by a loop containing Operator []. Specifies the functions such as Operator <<, Operator >>, and the Terms 19 explains why they often use non-members' friend functions without member functions. Also, don't forget that the friend's function is part of the interface of the class in all practical applications. This means that friend functions affect the integrity and minimum of the interfaces. Terms 19: Distinguished member functions, non-member functions, and friend function member functions and non-member functions The biggest difference is that the member function can be a virtual rather than a member function. So, if a function must perform a dynamic binding (see Terms 38), you must use a virtual function, and the virtual function must be a member function of a class. This is so simple about this. If the function does not have to be virtual, the situation is slightly complicated. Look below to represent a category of rational numbers: class russ {public: Rational (int name = 0, int devenator = 1); int nametor () const; int Denominator () const; private: ...}; this is a little Use the class. (Use the terminology of the Terms 18, the interface is indeed, but it is not complete enough.) So, to increase the addition, minus, multiplied arithmetic operation support, however, the member function is still non-member function, or non-member The friend function is implemented? When you don't get your mind, use the object-oriented way! The rumor multiplication is to contact the Rational class, so write a member function to the class. Class Rational {public: ... const}; (if you don't understand why this function is declared in this way - returns a const value to take a consisting as its parameters - Reference Terms 21-23.) Now Multiplication of rational numbers: Rational Oneeighth (1, 8); Rational Onehalf (1, 2); Rational Result = Onehalf * OneeightH; // Operation Good Result = Result * OneeighTh; // is good running, but don't satisfy, you have to support mixed type operations, for example, Rational Rational can multiply with INT. But when writing the following code, only half work: result = onehalf * 2; // Operate good result = 2 * onehalf; // error! This is a bad body. remember? Multiplication must meet the exchange law. If you rewrite two examples in the following equivalence function, the problem is obeky: result = onehalf.operator * (2); // Run good Result = 2.operator * (onehalf); // Error! Object OneHalf is an instance of a class containing the Operator * function, so the compiler calls that function.

The integer 2 does not have a corresponding class, so there is no Operator * member function. The compiler also searches for a non-member Operator * function that can be called below (ie, Operator * function in a visible namespace or global Operator * function): result = operator * (2, onehalf ); // Error! But there is no such parameter for the non-member Operator * function of INT and Rational, so search failed. Let's take a look at the successful call. Its second parameter is integer 2, but Rational :: Operator * The desired parameter is the Rational object. what happened? Why can't I work in one place and another place? The secret is implicit type conversion. The value of the compiler knows that the function is required to function, but it also knows that the Rational's constructor is converted into a suitable Rational Rational, so there is a successful call (see Terms M19). In other words, the compiler handles this call when this call is similar to the following: Const Rational Temp (2); // Generate a temporary // Rational object from 2 = Onehalf * Temp; // with onehalf.operator * (TEMP) Of course, only if the constructor involved is not declared as ExPlicit, because the Explicit constructor cannot be used for implicit conversion, this is explicit meaning. If the Rational image is defined below: Class Rational {public: Explicit Rational (int name = 0, // This constructor is int Denominator = 1); // Explicit ... const rulingal operator * (Const Rational & RHS) const; ..}; then, the following statement cannot be compiled: result = onehalf * 2; // error! Result = 2 * onehalf; // error! This will not provide support for hybrid, but at least two statements The consistency. However, this class we just study is to design implicit conversions that can allow fixed types to Rational - this is why Rational constructor does not declare the reason for explicit. In this way, the compiler will perform the necessary implicit conversion to make the first assignment statement of the above RESULT by compile. In fact, if needed, the compiler performs this implicit type conversion for each parameter of each function. However, it only converts the parameters listed in the function parameter table, which will never be converted to the objects where the member function is located (ie, the object corresponding to the * THIS pointer in the member function). That's why this statement can work: result = onehalf.operator * (2); // Converts int -> Rational = 2.Operator * (onehalf); // Will not convert // int -> The first case of the Rational is a parameter column in the function declaration, and the second case is not.

Despite this, you may still want to support hybrid arithmetic operations, and the implementation method should now be clear: make Operator * becomes a non-member function, allowing the compiler to perform implicit type conversions for all parameters: class russ {. .. // Contains no operator *}; // In the global or a namespace declaration, // See the Terms M20 Learn Why do I want to do this Const Rational Operator * (Const Rational & LHS, Const Rational & RTURN Rational (LHS. Numerator () * rhs.Numerator (), lhs.denominator () * rhs.denominator ());} Rational OnefouRTh (1, 4); Rational Result; Result = OnefouRTh * 2; // Working Result = 2 * OnefouRTH , // is long, it works! This is of course a perfect ending, but there is also a worry: Operator * should be a friend of the Rational class? In this case, the answer is unnecessary. Because Operator * can be implemented completely through the publication of the public. The code above is doing this. As long as you can avoid using a friend function, it is avoided, because it is almost in real life, the trouble brought by Friends (friends) is much more helpful than it (he / her). However, in many cases, it is not a member's function that may also be part of the class interface, and they need to access the non-public members of the class. Let us look back and look at the main example of this book, String class. If you want to override Operator >> and Operator << to read and write String objects, you will quickly find that they can't be a member function. If you are a member function, you must put the String object in their left: // an incorrect Operator >> and // Operator << as a class class string {public: string (const char * value); ... istream & iput; ostream & operator << (Ostream & Output); private: char * data;}; string s; s >> cin; // legal, but // violated Regular s << cout; // The same thing will confuse others. So these functions cannot be a member function. Note this situation and the previous difference. The goal here is natural call syntax, the front concern is implicit type conversion.

So, if you want to design these functions, just like this: iStream & Input, String & String {delete [] string.data; read from infut into some memory, and make string.data point to it returnin input;} Ostream & Operator << (Ostream & Output, Const String & String) {Return Output << String.Data;} Note that the above two functions have to access the String class's DATA member, and this member is private. But we already know that this function must be a non-member function. This way, there is no choice: non-member functions that need to access non-public members can only be a class of friend functions. The conclusions given this article are as follows. Suppose F is a function that wants to declare correctly, c is a class related to it: · The virtual function must be a member function. If f must be a virtual function, let it become a member function of C. · Operator >> and Operator << Never be a member function. If F is Operator >> or Operator <<, let F become non-member functions. If f also needs to access the C's non-public member, let F become the C 's favorite. · Only non-member functions conversion to the leftmost parameters. If f requires type conversion to the leftmost parameters, let F become non-member functions. If f also needs to access the C's non-public member, let F become the C 's favorite. · Statement as a member function in other cases. If the above situation is not, f becomes a member function of C. Terms 20: Avoiding a data member in the public interface first, from the perspective of "consistency". If the PUBLIC interface is a function, users do not need to catch their heads every time the member of the class: Is it necessary to use parentheses? - The use of parentheses is! Because each member is a function. In a lifetime, this can avoid how many times you caught your head! Don't buy "consistent" account? Then you have to admit that the function can more accurately control the right to access the access to the data. If you make the data member as public, everyone can read and write it; if you use a function to get or set it, you can achieve a variety of controls such as access, read-only access, and read and write access. Even, if you wish, you can achieve write-only access: class AccessLevels {public: int getReadOnly () const {return readOnly;} void setReadWrite (int value) {readWrite = value;} int getReadWrite () const {return readWrite;} Void setWriteonly (int value) {Writeonly = value;} private: int noaccess; // Do not access this int tent readonly; // You can read this int inwrite; // You can read / write this int int WriteOnly; // I only write this int}; I haven't convince you? That had to move out this heavy cannon: Functional Abstract.

If you use a function to enable access to data members, you may use a period of calculation to replace this data member, but users who use this class have not known. For example, it is assumed to write an application for detecting a car driving speed with an automated instrument. When each car is running, the calculated speed value is added to a collection of all current automotive speed data: Class SpeedDataColection {public: void addValue (int speed); // Add new speed value Double averasofar () Const ; // Return the average speed}; now consider how to implement the member function aveRageSofar (see also Terms M18). One method is to use a data member of the class to save the run average of all currently collected speed data. As long as the AVERAGESOFAR is called, the value of this data member is returned. Another different approach is to calculate the results by checking all the data values ​​in the collection at a time called. (About the more comprehensive discussion of these two methods See Articles M17 and M18.) The first method - keeps a run value - makes each SpeedDataCollection object more because the space must be assigned to the data member that must be saved. But the averatesofar is very efficient: it can be an inline function that only returns a data member value (see Terms 33). In contrast, the average value is calculated each time the call makes the averatesofar slower, but each SpeedDataColection object is smaller. Who can say which method is better? In a very tight machine in the memory, or in applications that are not frequently needed, each calculation average is a good solution. In applications that frequently require average, the speed is the most fundamental, memory is not a major problem, and the method of maintaining a run value is more preferable. Important, use member functions to access the average, you can use any way, it has great value flexibility, which is the scheme that contains average data members in the public interface. Therefore, the conclusion is that in the public interface, it is not far from searching for trouble, so it is necessary to safely hide the data after the high wall with the functional separation. If you start now, then we can exchange consistency and accurate access control without any cost. Terms 21: The benefit of using Const as much as possible is that it allows you to specify a constraint - some object cannot be modified - the compiler is specifically implemented. With const, you can notify the compiler and other programmers to remain unchanged. As long as this is this, you will use const as a clear, because doing so can make sure that this constraint is not destroyed with the help of the compiler. The Const keyword is really unity. Outside the class, it can be used for global or name spatial constants (see clauses 1 and 47), as well as static objects (local objects within a certain file or block range). Inside the class, it can be used for static and non-static members (see clause 12).

For pointers, you can specify the pointer itself as const, or you can specify the data referred to by the pointer to const, or both at the same time as const, and both, the two are not specified as const: char * p = "hello"; // Non-constical pointer, // non-const data const char * p = "hello"; // non-const pointer, // const data char * const p = "hello"; // const Pinger, // Nonconst Data Const Char * const p = "hello"; // constant, // const Data syntax does not seem to change the multi-end. In general, you can draw a vertical line in the mind through the asterisk (*) position in the pointer statement, if constant appears on the left side of the line, the data pointed to by the pointer is constant; if consts appears online right, the pointer itself Constant; if both of the CONST online appears, both are constants. In the case where the pointer is referred to as constant, some programmers like to put the const before the type name, and some programmers like to put the const after the type name, the asterisk. Therefore, the following function takes the same parameter type: Class Widget {...}; Void F1 (const widget * pw); // F1 takes a pointer Void F2 to // Widget constant object (Widget const * PW); // With F2 because two representations exist in actual code, they are used to these two forms. Some powerful features of Const are based on its application in the function declaration. In a function declaration, const can refer to the return value of the function, or a parameter; for the member function, it can also refer to the entire function. Let function return a constant value can often reduce the chance of making mistakes without reducing security and efficiency. In fact, as explained in Terms 29, it is possible to improve the security and efficiency of a function on the return value, otherwise there will be a problem. For example, see this declaration of the reasonable Operator * function introduced in Terms 19: Const Rational Operator * (Const Rational & LHS, Const Rational & Rhs); Many programmers first see it will wonder: Why Operator * returns the result Is a const object? Because if not this, users can do the following bad things: Rational A, B, C; ... (a * b) = C; // Assign a value for A * B, I don't know why some programmers will think The result of the two calculations is directly assigned, but I know: If A, B and C are fixed types, so it is obviously illegal. A nice user-defined type is characteristic that it avoids that unequality is not compatible with fixed types. For me, it is very unreasonable to assign a value for two counting operations.

Declare the return value of Operator * to prevent this, so this is correct. There is nothing special about const parameters to emphasize - their operations are the same as part of the local const. (However, see the clause M19, the const parameter will result in a temporary object production) However, if the member function is const, it is another thing. The purpose of the Const member function is of course to indicate which member function can be called on the Const object. But many people ignore such a fact that only a different member function can be overloaded only in Const. This is an important feature of C . Once again, see this String Class: Class string {public: ... // Used for non-const objects [] char & operator [] {return data [position];} // Operator for Const object [ ] Const char & operator [} const {return data [position];} private: char * data;}; string s1 = "hello"; cout << S1 [0]; // call non-const // string :: Operator [] const string s2 = "world"; cout << S2 [0]; // Call const // String :: operator [] can be used by overloading Operator [] and gives different versions of different returns to values, Different processes for const and non-const string: string s = "hello"; // NonconT string object cout << S [0]; // correct - read one // non-const strings [0] = 'x '; // correct - write one // non-const stringconst string cs = "world"; // const string object cout << cs [0]; // correct - read one // const stringcs [0] =' x '; // Error! - Write a //con ST STRING is noted that the error here is only related to the return value of calling Operator []; Operator [] call itself. The reason for the error is that attempts to a const char & assignment, because the object being assigned is the return value of the Const version of the Operator [] function. Also note that the return type of non-Const operator [] must be a reference to a char, CHAR itself can't. If Operator [] really returns a simple char, the statement as shown below will not be compiled: s [0] = 'x'; because the return value of the function modified a "return value is fixed type" is absolutely It is not legal.

Even if the C "is returned to the internal mechanism of the object" (instead of reference), S.DATA [0] is modified, not S.Data [0] This is not the result you want. Let us stop and see a basic principle. What is the exact meaning of a member function for Const? There are two main views: Const (Bitwise Constness) and Concept (Conceptual Constness) in the Const (Bitwise Constness) and conceptual sense. Bitwise Constness persists, this member function is constant when and only when the member function does not modify any data member (except static data members), that is, when any bit (bit) is not modified. Bitwise Constness is the most advantageous thing to easily detect events that violate Bitwise Constness: The compiler is only used to find the assignment of a non-paid data member. In fact, Bitwise Constness is the definition of C to Const issues, and the Const member function is not allowed to modify any of its objects. Unfortunately, many members of the Bitwise Constness definition can also be tested through Bitwise. In particular, a member function that "modify the data pointed to by the pointer", its behavior clearly violates the Bitwise Constness definition, but if the object contains only this pointer, this function is also bitwise const, compile time. This is a difference with our intuition: Class string {public: // constructor, make Data points to copy string (const char * value) of data pointed to by // Value; ... operator char * () const { Return Data;} private: char * data;}; const string s = "hello"; // Declaration constant object char * Nasty = S; // calls Operator char * () const * Nasty = 'm'; // Modify S.DATA [0] cout << S; // Output "Mello" Obviously, if you create a constant object with a value and invoke the Const member function of the object, there must be any errors, the value of the object can be modified! (About this example is discussed in more detail in detail 29) This results in the introduction of the Conceptual Constness view. The persistence of this point of view believes that a const member function can modify some of the data (BITS) of its object, but only if the user will not be found.

For example, assume that the string class wants to save the length of the data per time: Class string {public: // constructor, make Data points to the data pointed to by // Value String (const char * value): LengthsValid ( False) {...} ... size_t length () const; private: char * data; size_t datalength; // The last calculated // String length BOOL Length longThisvalid; // length Current // 合}; size_t String :: longth () const {if (! Longthisvalid) {data = strlen (data); // error! Length;} Return Datalength;} This Length's implementation is clearly not in line with "Bitwise Const" Definitions - Datalength and LengthThisvalid can be modified - but for Const String objects, it seems that it must be legitimate. But the compiler does not agree, they insist on "Bitwise Constness", what should I do? Solution is simple: use C Standard organizations to specifically provide another optional option for Const Problems for this type. This scenario uses a keyword Mutable. When the non-static data member uses Mutable, these members' "bitwise constness" restrictions are released: Class string {public: ... // Same As AbovePrivate: char * data; mutable size_t Datalength; // These data members are now // for Mutable; they can be modified in Mutable Bool Length, even where // is in the Const member function}; size_t string :: length () const {i (! Length "valid ) {DATAGTH = Strlen (data); // Now legthiThisvalid = true; // is equally legal} Return Datalength;} Mutable is a good solution when processing "BitWise-Constness" problem, but it is added to C The time is not long, so some compilers may not support it. If so, you have to go back to C dark old age, where, life is very simple, and constant may be abandoned. Among a member function of class C, the THIS pointer is as follows: c * const this; // Nonconst C * const this; // constist in this case (ie compiler) If Mutable is not supported), if you want to make the problem String :: Length is legal for Const and non-const objects, only the THIS is changed from Const C * Const to C * Const.

It cannot be done directly, but can be indirectly implemented by initializing a local variable pointer to indirectly implement the same object referred to by this. Then, you can access the member you want to modify by this partial pointer: size_t string :: longens () const {// Define a THIS pointer not pointing to the // local version of the Const object * Const Localthis = const_cast ; if (!legthisvalid) {localthis-> datalength = strlen (data); Localthis-> lengthisvalid = true;} return datalength;} is not very beautiful. But in order to complete the desired function, only do it. Of course, if this method cannot be guaranteed, don't do this: For example, some old "eliminating const" methods will not work. In particular, if the object refers to this is really const, that is, when it is defined as const, "eliminating const" will result in unsureable consequences. So, if you want to eliminate Const in a member function, it is best to confirm that the object you want to convert is not defined as const. In other cases, the CONST will be both useful and safe through the type conversion. This is: Pass a const object to a function that takes a non-const parameter, and you know that the parameters will not be modified inside the function. The second condition is important because the object that is only read (not written) eliminates constant, even if the object is initially defined as const. For example, I already know that some libraries are not correctly declared: size_t strlen (char * s); Strlen certainly will not modify the data referred to in S - at least I have never seen it for a lifetime. But because there is this statement, it will not be legal when calling this function for a const char * type pointer. To solve this problem, you can securely convert this pointer's constraper when the Strlen pass parameters: const char * klingongreeting = "nuqneh"; // "nuqneh", "hello" // size_t length = strlen (const_cast < CHAR *> (Klingongreeting)); but don't abuse this method. It can ensure that it can work normally when the function (such as the strlen in this example) does not modify the data referred to in its parameters. Terms 22: Try to use the "Biographical reference" without "passing the value" C language, what is achieved by passing, C inherits this tradition and uses it as a default. Unless explicitly specified, the function of the function is always initialized by "copy of the argument", and the caller of the function is also a copy of the function return value. As I pointed out in the introduction of this book, the specific meaning of "passing an object" is defined by the copy constructor of the class of this object. This makes the pass value a very expensive operation.

For example, look at this (just imaginary) class: class person {public: person (); // is simplified, omitted // ~ Person (); ... private: string name, address;}; STUBLIC PERSON {public: student (); // is simplified, omitted // ~ student (); ... private: string schoolname, SchooLaddress;}; now define a simple function returnstudent, it takes a student parameter (Via the value) and then return it immediately (also via the value). After the definition, call this function: Student ReturnStudent (Student S) {Return S;} Student Plato; // Plato Reads ReturnStudent (Plato) under // Socrates (Socrate); // Call ReturnStudent This There is no integral function calling process, what happened inside? Simply put in: First, call the copy constructor of Student to initialize STUDENT to Plato; then call the copy constructor to initialize the function return value to S; then, the destructor S is called Finally, the destructor of the ReturnStudent returns the value object is called. So, the cost of this change is the cost of the two student's copy constructor plus two Student destructor. But it's endless, there is! There are two String objects in the Student object, so two String objects must also be constructed each time you construct a Student object. The Student object is still inherited from the Person object, so a Person object must also be constructed each time constructed a Student object. There are two two String objects inside a Person object, so each Person constructed is also a consisting of the other two String. Therefore, through the value to pass a Student object, eventually causing a Student copy constructor, a Person copy constructor, four String copy constructor. When the Student object is destroyed, each constructor corresponds to the call of the destructor. Therefore, the final overhead of a STUDENT object is six constructor and six destructor functions. Because the returnStudent function uses two pass values ​​(once the parameters, the return value once), this function calls twelve constructor and twelve destructor! In the eyes of the C compiler, this is the worst situation. The compiler can be used to eliminate some calls for copy constructors (C standard - see clause 50 - describe which compilers can perform such optimization work, the clause M20 is given example). Some compilers have also done this. But in the case of all compilers, it must be vigilant to pass the values ​​to pass the values.

To avoid this potential expensive overhead, do not pass values, but to pass reference: const student & returnstudent (const student & s) {return s;} This will be very efficient: no constructor or destructor is called Since there is no new object being created. There is another advantage to pass the parameters by reference to the reference: it avoids the so-called "SLICING Problem". When a derived object is passed as a base class object, it (derived class object) is "cut" as a "cut", which has become a simple base class object. This is often not what you want. For example, assume that such a set of categories that implement graphics window systems: class window {public: string name () const; // Return window name Virtual Void Display () const; // Draw window content}; Class WindowWithscrollbars: public window { Publicue: Virtual void display () const; Display declares that Virtual means a simple Window base class object is displayed. The expensive WINDOWWITHSCROLLBARS object is displayed differently (see Terms 36, 37, M33). Now suppose to write a function to print the name of the window and display this window. Below is a function written in a wrong way: // A function of "cutting problem" is troubled by Void PrintNameAndDisplay (Window W) {cout << w.Name (); w.display ();} Imagine use one What will happen when calling this function when calling this function: WindowWithscrollBars WWSB; PrintNameAndDisplay (WWSB); parameter W will be created as a Windows object (it is passed by value, remember?), All WWSB has The behavioral characteristics of the WindowWithscrollbars object are "cut". PrintNameAndDisplay inside, W's behavior is like a class of Window objects (because it is a Window object), regardless of the object type that originally transmitted to the function. In particular, the call to Display is always Window :: Display instead of Windowwithscrollbars :: Display. The method of solving the cutting problem is to pass the W: // a function of "cutting problems", a function of "cutting problem", Void PrintNameAndDisplay (Const Window & W); w.display ();} Now W The behavior is consistent with the real type of the function. In order to emphasize that W Although the delivery is passed but cannot be modified inside the function, it is recommended to declare it as const. Passing a reference is a good practice, but it will lead to its own complexity, the biggest problem is an alias issue, which is discussed in terms 17. In addition, more importantly, sometimes it is not possible to deliver an object, see Terms 23. Finally, the reference is almost all through the pointer, so the passing object is actually transmitting the pointer by reference.

Therefore, if it is a small object, for example, INT - is actually more efficient than the pass reference. Terms 23: Do not try back a reference to returning a reference to an object. Einstein has raised such a suggestion: make it simple as much as possible, but don't be too simple. Similar statements in the C language should be: Efficient as much as possible, but don't be too efficient. Once the programmer seizes the "pass value" in efficiency handle (see Terms 22), they will become very extreme, and they must not dig out each of the pilot values ​​hidden in the program. I don't know, in the process of unremittingly pursuing pure "biography", they will inevitably make another serious error: pass a reference to the object that does not exist. This is not a good thing. Look at the class that represents a rigor, contains a friend function, used for two rational numbers: class russ {public: Rational (int name = 0, int devenator = 1); ... private: int N, d; // Molecular and denominates Friend Const Rational // See Terms 21: Why Operator * (Const Rational & LHS, // Return Value is Const Const Rational & RHS)}; Inline Const Rational Operator * (Const Rational & LHS, Const Rational & Rhs) {Return Rational (lhs.n * rhs.n, lhs.d * rhs.d);} is obvious, this version of Operator * is the result of returning to the object through the pass value, if you don't consider the overhead of the object construct and destructure, you It is the responsibility of escaping as a programmer. Another obvious fact is that unless it is necessary, no one is willing to bear the overhead of such a temporary object. So, the problem is attributed to: Is it necessary? The answer is that if you can return a reference, there is of course no necessary. But keep in mind that reference is just a name, a name of some other object already exists. Whenever I see a reference statement, I will ask myself immediately: What is the other name? Because it must have another name (see Terms M1). Take Operator * If the function is to return a reference, it must be a reference to some other Rational object, which contains two objects multiplied. However, it is expected that there is no reason to have such an object before calling Operator *. That is, if there is a code below: Rational A (1, 2); // a = 1/2 granal b (3, 5); // b = 3/5 granal c = a * b; // c is 3 / 10 It is expected that there is a reason that has a value of 3/10 is unrealistic. If Operator * must return a reference to such a number, you must create this number of objects yourself. A function can only have two ways to create a new object: in the stack or on a heap.

When creating an object in the stack, with a definition of a local variable, use this method, you should write Operator *: // Write the first error method of this function inline const rueal & operator * (Const Rational & LHS, Const Rational & RHS {Rational Result (lhs.n * rhs.n, lhs.d * rhs.d); Return Result;} This method should be vetoed because our goal is to avoid constructor being called, but Result must be like other objects. The same is constructed. In addition, this function has another more serious problem, it returns a reference to a local object, and the terms 31 have been discussed in depth. So, create an object on the pile and return it to its reference? Based on the heap-based object is generated by using new, you should write Operator *: // Write the second error method of this function Inline Const Rational & Operator * (Const Rational & LHS, Const Rational & Rhs) {Rational * Result = New Rational (lhs.n * rhs.n, lhs.d * rhs.d); return * result;} First, you still have to afford the overhead of the constructor call, because the memory allocated by the NEW is initialized by calling an appropriate constructor. (See Terms 5 and M8). In addition, there is a problem: Who will be responsible for delete to delete the NEW generated object? In fact, this is definitely a memory leak. Even if you can convince the caller to get the function return value address, then use Delete to delete it (absolutely impossible - Terms 31 show what this code will be like this), but some complex expressions will occur The temporary value of the name, the programmer is impossible. For example: Rational W, X, Y, Z; W = x * y * z; two calls for Operator * have a temporary value without names, and programmers cannot see, so unable to delete.

(Refer to Terms Refer to Terms) Maybe you will miss you than the general bear - or general programmers - to be smart; maybe, you note that the method of creating objects on stacks and piles avoids calls to constructor; Perhaps, you remember that our original goal is to avoid this call to the constructor; maybe, you have a way to use a constructor to make everything; maybe, your eyes appear like this: Operator * Returns a reference to "Static Rational Objects defined in the function": // Write the third error method of this function inline const rulingal & lh, const rules {static runch; // will be referenced The returned // static object LHS and RHS multiply, and the result is put forward; Return Result;} This method seems to have a play, although you will find when you actually implement the pseudo code above, don't call a Rational constructor is not May give the correct value of Result, and avoid such calls is the topic we have to talk about. Even if you realize the pseudo code above, you are smart and you can't eventually save this unfortunate design. Want to know why, look at the following this paragraph of user code: bool operator == (Const Rational & lhs, // Rational Operator == Const Rational & Rhs); // Rational A, B, C, D ;. // Rational A, B, C, D ;. ..if ((a * b) == (c * d)) {processing equal condition;} else {processing is not equal;} Did you see it? ((A * b) == (C * D)) will always be true, regardless of what is A, B, C, and D! Rewrite the equal judgment statement in the form of an equivalent function, it is easy to understand the cause of this evil behavior: IF (Operator == (Operator * (A, B), Operator * (C, D))) When operator == is called, there are always two Operator * just called, each call returns the reference to the internal static Rational object within Operator *. Thus, the above statement is actually requesting Operator == to compare the "Operator * internal static Rational Rational object" and "the value of the static Rational object of" Operator * ", this is not equal to it! Fortunately, I have the above instructions should be sufficient to convince you: "I want to" return a reference in the function of Operator * "actually waste time. But I am not naive, I believe that luck will always come. Some people - you know who these people do not think about it, "Hey, the method above, if a static variable is not enough, maybe you can use a static array ..." Please hit! " Are we not enough? I can't let yourself write a sample code is too high, because even if you only have the above ideas, it is ashamed. First, you have to choose a n, specify the size of the array. If N is too small, there will be no place to store the function return value, which has not improved compared to the "design of single static variable" in the previous negative.

If N is too large, it will reduce the performance of the program because each object is created in the first time the array is called. This will bring N constructors and the overhead of n-analyte functions, even if this function is called once. If "Optimization" refers to the process of improving the performance of the software, then this practice can now be called "pessimization". Finally, think about how to put the need to the value of the array and how much overhead is needed? The most direct way to pass the value of the object is to assign a value, but how big is the value of assignment? In general, it is equivalent to calling a destructive function (destroyed old value) plus calling a constructor (copy new value). But our current goal is to avoid the development of constructors and sectations! In the face of reality: This method is absolutely unique. So, the correct way to write a function that must return a new object is to let this function returns a new object. For Rational's Operator *, this means that it is not the following code (that is the code that originally saw), or it is essentially equivalent to the same price: Inline const rueal operator * (Const Rational & LHS, Const Rational & Rhs) {Return Rational (lhs.n * rhs.n, lhs.d * rhs.d); It is just in exchange for the right program to run behavior with a small price. Moreover, what you are worried can never appear: Like all programming languages, C allows the compiler to use some optimized measures to improve the performance of the generated code, so in some occasions, Operator * The return value will be safely removed (see Terms M20). When the compiler uses this optimization (the current most compiler is done), the program continues to work as before, but it is only the speed than you expected. The above discussion can be attributed to: When you need to make a decision between returns a reference and return object, your duty is to select the one you can complete. As for how to make this choice as small as small, it is a matter of the manufacturer of the compiler. Terms 24: Causes cautious during function overload and setting parameters Defaults to confuse the function overload and set parameters default values ​​are, they all allow a function to be called in a variety of ways: void f ( ); // F is overloaded F (int x); f (); // call f () f (10); // call f (int) Void g (int x = 0); // g has One / / default parameter value g (); // call G (0) g (10); // Call G (10) So, what should I use? The answer depends on the other two questions.

First, there is a value that can be used as a default? Second, how many algorithms do you want to use? In general, if a suitable default value can be selected and only one algorithm is used, the default parameter is used (see Terms 38). Otherwise, use the function overload. Below is a function of up to the maximum maximum value of five int. This function is used - deep breath, see clearly --std :: numeric_limits :: min () as the default parameter value. Waiting for this value to introduce this value, here to give the function code: Int Max (int A, int b = std :: numeric_limits :: min (), int c = std :: numeric_limits : : min (), int D = std :: numeric_limits :: min (), int E = std :: numeric_limits :: min ()) {int Temp = a> b? A: B; TEMP = TEMP> C? Temp: C; Temp = Temp> D? Temp: D; Return Temp> E? Temp: E;} It can now be relaxed. std :: numeric_limits :: min () is a C standard library with a unique new method that is defined in C, which is the int_min Hongfa defined in . The thing indicated - handles the minimum possible value of the intimate generated by your C original code compiler. Yes, its syntactic departures from the conciseness of C, but behind those colons and other strange syntax, it is reasonable. Suppose wants to write a function template, its parameter is a fixed digital type, and the function generated by the template can print the minimum value represented by "Instantiated Type". This template can be written like this: Template void printminimumValue () {cout << is indicated as the minimum of T type;} If you just use and to write this function will feel very difficult Because I don't know what T is, I don't know if the print int_min is DBL_MIN, or any other type of value. In order to avoid these difficulties, standard C libraries (see clause 49) define a class template Numeric_Limits in header file , which also defines some static member functions. Each function returns information of "Instantiating the Type of this template". That is, the information returned in Numeric_limits is about the information returned by the function in the type INT, Numeric_Limits is about type DOUBLE. Numeric_limits There is a function called min, MIN returns a minimum of "instantiated type", so Numeric_Limits ::min () returns to the minimum of the integer type.

With Numeric_Limits (like other things in the standard library, Numeric_Limits exists in the namespace STD; numeric_limits itself is in the header file ), write PrintMinimumValue, you can easily: Template Void PrintMinImumValue () { Cout << std :: numeric_limits :: min ();} Using Numeric_Limits-based method to indicate that "type-related constant" looks great, it is actually. Because the length of the original code is not reflected in the generated target code. In fact, no instructions are generated at all calls to Numeric_LIMITS. Want to know what happened, see below, this is a very simple implementation of numeric_limits :: min: #include namespace std {inn Int numeric_limits :: min () throw () {RETURN INT_MIN;}} Because this function is declared as inline, the call to its call will be replaced by the function (see Terms 33). It is just an int_min, that is, it is just a simple "constant defined constant". #Define. So even if the MAX function starting at this Territor seems to be called to each default parameter, it is only used to use another smart method to represent a type related constant (in this example INT_MIN). Some efficient and clever applications are all in C standard library, which can be referred to Terms 49. Back to the MAX function: The most critical is that the MAX calculation is the same (very low) algorithm when the MAX calculation is calculated regardless of the number of parameters of the function. Which parameters don't care about anywhere in the function inside the function is "true", which is the default; and the default value selected is impossible to affect the correctness of the algorithm used. This is the reason why the use default parameter value is used. For many functions, the right default is not found. For example, suppose you want to write a function to calculate the average of up to 5 INTs. You cannot use the default parameters, because the results of the function depends on the number of incoming parameters: If you pass 3 values, you must divide the total number of 3; if you pass 5 values, you must divide the total number 5. In addition, if the user does not provide a parameter, there is no "magical number" can be used as a default because all possible ints can be valid parameters. There is no choice if this case: must be overloaded: Double AVG (INT A); Double AVG (Int A, INT B); Double AVG (Int A, Int B, INT C); Double AVG (Int A, INT B, INT C, INT D; Double AVG (Int A, Int B, INT C, INT D, INT E); another case where the overload function must be used: I want to complete a special task, but The algorithm depends on a given input value.

This situation is very common for constructor: The "Default" constructor constructs an object with air (no input), and the copy constructor is based on an existing object to construct an object: // A class of Class Natural represents a natural number { public: Natural (int initValue); Natural (const Natural & rhs); private: unsigned int value; void init (int initValue); void error (const string & msg);}; inlinevoid Natural :: init (int initValue) {value = INITVALUE;} Natural :: Natural (INTITVALUE) {IF (InitValue> 0) Init (InitValue); Else Error ("Illegal Initial Value";} Inline Natural :: Natural (Const Natural & X) {INIT (x.Value );} The constructor input to int must perform an error check, and the copy constructor does not need, so two different functions are required, which is overloaded. Also note that both functions must assign a new object. This will result in repetitive code in the two constructor, so you have to write a private member function init to solve this problem with the private member function init of "containing two constructor public code". This method - calls a "Overloaded Function" in the overload function - it is worth remembering because it is often useful (see clause 12). Terms 25: Avoid quick answering of the pointers and digital types: What is "zero"? More specifically, what happens below the code? Void f (int x); void f (string * ps); f (0); // call f (int) or f (string *)? The answer is, 0 is an int-- accurately, a literal Integer constant - So, "Always" F (int) is called. This is the problem: because not all people always want it to do so. This is a unique situation in the C world: When people think that a call should be polymor, the compiler is not so dry. If you want to use symbolic names (for example, null means NULL pointer) to solve such problems, but it is difficult to achieve more than imagination. The first thing I thought should be to declare a constant called NULL, but constant has a type, what should NULL? It is to be compatible with all pointer types, but the only type of condition is Void *, and if you want to pass the VOID * pointer to a type of pointer, there must be an explicit type conversion.

This is not only hard to see, but it is not better than the initial situation: void * const null = 0; // Possible NULL definition f (0); / / still call f (int) f (static_cast (NULL)); // Call F (String *) F (static_cast (0)); // Call f (String *) But imagine, use null to represent a void * constant method or It is better to initially be better because if it is guaranteed to use null to indicate Null pointer, it is possible to avoid ambiguous: f (0); // call f (int) f (null); // error! - Type does not match F (static_cast (null)); // correctly, calling f (string *) at least a "F function) of a runtime error (to 0 calls" error is now shifted into a compile time error (Pass a void * to the string * parameter). The situation is slightly improved (see Terms 46), but what you need to conversion is still annoying. If you want to be shameless to return to help, you will find it can't solve the problem, because the most obvious way is nothing more than: #define null 0 or #define null ((void *)) The first way is only Is the literal 0, essentially an integer constant (if you remember, or the initial problem); the second method also pulls you back to the "passing VOID * pointer to a type of pointer" . If there is research on the rules of type conversion, you will know that C will consider "Conversion from long int 0 to NULL pointer" and "Convert from Long Int to Int", it is nothing wrong. So this can be used to introduce polymacies to the place you might think there is a "int / pointer" problem: #define null 0L // Null is now a long intvoid f (int x); void f (string * p); f (null); // error! - Ambiguous, when you want to override Long Int and pointers, it doesn't work: #define null 0lvoid f (long int x); // This f now The parameter is longvoid f (string * p); f (null); / / correct, call f (long int) actual programming, which is more secure than the NULL defined as int, but it is not just transfer problems, and Not eliminating problems. This problem can be eliminated, but you need to use the latest feature of the C language: Member Function Template (often referred to as a member template). As the name suggests, the member function template is a template that generates member functions within the class. Take the above discussion about NULL, we need a "Type of Types, which works like static_cast (0) expression". That is, the NULL becomes an object that "contains an implicit type conversion operator" class, which can be applied to each possible pointer type.

This requires a lot of conversion operators, but they can help C generate from member template: // A first step of the class that can generate NULL pointer objects Class NullClass {public: Template / / for all types T operator t * () const {return 0;} // generate Operator T *;}; // Each function returns a // NULL pointer // const nullclass null; // Null is a type nullclass // an object Void f (int x); // and previous VOID F (String * P); // Same as F (NULL); // convert null to string *, // then call f (string *) This is a good Preliminary design, but it can also improve from several aspects. First, we actually only need a NULLCLASS object, so we have no need to give this class; we only need to define an anonymous class and make NULL a type. Second, since we want NULL to convert to any type of pointer, it must also be able to handle member pointers. This needs to define the second member template, its role is to convert 0 to type T C :: * (pointing to a member of class C) to all class C and all type T. (If you don't understand the member pointer, or you have never heard of it, or very little, it is not tight. The member pointer can be called rare animals, which is very rare, maybe many people have never used it. This curious person can refer to Terms 30, and the member pointer is discussed in detail.) Finally, to prevent the user from taking NULL address, because the behavior of NULL is not like a pointer, but the value of the pointer And the value of the pointer (such as 0x453AB002) is no address. Therefore, the definition of improved null look like this: const // This is a const object ... Class {public: Template // can convert any type Operator T * () const // NULL Non-member pointer {return 0;} // template // can convert any type Operator Tc :: * () const // NULL member pointer {return 0;} private: void operator & () const ; // Do not take the address // (see clause 27)} null; // This is the real code seen, although it is possible to give a name in the actual programming.

If you do not give a name, the compiler pointing to the null type information is really hard to understand. Another example of the usage of a member template See the Terms M28. Important is that all of the above NULL designs that produce correct work is only meaningful when you are a caller. If you are a person designed to be called a function, write such a NULL that is used by others is actually not much, because you can't force your caller to use it. For example, even if you provide the NULL developed above, you still can't prevent them from doing this: f (0); / / still call f (int), // Because 0 or int it is still in front of this Territor The problem of appearing. Therefore, as the designer of the overload function, the most basic one in the final analysis is that as long as it is possible, it is necessary to avoid overloading a number and a pointer type. Terms 26: Beware of potential errange everyone has ideas. Some people believe in freedom economics, some people believe in life. Some people even believe that COBOL is a real programming language. C also has an idea: it thinks that potential amphibration is not a mistake. This is an example of potentially unsatisfied: Class B; // Prevents // Class A {public: A (const b "); // can be constructed from B}; Class B {public : Operator a () const; // can be converted from A. Class B}; these classes are not wrong - they can coexist in the same program without a problem. However, look at the following, use the two classes to use, actually passing a B object in a function of an input parameter A, what will happen? Void F (Const A &); B; F (b); // Error! - Erlightening See the call to F, the compiler knows that it must generate a type A object, even if it holds it It is an object of type B. There are two well-known methods to achieve (see clause M5). One method is to call the constructor of class A, which constructs a new A object with B parameter. Another method is to call a self-defined conversion operator in class B, which converts B to an object of a A. Because these two ways are as possible, the compiler refuses to select one from them. Of course, the program can be used without inciting the second sense. This is the latent harm of potential second sense. It can lurking in the program for a long time, not being discounted; once a uninformed programmer has really done an unintended operation, it will break out. This leads to such a worryable possibility: You post a library, it can be called in the case of second, but you don't know what you are doing. Another similar form of secondary originates from the standard conversion of C language - not even involves class: Void f (int); Void f (char); double d = 6.02; f (d); // error! - Secondary D is this converted into int or char? Both conversions are feasible, so the compiler simply does not practice. Fortunately, you can solve this problem by explicit type conversion: f (static_cast (d)); / / correct, call f (int) f (static_cast (d)); // correct, Calling F (CHAR) Multi-inheritance (see Terms 43) is full of potential amphibia.

One of the most often happened is that when a derived class has inherited the same member name from multiple base classes: Class Base1 {public: int DOIT ();}; class base2 {public: void DOIT ();}; Class Derived: public base1, // derived no declaration public base2 {// a function called DOIT ...}; derived d; d.doot (); // error! - Eryi DeriveD inherited two When the function of the name, C does not think it is wrong, and this time is just potential. However, the call to DOIT forces the compiler to face this reality unless explicitly through the base class required by indicating the function, the function call will be wrong: D.Base1 :: DOIT (); // correct, Call Base1 :: DOITD.BASE2 :: DOIT (); // Correctly, call Base2 :: DOIT This will not make many people feel trouble, but when you see the above code is not accessible, some is very stunned. People will move their minds to do something uncomfortable: class base1 {...}; // 同 同 Class Base2 {private: void DOIT (); // This function is now private}; Class Derived: Public Base1, Public Base2 {...}; // 同 同 Derived D; INT i = D.DOIT (); // Error! - Or II! The call to DOIT still has an amphony, even if only the function in Base1 can be access. In addition, only Base1 :: DOIT returned values ​​can be used to initialize an int this fact that is not related to it - calling still has an amplitude. If you want to call successfully, you must indicate which type of DOIT you want is. Some of C , it will find that it will feel very nice, this is this situation. Specifically, why do you eliminate the access rights when you residual? There is a very good reason, it can be concatenated: Changing access to a class member should not change the meaning of the program. For example, the previous example, suppose it takes into account access. So the expression D. DOIT () decided to call Base1 :: DOIT, because Base2 version cannot be accessed. Now suppose Base1's DOIT version is changed from PUBLIC to protected, the version of Base2 is changed from Private to public. Between transsthesia, the same expression D. DOIT () will cause another completely different function call, even if the calling code and the modified function itself are not modified! This is not intuitive, the compiler can't even produce a warning. It can be seen that it is reasonable to explicitly eliminate the secondary meaning as you think about what you think. Since writing procedures and libraries, there are so many different situations that will generate potential errange, then how do a good software developer do? The most fundamentally, be careful when you want to be careful. I want to find out all the roots of all potentially unsatisfactory, especially when the programmer combines different independently developed libraries (see Terms 28), but understanding often generate potential erliness After those situations, you can minimize the possibilities of it in software design and development.

Terms 27: If you do not want to use the implicitly generated function to explicitly prohibit it to assume that you want to write a class template Array, which is generated by the category that can make up and lower limit checks, other behavior and C standard archettries. One problem facing in the design is how to disable the assignment operation between the Array object, because assignments to the standard C array: Double Values1 [10]; Double Values2 [10]; VALUES1 = VALUES2; // Error ! This is not a problem for a lot of functions. If you don't want to use a function, you only use it simply to put it into the class. However, the assignment operator belongs to the unique member function. When you don't write this function, C will write one (see Terms 45). Then what should be done? The method is to declare this function (Operator =) and make it private. Explicitly declare a member function, prevent the compiler from automatically generating its version; make functions to private, prevent others from calling it. However, this method is not very safe, member functions and friend functions can also call private functions unless you are smart enough - do not define (implement) this function. This way, when this function is invisible, the program will report an error in the link. For Array, the definition of the template can be asby: Template Class Array {private: // Do not define this function! Array & Operator = (const array & rhs); ...}; now, when the user tries to pair When the Array object performs assignment operations, the compiler will not agree; when you are inadvertently call it in a member or a friend function, the linker will call it. Don't think that this Terfeter is only applicable to assignment operators because this example is. not like this. It applies to the function of automatically generated by each compiler described in Terms 45. In practical applications, you will find that assignment and copy constructor has behavioral similarities (see clauses 11 and 16), which means that almost whenever you want to prohibit one of them, it will also prohibit another one. Terms 28: Divided the global namespace The biggest problem is that it is only one. In large software projects, many people often put their definitions in this single space, which inevitably leads to name conflicts. For example, if library1.h defines some constants, including: const Double lib_version = 1.204; similar, library2.h is also defined: const Int lib_version = 3; it is obvious, if a program wants to include library1.h and Library2.h will have problems. For this type of problem, you have no other means in addition to the mouth, or send the author to retaliate mail, or to edit the header file to eliminate the name of the name. However, as a programmer, you can try our best to write these issues to others. For example, some prefix that is not much likely to cause conflicts can be presumed, add each global symbol. Of course, it has to be acknowledged that such a combined identifier does not look so comfortable. Another better way is to use C Namespace. Namespace is in essence, like a prefix method, but avoids others always see prefix.

So don't do this: const double sdmbook_version = 2.0; // In this library, // Each symbol begins in "SDM" Class SDMHANDLE {...}; sdmhandle & sdmgethandle (); // Why is the function to declare this ? // See Terms 47 and do this: Namespace SDM {Const Double Book_Version = 2.0; Class Handle {...}; Handle & GetHandle ();} The user can access the symbol in this name space through three ways: Top all symbols in the namespace into a user space; introduce some symbols into a certain user space; or use a symbol by modifier: Void f1 () {use Namespace SDM; // Make all symbols in the SDM without adding // modifier, you can use cout << book_version; // interpretation as SDM :: BOOK_VERSION ... HANDLE H = getHandle (); // handle Explain to SDM :: Handle, // GetHandle explained as SDM :: getHandle ...} void f2 () {using sdm :: book_version; // allows only Book_version without add // modifier to use cout << book_version; // interpretation as // sdm :: Book_version ... handle h = getHandle (); // error! Handle and getHandle // None introduced to this space ...} void f3 () {cout << SDM :: Book_version; // makes book_version // This statement is Effect ... double d = book_version; // error! Book_version // is not in this space handle h = getHandle (); // error! Handle and getHandle // have not introduced to this space ...} (Some name space is not first name. This unnamed name space is generally used to limit the visibility of the internal elements of the namespace. See the Terms M31 for details. One of the biggest benefits brought by name space lies in: Potential errange will not cause errors (see Terms 26).

So, introducing the same symbol name from multiple different namespaces that will not cause conflicts (if you really never use this symbol). For example, in addition to the namespace SDM, if you want to use the following name space: Namespace AcmeWindowsystem {... typedef int handle; ...} will not conflict with SDM and AcmeWindowsystem when using SDM and AcmeWindowsystem. If you really want to reference, you can clearly indicate which name space of Handle: void f () {using namespace SDM; // introduces all symbols in the SDM Using Namespace AcmewInDowsystem; // Introduced all symbols in the ACME ... / / Free references SDM / / and ACME other than Handle Handle H; / / error! Which handle? Sdm :: handle h1; // is correct, no secondary ACMEWINDOWSYSTEM :: Handle H2; // There is also no second sense ...} If you use a conventional header-based method, just simply contain SDM.H and Acme.h, in which case compile will not pass because there is a plurality of definitions of Handle. The concept of name space is relatively late, so some people will think it is not important, but there is no. But this idea is wrong because almost all things in the C standard library (see Terms 49) are exist in the namespace STD. This may make you don't agree, but it affects you in a direct way: this is why C provides a header file that looks very interesting, no extension, such as , , etc. See Terms 49 in detail. Some compilers may not support because the concept of name space is relatively late. Even if this is, then there is no reason to pollute the global name space, because you can use Struct to approximate Namespace. You can do this: first create a structure to save the global symbol name, then put these global symbol names as a static member into the structure: // Definition of a structure for simulating the namespace Struct SDM {Static Const Double Book_Version; Class; Class; Handle {...}; static handle & getHandle ();}; const double sdm :: book_version = 2.0; // Static member definition Now, if someone wants to access these global symbol names, only simply add it in front of them Structural name as a prefix: void f () {cout << SDM :: Book_version; ... SDM :: Handle H = SDM :: getHandle (); ...} However, if there is no name conflict in the global scope, Users will feel addressed and more troublesome. Fortunately, there is a way to let users choose to use them or ignore them. For type names, you can explicitly remove spatial references with type definitions (TypeDef).

For example, assume that there is a type name t in the structural S (analog name space), which can be used to make T as synonyms of S :: T becomes: TypeDef SDM :: Handle Handle; for each (static) object in the structure X, you can provide a (global) reference X, and initialize to s :: x: const Double & book_version = SDM :: Book_version; honestly said that if you read the terms 47, you will not like to define a non-local part like Book_Version. Static object. (You will replace such objects in terms 47 to process the method of processing functions and processing objects, but pay attention to, even if the definition function is legal, the maintenance of the code will prefer you. Function Pointer: SDM :: Handle & (* const result "() = // getHenle refers to the SDM :: GetHandle SDM:: GetHandle; // Note that getHandle is a standing pointer. Because you certainly don't want your user to point it to something, not SDM :: getHandle, right? (If you really want to know how to define a function of a function, see below: SDM :: Handle & (& GetHandle) () = // getHandle is the reference to SDM :: GetHandle; // SDM :: GetHandle I personally think this practice It is also very good, but you may have never seen it before. In addition to initialization, the reference to the function is exactly the same, but the function pointer is easier to understand.) Has the above type definition and reference Users who do not encounter global name conflicts will use the type and object name without modifications; their, the user who has a global name conflict will ignore the type and reference definition, in order to the symbolic name of the trigger. Also note that all users want to use this book name, so put the type definition and reference in a separate header file, do not mix it and (analog Namespace) structure. Struct is a good approximate, but actually differs far from Namespace. It is very lacking in many ways, which is obvious that the handling of the operator. If the operator is defined as a static member of the structure, it can only be used by the function call, and can be used to use natural infix syntax as designed by the regular operator: // Define an analog name space The structure, the structure inside the structure contains Widgets type // and functions.

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

New Post(0)