Reading notes _effective c++_ construction, destruction, assignment

Source: Internet
Author: User

Article Five

class Empty { };

Such a class, when C + + is processed, the compiler declares a copy constructor, a copy assignment operator, a destructor, and a default constructor, all of which are public and inline.

class Empty {public:    Empty() { ... }    Empty(const Empty& rhs) { ... }    ~Empty() { ... }    Empty& operator=(const Empty& rhs) { ... }};

The default constructor and destructor are basically the constructors and destructors used by the compiler to place calls to base classes and non-static member variables, and the destructor of the compiler output is a non-static, Unless the class's base class declares itself to have a virtual destructor.

The copy constructor and the copy assignment operator, the compiler simply copies each non-static member variable from the source object to the target object. Consider a class like this:

template<typename T>class NamedObject {public:    NamedObject(const char* name, const T& value);    NamedObject(const std::& name, const T& value);    ...private:    std::string nameValue;    T objectValue;}NamedObject<int> no1("doubi", 2);NamedObject<int> no2(no1);

The copy constructor generated by the compiler sets No2.namevalue and no2.objectvalue for the initial value of No1.namevalue and No1.objectvalue. The type of namevalue is string, and the standard string has a copy constructor, So No2.namevalue is initialized by invoking the copy constructor of string and No1.namevalue as an argument, the other member Nameobject::objectvalue is int, is a built-in type, Each bits in the direct copy memory is completed for initialization.

The copy assignment operator is generated in the same way as the copy constructor, but if the generated code is illegal, the compiler rejects the generation of operator= for class. For example, the member variable is reference or const, or the base class declares the copy assignment operator as private.

Clause VI

Typically you do not want class to support a particular function, as long as you do not declare the corresponding function, but this policy does not work for the copy constructor and copy assignment operators, because the compiler declares it by default. So how do you get these functions to fail?

all compiler output functionsare public, and to prevent these functions from being created, they need to be declared themselves and declared as private. In general, this practice is not absolutely safe, because the member function and the friend function can still call your private function. Of course you don't have to define them, and if someone accidentally calls any one, you get a link error (linkage error), which is what the C + + iostream library does.

class HomeForSale {public:    ...private:    ...    HomeForSale(const HomeForSale&);    HomeForSale& operator=(const HomeForSale&);};

It is possible to transfer a connection-period error to the compile time (and, as a good thing, the sooner the error is detected), design a base class class that specifically blocks the copying action:

class Uncopyable {protected:    Uncopyable() {}    ~Uncopyable() {}private:    Uncopyable(const Uncopyable&);    Uncopyable& operator=(const Uncopyable&);};class HomeForSale : private Uncopyable  {};

So when the member function and the friend function try to copy the Homeforsale object, the compiler tries to generate a copy constructor and a copy assignment operator, and the compiler attempts to call the base class. Because it is private, it is rejected.

Uncopyable class implementation and use is very subtle, not necessarily to the public to inherit it, uncopyable of the destruction is not necessarily virtual.

Article VII

class TimeKeeper {public:    TimeKeeper();    ~TimeKeeper();    ...};class AtomicClock : public TimeKeeper { ... };class WaterClock : public TimeKeeper { ... };class WristWatch : public TimeKeeper { ... };TimeKeeper* ptk = getTimeKeeper();...delete ptk;

Gettimekeeper returns a derived class object that is deleted by a base class pointer, and the current base class has a non-virtual destructor. when the derived class object is deleted through a base class pointer, and the base class carries a non-virtual destructor, the actual execution usually occurs when the derived component of the object is not destroyed, causing a resource leak, Corrupt data structures and waste time on the debugger .

The way to solve this problem is to give the base class A virtual destructor.

class TimeKeeper {public:    TimeKeeper();    virtual ~TimeKeeper();    ...};

Consider the following class:

class Point {public:    Point(int xCoord, int yCoord);    ~Point();private:     int x,y;};

any class with the virtual function is almost certain that there should also be a virtual destructor, with the virtual function usually indicating that the class is designed as a virtual function. Because the virtual function implementation allows an object to carry certain information to determine which virtual function should be called. This information is usually indicated by the vptr pointer, vptr points to an array of function pointers, called VTBL.

Because of the virtual function, the volume of the object will increase, the 32-bit computer volume structure would occupy 64bits (storage 2 ints) to 96bits (2 ints plus vptr), 64-bit machine architecture may occupy 64~128bits, Because the pointer occupies 64bits in such a computer structure.

class AWOV {public:    virtual ~AWOV() = 0;};AWOV::~AWOV() { }

For an abstract class that has a pure virtual destructor, you must provide a definition for the destructor, or the connector will give an error.

Article Eight

class Widget {public:    ...    ~Widget() { ... };};void doSomething(){    std::vector<Widget> v;    ...}

When Vector v is destroyed, it needs to destroy all the widgets it contains, and if an exception is thrown in the destructor, if the two exceptions are present, the program may not end up with an ambiguous behavior. What if we need to ensure resource recovery at this time?

class DBConnection {public:    ...    static DBConnection create();    void close();};

To ensure that the customer does not forget to call Close () on the DbConnection object, it is common practice to create a class that manages the DbConnection resource and call Close in the destructor.

class DBConn {public:    ...    ~DBConn()    {        db.close();    }private:    DBConnection db;};

This approach throws a difficult problem if the close call fails or if the problem occurs in the destructor.

There are two ways to solve this problem:
(1) If close throws an exception, the program ends:

DBConn::~DBConn(){    try { db.close(); }    catch (...) {        std::abort();    }}

(2) Swallow the exception that occurs because of the call to close:

DBConn::~DBConn(){    try { db.close(); }    catch (...) {    }}

Neither of these methods can really solve the problem. A good strategy is to redesign the Dbconn interface, such as Dbconn to provide a close function itself, to track whether DbConnection is closed, and to close the destructor if the answer is no, to prevent the loss of the database connection. However, if the DbConnection destructor calls close fails, it is still necessary to "end the program" and "Swallow the exception".

Calling close by the customer does not burden them, but gives them an opportunity to deal with the error or they will not be able to respond.

Clause IX

Look at the following code:

class Transaction {public:    Transaction();    virtual void logTransaction() const = 0;    ...};Transaction::Transaction() {    ...    logTransaction;}class BuyTransaction : public Transaction { public:    virtual void logTransaction() const;    ...};class SellTransaction : public Transaction {public:    virtual void logTransaction() const;    ...};BuyTransaction b;

When Buytransaction B is executed, the first transaction constructor is called, and the last line of the constructor calls virtual Logtransaction. This time calling Logtransaction is the version of transaction, not the version in Buytransaction, because the virtual function does not fall to the derived classes hierarchy during the base class construction .

during the base class construction of the derived class object, the object's type is base class rather than derived class. Not only is the virtual function parsed to the base class, but if you want to use run-time type information such as dynamic_cast and typeID, the object will also be treated as a base class type. The same applies to destructors, where the object becomes a base class object after it enters the base class destructor .

So the above example will appear when the connector error when running, pure virtual is not defined. So how do you implement such a mechanism? Every time an object is created on the transaction inheritance system, the correct logtransaction function is called?

(1) Change the Logtransaction function to non-virtual, and then ask the derived class constructor to pass the necessary information to the transaction constructor:

class Transaction {public:    explicit Transaction(const std::string& logInfo);    void logTransaction(const std::string& logInfo) const;    ...};Transaction::Transaction(const std::string& logInfo){    ...    logTransaction(logInfo);}class BuyTransaction : public Transaction {public:    BuyTransaction (parameters) : Transaction(createLogString(parameters)) { ... }    ...private:    static std::string createLogString(parameters);};

Because you cannot use the virtual function to call down from base classes, during construction, you can pass the necessary information up to the base class constructor in derived classes.

Here's a tip to note: The private static function in Buytransaction, createlogstring, is more convenient than passing data directly in the initialization list, and it's better to make this function static. This will not inadvertently point to uninitialized member variables in Buytransaction .

Article Ten

Assignments can be written in the form of a chain:

x = y = z = 15;

Because the assignment is a right-associative law, the assignment operator must return a reference to the left argument of the operator.

class Widget {public:    ...    Widget& operator=(const Widget* rhs) {        ...        return *this;    }};

This Protocol applies not only to the above standard assignment forms, but also to all assignment related operations, such as +=,-=,*=.

Terms Xi.

Self-assignment is likely to occur if an object or variable has an alias, assuming you create a class to hold a pointer to a dynamically allocated bitmap:

class Bitmap { ... };class Widget {    ...private:    Bitmap* pb;};Widget& Widget::operator=(const Widget& rhs) {    delete pb;    pb = new Bitmap(*rhs.pb);    return *this;}

The self-assignment problem here is this if and RHS is the same object, delete is the current object's bitmap, want to prevent this error, the traditional practice is to operator= the first "identity test" to achieve "self-assignment" test purposes

Widget* Widget::operator=(const Widget& rhs) {    if (this == &rhs) return *this;    delete pb;    pb = new Bitmap(*rhs.pb);    return *this;}

Such an approach does solve the problem of self-assignment, but as stated in the previous article, there is no exceptional security. If new bitmap throws an exception, the widget holds the pointer pointing to a deleted bitmap. In many cases, it is important to note that the order of the statements can solve the problem of exception security.

Widget& Widget::operator=(const Widget& rhs) {    Bitmap* pOrig = pb;    pb = new Bitmap(*rhs.pb)    delete pOrig;    return *this;}

Now if "New Bitmap" throws an exception, PB remains the same, even if there is no identity test copy a copy of the Bitmap, delete the original Bitmap, efficiency is reduced. However, if it is necessary to add the approval test to the beginning of the function, because the probability of "self-assignment" is usually very low, the approval test makes the code larger and imports a new branch of control flow, which reduces the execution speed, prefetching, Orders such as caching and pipelining will be less efficient.

Another option is to use the copy and swap technology, which is a common writing technique for operator=.

class Widget {    ...    void swap(Widget& rhs);    ...};Widget& Widget::operator=(const Widget& rhs) {    Widget temp(rhs);    swap(temp);    return *this;}

Another way to implement this approach is based on the fact that:
(1) The copy assignment operator of a class may be declared as "accept the argument by value";
(2) Passing something by value will result in a copy.

Widget& Widget::operator=(Widget rhs) {    swap(rhs):    return *this;}

This practice sacrifices clarity with its ingenious patching, and moving the copying action from the body of the function to the constructor stage of the function parameter allows the compiler to generate more efficient code.

Clause 12

A well-designed object-oriented system encapsulates the interior of an object, leaving only two functions responsible for copying the copy constructor and the copy assignment operator, and the compiler creates the copying function for classes at the appropriate time. And make a copy of all the member variables of the object being copied. If you declare your own copying function, the compiler will not tell you when an error occurs in the implementation code.

Consider a class like this:

void logCall(const std::string& funcName);class Customer {public:    ...    Customer(const Customer& rhs);    Customer& operator=(const Customer& rhs);    ...private:    std::string name;};Customer::Customer(const Customer& rhs) : name(rhs.name){    logCall("Customer copy constructor");}Customer& Customer::operator=(const Customer& rhs) {    logCall("Customer copy assignment operator");    name = rhs.name;    return *this;}

The problem with this approach is that every time a new member variable is added, the copying function must be modified at the same time, and the compiler will not give an error at this time. And once inheritance has occurred, it is more likely that such a situation. If you are overriding the copying function of an inherited class, you need to ensure that the copying function of the base class is called.

When writing a copying function, make sure that:
(1) Copy all local member variables;
(2) Call all the appropriate copying functions within the base classes.

Many times when you construct a class, consider the complexity of purging class extensions:
(1) Adding member variables, functions;
(2) Inheritance polymorphism;
(3) operator operation;
(4) Multithreading.

If you find that the copy constructor and the copy assignment operator have similar code, the practice of eliminating duplicate code is to create a private and usually named Init function to call both.

... Finally ended the second chapter ....

Reading notes _effective c++_ construction, destruction, assignment

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.