"Effective C + +" study notes (vi)

Source: Internet
Author: User

original articles, reproduced please specify the source:http://blog.csdn.net/sfh366958228/article/details/38922567


Objective

The terms of the study today are all derived from the design and declaration, so what is software design? As explained in the book, the steps and practices of "making the software do what you want it to do" usually start with a rather general idea and eventually evolve into full detail to allow for the development of special interfaces.


Article 18: Make the interface easy to use correctly, not easy to misuse

C + + has a variety of interfaces, function interfaces, class interfaces, template interfaces, each of which is the way customers interact with your code. Ideally, if a client attempts to use an interface without getting the behavior he expects, the code should not be compiled, and if the code is compiled, it should be what the customer wants.

Let's look at a constructor for the class class that represents the date:

Class Date{public:    Date (int, int month, int day);    ...}
At a glance, this constructor is reasonable, but its customers can easily commit at least two mistakes:

1, in the wrong order to pass parameters, such as the Order of transmission for the month, day, year

2, the first invalid month or the number of days, such as a 1 March, or pass a February 30

Do not feel that this is not possible, once you compile the customer you may think the code is normal.

The simplest solution is to distinguish the number of days, months, and years with a simple type of outer covering:

struct day{    Explicit day (int D): Val (d) {}    int val;}
struct month{    explicit Month (int D): Val (d) {}    int val;}
struct year{    explicit year (int D): Val (d) {}    int val;}

Class Date{public:    Date (const year &y, const Month &m, const Dat &d);    ...} Date Date (Year (1995), Month (3), Day (30));

This approach simply solves the problem of the order of magnitude, but if you make year, Month, and day a mature and well-trained class and encapsulate its data, it is better than simply using the above-mentioned struct. Once the correct type is positioned, it is sometimes reasonable to limit its value. For example, there is only one 12 effective month in a year, so month should reflect that fact.

One way to do this is to use an enum to represent the month, but the enum does not have the type security we want to have, such as an enum that can be used as an int, and the safer solution is to pre-define all valid month:

Class Month {public:    static Month Jan () {return month (1)};    Static month Feb () {return month (2)};    ...    Static month Dec () {return month (n)};p rivate:    explicit month (int m);    ....} Date Date (Year (1995), Month::mar (), Day (20));
Another way to prevent customer errors is to limit what types of things to do and what not to do. The common limitation is to add a const.

The next is another general guideline, "make types easy to use, not susceptible to misuse": unless there is a good reason, try to keep your types behavior consistent with the built-in types behavior. For example A and B are int, then the a*b assignment is not legal, once the consistency of doubt, you can take int as a model reference.

Any interface has a tendency to use "incorrect" if there are certain things that a customer must do. A factory function, for example, returns a pointer to a dynamically allocated object within the investment inheritance system:

investment* createinvestment ();
To avoid a resource leak, the pointer returned by create must eventually be deleted, but this could be two errors: no pointer was removed, or two times the same pointer was deleted.

Many times, the design principle of the better interface is preemptive, that is, the factory function directly returns a smart pointer.

If you have a special delete handler for the returned pointer, you can take advantage of the shared_ptr custom delete implementation, such as:

Std::shared_ptr<investment> createinvestment () {    std::shared_ptr<investment*> retVal (static_cast <Investment*> (0), Mydelete ());    RetVal = ...; Make RetVal point to the correct object    return retVal;}
The reason to add a static_cast conversion before 0 is because 0 is an int, although it can be used as a pointer, but this situation is not good enough. If the original pointer that is pointed to by retval can be determined before it is established, passing the original pointer to the RetVal constructor will be assigned once more than the retval is initialized to null.

Another good feature of shared_ptr is that it automatically uses its own delete, without DLL problems.


Summarize:

1) Good interfaces are easy to use correctly and are not easily misused. You should strive to achieve these properties in all of your interfaces.

2) The "promote proper use" approach includes interface consistency and compatibility with built-in types of behavior.

3) the "block misuse" approach includes establishing new types, restricting operations on types, constraining object values, and eliminating customer resource management responsibilities.

4) shared_ptr supports custom-made filters. This prevents DLL problems and can be used to automatically unlock mutexes.


Article 19: Design class like design type

C + + and other OOP (object-oriented programming) languages, when you define a new class, you define a new type.

As a C + + program ape, you spend a lot of time primarily to expand your type system. This means you're not just a class designer, or a type designer. Overloading, functions, operators, controlling the allocation and restitution of memory, defining initialization and finalization of objects ... All in your hands.

So you should take care to discuss class design with the same caution as the language designer originally designed the language built-in type.

So, how to design an efficient class? First you have to understand the problems you face. Almost every class requires you to face the following questions, and your answers often lead to your design specifications:

    • How should the object of the new type be destroyed? this affects the class's constructor, destructor, memory allocation function (new), and deallocation (delete)
    • Object initialization and the assignment of the object what is the difference? initialization is the invocation of the constructor, and the assignment is an object that invokes the assignment operator
    • new type if passed by value, what does it mean? The copy constructor is used to define how the value of a type is to be implemented
    • What is the "legal value" of the new type? for class member variables, usually only certain values are valid, it affects the exception that the function runs out, and so on
    • do your new type need to match an inheritance graph? If you inherit from another class, you will be constrained by their design, especially if their function is the effect of virtual or non-virtual. If you allow other classes to inherit your class, then the function you declare is affected, especially if the destructor is virtual.
    • What kind of conversion does your new type need to match?
    • What operators and functions are reasonable for this type?
    • What kind of standard function should be dismissed? Some functions must be declared private
    • who should take a member of the new type? The helps you decide which member is public, which is protected, and which is private. It also helps you decide which class or functions should be friend,
    • What is the "undeclared interface" of the new type?
    • How generalized is your new type? Perhaps you need to define a set of similar type families, and if so you should define a new class template
    • Do you really need a new type? If you simply add a derived class to add a function to an existing class, you may be able to define one or more non-member functions or templates.

Summary:the design of class is the design of type. Before you define a new type, make sure that you have considered all the discussion topics covered by this article.


Clause 23: Prefer to replace the member function with Non-member and non-friend

If a class is used to represent a browser, there are some general rudder functions available in this class that are used to clear the cache, clear the history of the URLs visited, and remove all cookies from the system:

Class webbrowser{...    void ClearCache ();    void ClearHistory ();    void Removecookies ();        void Cleareverything (); Call ClearCache, clearhistory, Removecookies    ...} void Clearbrowser (WebBrowser &wb) {    wb.clearcache ();    Wb.clearhistory ();    Wb.removecookies ();}

We can write cleareveryting, can also be written as Clearbrowser. Well, which one is better?

The object-oriented code requires that the data and those functions that manipulate the data should be bundled together, which unexpectedly suggests that the member function is a better choice. Unfortunately, this proposal is not correct.

The object-oriented code requires that the data should be encapsulated as much as possible, and then, in contrast to the intuitive, member function cleareverything is less encapsulated than the Non-member function Clearbrowser.

In addition, providing a non-member function allows for greater wrapping elasticity (packaging flexibility) for WebBrowser related skills, which ultimately results in a lower compilation dependency, which increases the extensibility of WebBrowser.

Let's talk about encapsulation. If something is encapsulated, it is no longer visible. The more things are encapsulated, the fewer people can see it, the more resilient we are to change it.

Now consider the data within the object. The less code can see the data, the more data can be encapsulated. Functions that can access private member variables are only class member functions plus the friend function. If you want to make a choice between a member function and a non-member non-friend function, and both provide the same function, then the Non-member non-friend function is the larger package. Because it does not increase the number of functions that "have access to private components within class."

There are two things to note at this point:

1) This argument applies only to the Non-member non-friend function.

2) It does not mean that it cannot be a member of other classes simply because it cares about the encapsulation and the function "becomes the Non-member of class".

In C + +, it is more natural to make Clearbrowser a non-member function and be in the same namespace (namespace) where WebBrowser is located:

Namespace webbrowserstuff{    class WebBrowser {...}    void Clearbrowser (WebBrowser &wb);    ...}
However, not only for the sake of looking natural, namespace and class, the former can cross multiple source files and then not.

A class like WebBrowser may have a lot of convenience functions, a bit related to bookmarks, some related to printing, some related to the management of cookies, but most customers may only be interested in some of them.

The most straightforward way to separate them is to declare the convenience function of the bookmark in a header file, declare the cookie-related header file in another header file, and then declare the printed related traversal function in a third header file, and so on.

This is how the C + + standard library is organized. Placing all convenience functions in multiple header files but belonging to the same namespace means that customers can easily extend this set of convenience functions, all they need to do is add more non-member non-friend functions into this namespace.

Summarize:

Rather take the Non-member non-friend function to replace the member function. This can increase encapsulation, wrap elasticity (packaging flexibility), and functional expandability.


Clause 24: If all parameters require type conversion, use the Non-member function for this

Although implicit conversions are not advocated in many cases, why not do so if there are enough reasons to do so? For example, it is reasonable to "implicitly convert" integers into rational numbers.

Class Rational{public:    Rational (int numerator = 0, int denominator = 1),//Allow implicit conversion    of int numerator () const;//Numerator denominator Access function    int denominator () const;    Const rational operator* (const rational &RHS) Const;private:   ...} Rational oneeighth (1, 8); Rational onehalf (1, 2); Rational result = onehalf * Oneeighth;result = result * ONEEIGHTH;
The arithmetic operations of multiplication are implemented in our class, and everything is normal at the time of invocation, which can be easily multiplied by two rational numbers. But you want to support mixed operations, such as supporting int multiplication.
result = Onehalf * 2; correct result = 2 * onehalf; Error
Why is the second kind wrong? We can rewrite the second form of:
result = 2.operator* (onehalf);
The integer 2 does not have a corresponding class and there is no operator* member function. Why is the first kind of 2 right? Because it has an implicit conversion.

Only if the parameter is listed in the parameter column, this parameter is the implicit type conversion of the qualified participant, the status equivalent to "the called member function of the object"-that is, the this object-the metaphor parameter, is not the implicit conversion of the qualified.

Then if you have to make a second form, let operator* become a non-member function.

Class rational{...   //There is no operator*};const rational operator* (const rational &LHS, const rational &RHS) {
   ...}

Summarize:

This function must be a non-member if you need to convert all of the parameters of a function, including the metaphorical parameter referred to by the this pointer.


Article 25: Consider writing a swap function that does not throw an exception

Swap is an interesting function. Originally it was only part of the STL, and later became the spine of unusually safe programming, as well as a common mechanism for dealing with the possibility of self-assignment.

The so-called swap two object means that the values of the two objects are given to each other. By default, the swap action is done by the swap algorithm in the STD standard library.

namespace std{    template<typename t>    void swap (t &a, T &b)    {        T temp (a);        A = b;        b = temp;    }}

As long as the type T supports copying, the default swap implementation will help you replace the object of type T, and you do not need to do any more work.

However, for some types, these copy actions are not necessary, such as "pointing to an object with a pointer, containing the real data" type. The common manifestation of this design is the so-called "pimp (Pointer to implementation) approach". If you design the widget in this way, it looks like this:

Class Widgetimpl{public:    ... private:    int A, b, C;    Std::vector<double> v;    ...}; Class Widget{public:    widget (const widget &RHS);    widget& operator= (const Widget &RHS)    {        ...        *pimpl = * (Rhs.pimpl);        ...    } Private:    Widgetimpl *pimpl;};
If we need to change only two Widget object values, in fact all we need to do is replace their pimpl pointers, but the default swap algorithm does not know that it not only duplicates three widgets, but also replicates three Widgetimpl objects, which is very inefficient.

Std::swap for widgets is a way to improve efficiency. We can't change what's in the Std namespace but we can make a special version of the standard template so that it belongs to our own class.

We will first write a public function called Swap in the Widget class to implement the substitution work. The Std::swap is then customized so that it calls the member function.

Class Widget{public: ...    void swap (Widget &other)    {        using Std::swap;        Swap (Pimpl. Other.pimpl);    }; namespace std{    template<>    void swap<widget><widget> (widget &a, widget &b)    {        A.swap (b);    }} </widget>
This practice is not only compiled, but also consistent with STL containers, because all STL containers also provide public swap member functions and STD::SWAP versions.

However, assuming that both widgets and Widgetimpl are class template, we can also parameterize the data types within the Widgetimpl.

Template<typename t>class Widgetimpl {...}; Template<typename t>class Widget {...};

Writing a swap in the widget is as simple as ever, but we're in a std::swap when it comes to special.

namespace std{    template<typename t>    void swap<widget<t>> (widget<t> &a, Widget <T> &b)    {        a.swap (b);    }} namespace std{    template<typename t>    void Swap (widget<t> &a, widget<t> &b)    {        A.swap (b);    }}
Both of the above versions of the special version are wrong. The user can fully special the template in Std, but cannot add a new template. What about that?

The answer is simple, and we declare a non-member swap to let him call member swap, but no longer declare that Non-member swap as a special or overloaded version of Std::swap.

To simplify, we put all the relevant skills of the widget in the namespace Widgetstuff.

Namespace widgetstuff{...    Template<typename t>    class Widget    {        ...    }    ...    Template<typename t>    void Swap (widget<t> &a, widget<t> &b)    {        a.swap (b);    }}

Now this is going to work for both class and class templates, but there's a reason you should be std::swap for class specialization, so if you want your "class-specific" swap to be called in as many contexts as possible, You need to write a non-member version and a Std::swap special version in the namespace of class.

Think about it, if you are a customer, which swap is called when you use swap?

Template<typename t>void dosomething (t &obj1, T &obj2) {    using Std::swap;//Make Std::swap available    within this function ...    Swap (obj1, obj2); Call the best swap version for the T-function    ...}

Do not add STD when calling swap:: Because it will allow you to call the swap in Std forever.

If the default code for swap provides an acceptable efficiency for your class and class template, you don't need to do anything extra. If the swap default version is inefficient (almost always means that your class or template uses the Pimpl technique), you can:

1) a public swap member function is provided, and the function should never throw an exception.

2) provide a non-member swap in the namespace where your class or template resides, and make it call the Swap member function above.

3) If you are writing a class, for your class special Std::swap. And make him call your swap member function.

Finally, if you call swap, be sure to include a using declaration so that std::swap can be exposed in your function. Remember to call the time not to add to the superfluous std::.

The member swap must never throw an exception, because one of the best uses of swap is to help class provide strong exception security. This technique is based on the assumption that the swap of the member version never throws an exception. This constraint only applies to the member version, not to the non-member version, because the default version of Swap is based on copying, while the general conditions are allowed to throw exceptions.


Summarize:

1) When Std::swap is not efficient for your type, provide a swap member function and make sure that the function does not throw an exception.

2) If you provide a member swap, you should also provide a non-member swap to invoke the former. For class, please also special std::swap.

3) Use a using declaration for std::swap when calling swap and then call swap without any "namespace adornments"

4) It is good to have a "user defined type" for the STD template, but don't try to add something new to STD in Std.

Effective C + + learning Note (vi)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.