"Effective C + +" learning notes (eight)

Source: Internet
Author: User

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


Article 29: It is worthwhile to work for "exceptional safety"

After reading this article, the first feeling is how risky the code has been written before.

First look at a book example, suppose there is a class to show the background pattern of the GUI menu, this class is also used in the multi-threaded environment, so we consider a mutex (mutex) as a concurrency control (concurrency) use:

Class Prettymenu{public: ...    void Changebackground (std::istream& imgsrc); Change Picture background ...    private:    mutex mutex;       Mutex    image* bgimage;    Current background image    int imagechangescounts;/////Background image is changed};//the following is a possible implementation of the Changebackground function of prettymenu: void Prettymenu:: Changebackground (std::istream& imgsrc) {    lock (&mutex);          Obtain the mutex    delete bgimage;        Get rid of old background image    ++imagechangescounts;  Modify the number    of changes Bgimage_ = new Image (IMGSRC);//install a new background image    unlock (&mutex);        Release Mutex}
In terms of "exception security", this function is very bad. "Exception safe" has two conditions, and this function does not satisfy any of these conditions.

1) do not disclose any resources: once "new Image (IMGSRC)" Causes an exception, the call to unlock will never be executed, and the mutex will always be locked.

2) Do not allow data corruption: if "new Image (Imgsrc)" Throws an exception, Bgimage is pointing to an object that has been deleted, and Imagechanges is added, and no new image is successfully installed.

Resolving resource leaks is simple, and we can use resource management classes to ensure that the mutex is locked and will be released.

The exception security function provides one of the following three guarantees:

Basic commitment: If an exception is thrown, anything inside the program remains in a valid state.

Strongly guaranteed: If the exception is thrown, the program state does not change.

Do not throw (nothrow) Guarantee: Promise never throws an exception, because they are always able to accomplish the functions they originally promised.

In general, we would like to provide the highest assurance, but it is difficult to invoke a function in C part of C + + that is completely free from the possibility of throwing an exception. Anything that uses dynamic memory will throw a Bad_alloc exception once there is not enough memory to satisfy the requirement.

Class prettymenu{...    Std::tr1::shared_ptr<image> Bgimage;}; void Prettymenu::changebackground (std::istream& imgsrc) {    Lock (&mutex);          Obtain the mutex and make sure it is released later        Bgimage.reset (new Image (IMGSRC));     ++imagechangescounts;  }
The above is our optimized code, but the function can only provide a "basic exception security guarantee." Because if the image constructor throws an exception, it is possible that the read token of the input stream has been moved, and such removal is a visible state change to the rest of the program.
There is a generalization of the design strategy to achieve this goal, this strategy is called "Copy and Swap", as the saying goes, "name as a person", the principle is actually: for the object you intend to modify a copy, and then modify on the copy. If an exception occurs during the modification process, the problem is small because the original object state has not been changed. The "copy-to-original object interchange" operation is performed after the modification action is completed.

struct pmimpl{    std::tr1::shared_ptr<image> bgimage;    int imagechangescounts;}; Class prettymenu{...    void Changebackground (std::istream& imgsrc)    {        using Std::swap;        Lock M1 (&mutex);        Std::tr1::shared_ptr<pmimpl> Pnewimpl (New Pmimpl (*pimpl)); Make copy        pnewimpl->bgimage_.reset (new Image (IMGSRC));//handle copy        ++pnew->imagechangescounts;        Swap (PIMPL,PNEWIMPL); Swap     }private:    Mutex mutex_;    Std::tr1::shared_ptr<pmimpl> Pimpl;};
We note that the key to Copy-and-swap is to "modify the object data copy and then replace the modified data with the original in a function that does not throw the exception", so you must make a copy of each object that is about to be changed, which consumes you
Time and space that may not be available.

This is a very practical question: we all want to provide a "strong guarantee"; when it can be implemented you should certainly provide it, but "strong assurance" is not practical at all times.

When a strong guarantee is impractical, you must provide basic assurance.

In the actual development you can provide a strong guarantee for certain functions, but the cost of efficiency and complexity will make you have to abandon it, in case the actual is not practicable, so that you back to the second only to provide basic assurance, no one should blame you. For many functions, the "Basic assurance of Exception security" is an absolutely reasonable choice.

Summarize:

1) The Exception security function (Exception-safe functions) does not leak resources or allow any data structures to be corrupted even if an exception occurs. Such a function area is divided into three possible guarantees: basic type, strong type, non-throwing anomaly.
2) "Strong assurance" can often be achieved with copy-and-swap, but "strong guarantee" is not for all functions can be achieved or have practical significance.
3) The "Exception security guarantee" provided by the function is usually the highest of the weakest in the exception security guarantee for each function that it invokes.


Article 30: A thorough understanding of the inside and outside of inlining

inline functions, which you can invoke without incurring the extra overhead of function calls, the compiler performs context-sensitive optimizations on the function ontology, and most compilers do not perform such optimizations on a non-inline function call action.

Since each call to the inline function is replaced with a function body, so it may increase your target code, on a limited machine, excessive enthusiasm for inlining will cause the program volume is too large, even if you have virtual memory, the code expansion caused by the inline will also cause additional page change behavior, Reduce the hit rate of the instruction cache and the accompanying efficiency loss.

These are the advantages and disadvantages of the inline function, so we still have to look at the inline function dialectically.

One thing you can't ignore is that the inline function is just an application, not a mandatory command, so it could be rejected by the compiler. The application can be presented in a metaphorical or explicit form.

Metaphor suggests that a function is defined in a class definition, which is usually a member function or a friend function. As follows:

Class Person{public: ...    int age () const    {        return theage;//a metaphor for the inline application    }    ... private:    int theage;}
The explicit declaration is to add the keyword "inline" before the function. For example, the standard Max template is often implemented like this:

Template<typename t>inline Const t& Std::max (const t &a, const T &b) {    return a < b? b:a;}

The inline function must always be placed inside the header file, which is the compile-time behavior in most C + + programs.

Most compilers refuse to inlining functions that are too complex (including loops, recursion, etc.), and all virtual functions cannot be inlining because virtual means "wait, know the runtime to determine which function to call," and inline means " Replace the action with the body of the called function before execution. If the compiler doesn't know which function to call, it's hard to blame them for refusing to inlining the function body.

Sometimes when the compiler is inline with a function, it may also generate a function ontology (such as a program to fetch a line function address), it is worth mentioning that the compiler does not usually "call through the function pointer" Implementation of inling, This means that the call to the line function may be inlined or not inlined, depending on how the call is implemented.

"Inling Constructors and destructors" is a bad idea. Look at the following code:

Derived: Conceptual implementation of the:D erived () {//blank Derived constructor    base::base ();//Initialize Base component        try{        dm1.std::string::string ();    } catch (...) {        base::~base ();        throw;    }        try{        dm2.std::string::string ();    } catch (...) {        dm1.std::string::~string ();        Base::~base ();        throw;    }        try{        dm3.std::string::string ();    } catch (...) {        dm2.std::string::~string ();        Dm1.std::string::~string ();        Base::~base ();        throw;    }}
This code does not represent the code that the compiler actually makes, because the real compiler handles exceptions in a more sophisticated way. Nonetheless, this has been able to accurately reflect the behavior that derived's empty constructor must provide.

The library designer must evaluate the shock of declaring a function as inline: the inline function cannot be upgraded with the upgrade of the library.


Summarize:

1) Limit the majority of inlining to small, frequently called functions. This makes future debugging and binary upgrades (binary upgradability) easier, as well as minimizing potential code bloat issues, maximizing the chance of a program's speed increase.
2) Do not declare the function templates as inline because it appears in the header file.


Article 31: Minimize compilation dependencies between files

Suppose you make some minor changes to a class implementation file for a C + + program, instead of an interface, it is implemented, and only the private component is changed.

Then rebuild the program and expect it to take just a few seconds, when you press "Build" or type make, it's a surprise because you realize the whole world is being recompiled and linked!

The problem is that C + + does not "detach the interface from implementation" very well. The definition of class not only describes the class interface in detail, but also includes a full implementation breakdown:

Class person{Public: Person     (const std::string& name, const date& birthday, const address& addr);     std::string name () const;     std::string birthDate () const;     std::string address () const;     ... private:     std::string thename;        Implementation details     Date thebirthdate;          Implementation of the detail     Address theaddress;         Implementation details};

This class person cannot compile, and the person definition file may have something like this at the top:

#include <string> #include "date.h" #include "address.h"
This writing clearly creates a compilation dependency between the person definition file and its included files (compilation dependency). It may lead to a situation in which you get into a dilemma at the beginning. So here we take another way of implementing the object to implement the rules hidden behind a pointer. To do this: divide the person class into two classes, one provides only an interface, and the other is responsible for implementing the interface.

#include <string> #include <memory>class personimpl;class date;class address;class person{public:    Person (const std::string& name,const date& birthday,const address& addr);    std::string name () const;    std::string birthDate () const;    std::string address () const;    ... private:    std::tr1::shared_ptr<personimpl> Pimpl;}

Here, the person contains only one pointer member, pointing to its implementation class (PERSONIMPL). This design is often referred to as Pimpl idiom (Pimpl is the abbreviation for "pointer to implementation").

The key to this separation is to replace the "dependency of the definition" with "declared dependency", which is the essence of minimizing the compiler dependencies: make the header file as self-sufficient as possible, and if not, let it be dependent on the declarative (rather than the defined) in the other files. Everything else comes from this simple strategy of involvement.

1) If you can complete the task with object reference or object pointer, do not use objects.

2) If possible, replace the class definition with class declaration.

3) Provide different header files for declarative and defined expressions.

First person so use Pimpl idiom classes, often called handle classes.

Another way to make handle class is to make person a special abstract base class called interface class. This class has only a virtual destructor and a set of pure virtual functions to describe the entire interface. A interface class written for person may look like this:

Person.h...using std::string;class date;class address;class person{public:virtual ~Person ();    Virtual string name () const = 0;    Virtual string birthDate () const = 0;    Virtual string address () const = 0; ... static std::tr1::shared_ptr<person> Create (const string& name,const date& birthday,const ADDRESS&A mp addr);}; ...//Person.cpp...class realperson:public Person{public:realperson (const string& name,const Date& birthday,c    Onst address& addr);    Virtual ~realperson () {} string name () const;    ..... private:string name_;    Date Thebirthdate_; Address Theaddress_;};  Std::tr1::shared_ptr<person> person::create (const string& name, const date& birthday, const address& addr) {return STD::TR1::SHARED_PTR&L T Person> (New Realperson (NAME,BIRTHDAY,ADDR));}
Handle classes and Interface classes remove the coupling between the interface and the implementation, thus reducing compilation dependencies between files. Note that the operating costs of the two class implementations are not negligible. If you should start with your reality, consider using these techniques in an asymptotic way.


Summarize:

1) The general idea of supporting the minimization of compile dependencies is that it depends on the declarative, not on the definition. The two instruments based on this conception are handle classes and interface classes.
2) The library header file should be in the form of "complete and only declarative" (Full and Declaration-only forms). This practice applies whether or not the templates is involved.


Conclusion

Although there are only three clauses, but the length is relatively long, the logic is relatively not particularly easy to understand, after that should continue to review more.

"Effective C + +" learning notes (eight)

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.