C + + What we have to know about specific practices (1)--The story behind construction, copying constructs, destructors, assignment operators

Source: Internet
Author: User

1. What functions are called by default in C + +

When the data member type in a class is the Trival data type (that is, the struct type of the original C language), the compiler does not create ctor, copy ctor, assign operator, dctor by default.

The compiler will only create them when these functions are called.

At this point we will create our own constructors and initialize the built-in data types. In general, we do not need to copy the control functions, and when necessary, the compiler is very good composition. The general compiler synthetic Copy control function is simply a copy of the member, if you can meet the requirements, you do not need to write.

When a class contains a reference, a const member, the member must be initialized in the initialization list. And their copy COTR, assign operator are not allowed.

Three-element rule: Classes that have constructors in general do not require destructors. However, when a class needs a destructor (often to delete a resource that is initialized by a constructor, such as a pointer on a heap), copy ctor, assign Operaotr are also required.

However, the following compilers are bound to synthesize ctor:

Class that contains classes (such as vectors), the compiler calls its default constructor to initialize the member.

The compiler initializes the vptr if the class contains virtual functions.

The class is virtual-inherited, to initialize the offset of the virtual base class in this class.

If only these types, the compiler synthesis of the ctor is very useful. Note, however, that if you have a built-in data type, we need to create the ctor ourselves and initialize the built-in data members.

For more information, see another blog post:

2. If you do not want to use the compiler to synthesize the copy ctor, copy assign need to explicitly deny

The compiler will synthesize these two functions for us when necessary, but for some classes we do not need them (for example, the class in iostream, or some kind of first zero resource, etc.).

At this point we need to explicitly reject: The method is to declare the two as private, do not define them.

Class Home {

Public

...

Private

Home (const home&); declarations without defining them

Home &operator= (const home&);

};

The compiler issues an error when the class attempts to copy home (no access). An error has been issued for the member function and the FRIEND function linker (with access, but when there is no declaration defined).

Another way is to define a base class: There is an error at compile time and no access rights.

Class Uncopyable {

Protected://Allow derived class construction and destruction

Uncopyable () {}

~uncopyable () {}//does not need to be virtual, this is not a polymorphic base class. And without data members, the space base class optimization can be implemented.

Private://block coping

Uncopyable (const uncopyable&);

Uncopyable &operator= (const uncopyable);

};

Class Home:private Uncopyable {//private Inheritance does not necessarily require public inheritance

...//not declaring the copy constructor, copy assign operator

};

3. Declaring the virtual destructor for a polymorphic base class

Principle:

We need to declare the base class destructor as virtual when the class we write is used as a base class and polymorphic use of the base class (a pointer or reference to the base class will handle the inheriting class object).

The reason is that when the pointer to the destructors class points to a derived class (on the heap), undefined behavior occurs when we delete the pointer (first calling the base class destructor, discovering that the derived class destructor is not called by virtual).

In most cases, the base class object is only refactored, and the derived class is not destroyed, resulting in a partial destroy.

If the class has a virtual function (allowing derived classes to be customized), there should be a virtual destructor.

For a class that is not a base class, we should not declare a virtual destructor.

However, some classes can be used as base classes, but do not want to be polymorphic, so we should not declare virtual destructors. As in the previous section, Uncopyable, String, STL containers, their data members are often protect, we can inherit, but not polymorphic.

Finally, of course, do not derive them.

4. Destructors should never throw exceptions

C + + does not prohibit destructors from throwing exceptions, but should not do so. This will certainly lead to premature termination or ambiguous behavior.

When other functions throw an exception, the stack unwind (stack unwind) takes place (the purpose is to catch the exception, the field information of the function call, etc.), the object's destructor is called, and if the destructor throws an exception, the program terminates prematurely or an ambiguous behavior occurs.

If a normal call destructor, the destructor throws an exception, the code after the exception call point does not execute, in which there may be recycling resources, there is a resource leak.

Then a stack expansion occurs, and an exception program is thrown prematurely to terminate or to cause ambiguous behavior.

Several common processing methods:

When there is a pointer in the class:

Class Test {

Public

Test (int val): p (new int (val)) {}

~test () {delete p;}

Private

int *p;

};

When a class contains pointers, destructors and two copy functions are often required.

At this point, new may throw an exception bad_alloc.

When a class destructor needs to handle some necessary operations, such as CLOSE_USB, close_db (shutting down the database), the destructor may throw an exception.

Take a database connection as an example:

Responsible for database connection

Class DbConnection {

Public

Static dbconnection Create (); Online

void Close (); Failed to throw exception when online is turned off

};

Management DbConnection

Class Dbconn {

Public

~dbconn ()

{

Db.close ();
}

Private

DbConnection DB;
};

We can use this:

Dbconn DBC (Dbconnection::create ());

Automatically call ~dbconn (); close (); But this is just the ideal state, and if close () throws an exception, the destructor throws an exception and there is a problem.

Possible procedure 1: Throw an exception on the end of the program, call Abort () to complete.

Dbconn::~dbconn ()

{

try {

Db.close ();

} catch (...) {

Log some necessary information to indicate that close () failed.

Std::abort;

}
}

Possible procedure 2: Swallow the abnormality

Dbconn::~dbconn ()

{

try {

Db.close ();

} catch (...) {

Log some necessary information to indicate that close () failed.

}

}

It is generally believed that swallowing an exception is a bad idea because it suppresses important information about "certain actions fail."

But sometimes it's better than terminating directly.

Both of these possibilities are not good, and a good practice is to re-design the dbconn and give the client a handle on the exception.

Class Dbconn {

Public

void Close ()//new function used by customer

{

Db.close ();

closed = true;
}

~dbconn ()

{

if (! closed) {

try {

Db.close ();

} catch (...) {

Log some necessary information to indicate that close () failed.

}

}

Private

DbConnection DB;

BOOL closed;
};

This gives the client a chance to deal with the error, if the client does not call this close, the destructor is called.

Here Db.close () throws an exception, and we should never throw it in a destructor, but rather like close () here, perform the operation in a common function, giving the client the opportunity to handle the exception.

5. Never call the virtual function in a constructor or destructor

Call a virtual function in the constructor:

Virtual functions involve base classes and derived classes. A virtual function is never dropped to a derived class during the constructor of the base class, that is, the virtual function is not a virtual function at this time. Root cause during the base class construction of a derived class object, the type of the object is a base class and not a derived class.

Not only virtual functions are parsed as base classes by the compiler, but run-time type information (dynamic_cast, typeid) is also considered a base class type.

Reasons to do so:

The constructor of the base class executes earlier than the derived class constructor, when the base class constructor executes when the derived class member has not been initialized. Using these member variables that have not been initialized at this point can cause ambiguous behavior. C + + does not allow you to do so.

Call a virtual function in a destructor: once the destructor for the derived class starts executing, the derived class member variables within the object are in the defined value, and C + + does not seem to exist. Entering the base class destructor object becomes the base class object.

And any part of C + + includes virtual functions, dynamic_cast, and so on.

A constructor or destructor may place the same code that needs to be executed in a function, such as: Init (), Destroy (), which may call a virtual function, which is relatively covert and not easily perceptible.

How do you know if a virtual function is called?

Make sure your constructors and destructors do not call virtual functions, and that all of the functions they call meet this requirement.

One way to solve this problem is for the derived class constructor to pass the necessary information to the base class constructor, which can safely call a non-virtual function.

Class Base {

Public

Explicit base (const std::string &loginfo)

{

Log (Loginfo);

}

void log (const std::string &loginfo) const; At this point, the non-virtual function
};

Class Derived:public Base {

Public

Derived (para): Base (Create_loginfo (Para)) {}//pass log information to the base class constructor

Private

Static std::string Create_loginfo (para); A static member function, which does not call a member function, can be used to pass a parameter using a member function.
};

6. opreator=

Because the built-in assignment operator returns a reference to the left operand. So the correct form is:

TestClass &operator= (const TestClass &RHS)

{

...

return *this; Returns the left operand
}

The question to deal with is how to handle self-assignment:

Class Bitmap {

...
};

Class Wrapper {

...

Private

Bitmap *PB; Point to an object allocated from the heap

};

Wrapper &operator (const wrapper &RHS)

{

Delete PB;

PB = new Bitmap (*RHS.PB);

return *this;
}

When self-assignment is not handled: the resource of PB is already reclaimed, the value he executes is in an undefined state (random value), and *RHS.PB is a deleted object new cannot get the correct pointer.

Process Self-Assignment Method 1: Certificate and Test (identify tests);

Wrapper &operator (const wrapper &RHS)

{

if (&rsh = = this) {//Self-assignment does nothing

Reuturn *this;

}

Delete PB;

PB = new Bitmap (*RHS.PB);

return *this;
}

Method 2: The problem with Method 1 is that there is no exception security: If new throws an exception, Pb points to the bitmap that have been deleted. The good thing is to make it "exception safe", with the help of preventing self-assignment.

By arranging the order of the sentences rationally

Wrapper &operator (const wrapper &RHS)

{

Bitmap *old = PB;

PB = new Bitmap (*RHS.PB); If the exception is thrown in the original state

Delete old;

return *this;
}

You can put the certificate in front of the test, but doing so will make the code larger and slow down execution. We need our own "self-assignment" frequency to occur.

Method 3:copy and swap technology, which is also a way of abnormal security.

Wrapper &operator (const wrapper &RHS)

{

Wrapper tmp (RHS);

Swap (TMP);

return *this;
}

The following approach is equivalent to this: Using an argument copy is not clear enough, but sometimes produces more efficient code

Wrapper &operator (wrapper RHS)

{

Swap (TMP);

return *this;
}

When a function is manipulating more than one object, we want to ensure that the same object is still in the correct behavior.

7. The coping function must copy each part

If the derived class constructor does not call the constructor of the base class, the default constructor for the base class is called, and if there is no default constructor, it cannot be compiled successfully.

The copy constructor also has the same problem, if the copy constructor does not call the base class's constructor, it also calls the base class's default constructor, causing the data members of the base class to still be part of the base class.

The data members of the derived class are initialized with the derived class data in the const TestClass &RHS, resulting in inconsistent data.

The copy assign operator is somewhat different from copy ctor, and it does not modify the base class data members, which remain unchanged.

So what we're going to do is to copy all the member variables in the object, and call the base class's appropriate constructor base (RHS), call the base class's operator= (RHS) to finish initializing all the data on the base class.

Precautions:

We cannot make the copy assignment operator call the copy constructor, because the copy constructor is used to construct the object, which is equivalent to constructing an already existing object.

Similarly, making the copy constructor call the copy assignment operator is not allowed because the copy assignment operator is acting on an object that has already been initialized, and the object is not yet constructed.

Correct practice:

Put their similar code in a private member function, often named Init.

The most important thing is: we need to know when we have to write our own coping function, instead of using the compiler default composition. See the previous story.

C + + What we have to know about specific practices (1)--The story behind construction, copying constructs, destructors, assignment operators

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.