Article 18: Make the interface easy to use and misuse
1. Good interfaces are easy to use correctly and cannot be misused. You should try to achieve these properties in all your interfaces.
2. The "Promoting positive use" approach includes interface consistency and compatibility with built-in behaviors.
3. Methods for preventing misuse include creating new types, limiting operations on types, binding object values, and eliminating the customer's resource management responsibilities.
4. shared_ptr supports custom delimiters. This prevents DLL problems and can be used to automatically remove mutex locks.
Clause 19: design class is like design type
Blog address: http://www.cnblogs.com/ronny/ reprint please indicate the source!
How to design your class:
1. How should I create and destroy a New type object?
Affected design functions: constructor and destructor with memory allocation functions and release functions (operator new, operator new [], operator delete, operator delete []).
2. What is the difference between object initialization and object assignment?
Depends on the behavior of constructors and value assignment operators.
3. What does the new type object mean if it is passed-by-value?
The copy constructor defines the pass-by-value of a type.
4. What is the "valid value" of the new type "?
The constructor must perform a valid check.
5. Do your new type need to work with an inherited Graph System?
If you inherit from other classes, you must be bound by other classes. Note the impact of virtual and no-virtual. If other classes inherit this class, consider whether to design the Destructor as a virtual function.
6. What kind of conversion does your new type need?
To convert other types to the type you designed, the corresponding non-explicit constructor is required. If you need to convert the designed type to another type, you need to define the type conversion function operator T.
7. What operations and functions are reasonable for this new type.
It depends on which functions you declare for your class. Some of them may be member functions, while others may not.
8. What kind of criteria should the function be rejected?
Those are exactly the ones you must declare as private.
9. Who should use members of the new type?
In this question group, you can decide which member is public, which is protected, and which is private. It also helps you decide which class or function should be a friend, and whether it is reasonable to nest them in another.
10. How General is your new type?
If you define an entire types, should you define a new class template.
11. What is the "undeclared interface" of the new type "?
12. Do you really need a new type?
If you only define a new derived class to add functions to existing classes, you may simply define one or more non-member functions or templates to achieve the goal.
Clause 20: replace pass-by-reference-to-const with pass-by-value.
Pass-by-value causes a large amount of constructor and destructor overhead, and class cutting occurs when passing the derived class to the base class interface.
The above rules do not apply to built-in types, as well as STL iterators and function objects. For them, pass-by-vaule is usually appropriate.
Clause 21: when an object must be returned, do not think about returning its reference
Do not return a point or reference pointing to a local stack object, or return a reference pointing to a heap-allocated object, or return point or reference pointing to a local static object, and multiple such objects may be required at the same time.
Let's consider a rational number class and define a rational number youyuan for it:
// Blog address: http://www.cnblogs.com/ronny/class Rational {public: Rational (int numerator = 0, int denominator = 1); private: int n, d; friend const Rational operator * (const Rational & lhs, const Rational & rhs );};
Now let's design this friend function. Its function is to return the product of two Rational objects. We have three solutions:
// Solution 1 const Rational & operator * (const Rational & lhs, const Rational & rhs) {Rational result (lhs. n * rhs. n, lhs. d * rhs. d); return result ;}
This function returns a reference to the result, but the result is a local object. When the function is called, the object will be destroyed, and its reference will point to an undefined object without any significance.
// Solution 2 const Rational & operator * (const Rational & lhs, const Rational & rhs) {Rational * result = new Rational (lhs. n * rhs. n, lhs. d * rhs. d); return * result ;}
In the solution, the result is not a local object, but a pointer to the object dynamically allocated from heap. Who is responsible for the delete pointer, suppose there is an expression like this:
Rational w, x, y, z;w = x*y*z;
This expression actually calls two operator * operations, that is, two dynamic memory regions are created, but there is no way to get their pointers.
// Solution 3 const Rational & operator * (const Rational & lhs, const Rational & rhs) {static Rational result; result = Rational (lhs. n * rhs. n, lhs. d * rhs. d); return result ;}
This time, the result still exists after being detached from the function through the static object, but like all the static object designs, this one also immediately raises our doubts about the security of multithreading. And if there is the following code:
bool operator==(const Rational& lhs, const Rational& rhs);Rational a, b, c, d;if ((a*b) == (c*d)){}else{}
The if condition in the above Code is always true, because a * B returns a reference to a static object, and c * d returns a reference to the same static object, so it is always equal.
Therefore, we finally choose to return the new object through pass-by-value.
const Rational operator*(const Rational& lhs, const Rational& rhs){ return Rational(lhs.n*rhs.n, lhs.d*rhs.d);}Clause 22: declare member variables as private
The first is code consistency (whether it is a member or a function is unnecessary when calling a public member ).
Secondly, encapsulation is written as a function to provide the possibility of modifying the access method in the future without affecting the usage. In addition, public affects all users, while protected affects all successors and has a huge impact. Therefore, we do not recommend declaring member variables.
Remember to declare the member variables as private. This gives the customer the consistency of access data, the ability to slightly divide access control, the guarantee of commitment conditions, and the class author to fully implement elasticity.
Protected is not more encapsulated than public.
Cla23: Native replaces the member function with non-member and non-friend.
Imagine that a class is used to represent a web browser. Among the many functions provided by such a class, there is a high-speed buffer (cache of downloaded elements) used to clear the downloaded elements) clear the history of the accessed URLs and remove all cookies from the system.
class WebBrowse{public: void clearCache(); void clearHistroy(); void removeCookies();};
Many users want a function to execute the entire action, because some WebBrowse also provides the following function:
Class WebBrowse {public: //... void clearEverything (); // call sort AchE (), clearHistory (), removeCookies ()} in sequence ()};
Of course, this function can also be completed by a non-member function:
void clearEverything(WebBrowse& wb){ wb.clearCache(); wb.clearHistory(); wb.removeCookies();}
Which one is better?
According to the requirements of the object-oriented code, data and the functions for operations should be bundled together, which means member functions are a good choice. Unfortunately, this suggestion is incorrect. Object-oriented Data should be encapsulated as much as possible.
The member function is less closed than the non-member function because it does not increase the number of functions that can access the private components in the class.
In addition, the non-member function can be provided to enable greater flexibility for WebBrowse-related functions, which eventually leads to a lower degree of consistency in compilation and increases the scalability of WebBrowse.
If we design the functions related to WebBrowse as non-member functions, we can separate the declarations of functions related to WebBrowse in a header file and put them in a namespace. At this time, if we want to extend these functions, just declare them in the header file like other functions.
This cutting method is not applicable to class member functions, because a class must be defined as a whole and cannot be cut into fragments.
Remember
Instead, replace the non-member non-friend function with the member function. This will increase encapsulation, package elasticity, and functional scalability.
Clause 24: If all parameters require type conversion, use the non-member function.
When we design a multiplication operator overload function for a rational number class, if we use it as a member of the class:
class Rational{public: //... const Rational operator*(const Rational &lhs);};
When we try mixed arithmetic, you will find that only half of it works:
Rational result, oneHalf;result = oneHalf * 2;result = 2 * oneHalf;
The second assignment statement above is incorrect because it is equivalent to result = 2. operator * (oneHalf), which is obviously incorrect.
The 2nd statements are equivalent to result = oneHalf. operator (2), which can be executed successfully because 2 has an implicit type conversion, because Rational has a non-explicit constructor that accepts int parameters.
Therefore, if we want to perform the above operations, we need to design this overload function as a non-member function.
const Rational operator*(const Rational& lhs, const Rational& rhs);
If you want to perform type conversion for all parameters of a function (including the metaphor parameter pointed to by this pointer), the function must be a non-member.
Clause 25: Write A swap function without throwing an exception.
Blog address: http://www.cnblogs.com/ronny/ reprint please indicate the source!
1. When std: swap is inefficient at your type, provide a swap member function and make sure this function does not throw an exception.
2. If you provide a member swap, you should also provide a non-member swap to call the former. For classes (rather than templates), please also specialize in std: swap.
3. When swap is called, the using declarative type should be used for std: swap, and then swap is called without any "namespace qualification modification ".
4. It is good to fully specialize std templates for "user-defined types", but never try to add something brand new to std in std.