2. Construction / deconstruction / assignment operation

Clause 05: understand which functions are written and called by default in C + +

When you don't write it yourself, the compiler will create default constructor, destructor, copy constructor and copy assignment operator for your class by default.
For example:

class Empty{};

In fact, it's equivalent to

class Empty{
public:
	Empty(){...}  //defualt constructor
	Empty(const Empty& other){...} //copy constructor
	~Empty(...)   //Destructor

	Empty& opreate=(const Empty& other){...}  //copy assignment function
};

When you write the constructor yourself, the compiler won't generate the defualt constructor for you.
In two cases, the compiler will refuse to perform the copy assignment operation.

template<class T>
class NamedObject {
public:
	NamedObject(std::string& name, const T& value);
	...
private:
	std::string& nameValue;
	const T objectValue;
};
std::string NewDog("PersePhone");
std::string OldDog("Satch");
NameObject<int> p(newDog, 2);
NameObject<int> s(OldDog, 36);
p = s; //The compiler will report an error during this process

Because nameValue is a reference type, C + + does not allow reference types to point to other objects. Therefore, when p.nameValue has been assigned to the reference object, it cannot be changed, so the p=s operation will be rejected by the compiler.
The same const member cannot be changed, so if you try to change p.objectValue during p=s operation, the compiler will refuse to execute.
In addition, if base classes declares copy assignment as private, the compiler refuses to generate a copy assignment operator for derived classes.


The compiler can secretly create the default constructor, copy constructor, copy assignment operator and destructor for class

Clause 06: if you do not want to use functions automatically generated by the compiler, you should explicitly refuse.

If you do not want to use the compiler to support functions such as copy or assignment, you can declare the copy constructor and copy assignment operator private.

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

However, there will be problems when writing this. For example, doing so in the member function or friend function will cause unnecessary errors. Therefore, it is more appropriate to define a class that cannot be copied to base class, and then inherit it, as follows:

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

class HomeForSale : private Uncopyable{
};

Clause 07: declare a virtual destructor for a polymorphic base class

If you do not do so, the derived class will not be destructed, resulting in unpredictable errors.

#include <iostream>
class Base{
public:
        Base(){}
        virtual void Hello(){std::cout << "base hello \n";}
        ~Base(){std::cout << "~Base\n";}
};
class Derived : public Base{
public:
        Derived(){}
        void Hello() override{std::cout << "derived hello\n";}
        ~Derived(){std::cout << "~Derived\n";}
};
int main(){
        Base* base = new Derived();
        base->Hello();
        delete base;

        return 0;
}

output

derived hello
~Base

It can be seen that the derived class is not destructed. If the derived class needs to release memory in the destructor, it will cause memory leakage.

However, not all classes should define destructors as virtual functions, because virtual functions need to maintain a virtual table, which may bring additional memory use.

A base class with polymorphism should declare a virtual destructor
If the class is not designed as a base class or polymorphism, it should not declare a virtual destructor

Clause 08: don't let exceptions escape the destructor

class Widget{
public:
	...
	~Widget(){}   //Suppose there's an exception here
};
void  dosomthing(){
	std::vector<Widget> v;
	...      //v automatically destroy at the end of the function
}

This will cause multiple exceptions when the Widget in the vector is destructed, and C + + will end execution or generate unknown errors when two exceptions exist at the same time.
The best way is to give the exception to the customer for handling, and double insurance in the destructor to catch the exception.

class DBConn{
public:
...
void close(){
	db.close();
	closed = true;
}
~DBConn(){
	if(!closed){
		try{
			db.close();
		}catch(...){
			//Record the exception and close the program or swallow the exception
		}
	}
}
private:
	DBConnction db;
	bool closed;
};

Destructors should never spit out exceptions. If a function called by the destructor may throw an exception, the destructor should catch it and handle it.
If a customer needs to respond to an exception thrown during the operation of an operation function, class should provide a normal function to perform the operation.

Clause 09: never call the virtual function in the structure or destructor.

Do not call virtual functions during construction and destruction, because such calls never descend to derived classes

Clause 10: make operator = return a reference to *this

int x,y,z;
x=y=z=10;

In order to implement chained assignment, the assignment operator needs to return a left argument of the reference execution operator.

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

Make the assignment operator return a reference to *this

Clause 11: Handling "self assignment" in operator =

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

If the parameter rhs is equal to this, that is, when self assignment occurs, pb will be released in the first step. Finally, the object returned by this points to a null pointer, and an unknown error occurs.
It can be modified as follows

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

If it is self assigned, it returns directly.
perhaps

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

Using temporary variables, first report the error of the original pointer, regenerate the object, and then delete the original pointer.

Or use swap

class Bitmap{...};
class Widget{
public:
	...
	Widget& operator=(const Widget& rhs){
		Widget temp(rhs);
		swap(temp);   //Exchange * this data with temp
		return *this;
	}
private:
	Bitmap* pb;
};

Ensure that operator = behaves well when the object is self assigned
Determines if any function operates on more than one object, and multiple objects are the same object, its behavior is still correct

Clause 12: don't forget every component when copying an object

For example:

class Customer{
public:
	...
	Customer(const Customer& rhs):name(rhs.name){}
	Customer& operator= (const Customer& rhs){
		name = rhs.name;
		return *this;
	}
private:
	std::string name;
};

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

In this way, when the derived class is called and copied, it will forget to copy the member variables of the base class, resulting in an error.
The correct approach is:

class Customer{
public:
	...
	Customer(const Customer& rhs):name(rhs.name){}
	Customer& operator= (const Customer& rhs){
		name = rhs.name;
		return *this;
	}
private:
	std::string name;
};

class PriorityCustomer : public Customer{
public:
	...
	PriorityCustomer(const PriorityCustomer& rhs):
		Customer(rhs),
		priority(rhs.priority){}
	PriorityCustomer& operator=(const PriorityCustomer& rhs){
		Customer::operator=(rhs);
		priority = rhs.priority;
		return *this;
	}
};

The Copying function should ensure that "all member variables within the object" and "all base class components" are copied
Do not try to implement one Copying function with another. The common function should be put into the third member function and called by two Copying functions.

Keywords: C++

Added by eelmitchell on Tue, 18 Jan 2022 08:02:10 +0200