ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

2、构造/析构/赋值运算

2021-07-12 19:02:01  阅读:146  来源: 互联网

标签:... const 运算 rhs public 函数 析构 class 赋值


条款05: 了解C++默认编写并调用了哪些函数

当你自己没有编写时,编译器会默认为你的类创建defult构造函数、析构函数、拷贝构造函数和copy assignment操作符。
如:

class Empty{};

实际上,相当于

class Empty{
public:
	Empty(){...}  //defualt构造函数
	Empty(const Empty& other){...} //copy构造函数
	~Empty(...)   //析构函数

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

当你自己写了构造函数后,编译器就不会为你生成defualt 构造函数了。
有两种情况下,编译器会拒绝执行copy assignment操作。

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; //此过程编译器会报错

因为nameValue是引用类型,C++不允许引用类型指向其他对象。所以当p.nameValue已经赋值引用对象后,不能更改,所以 p=s 操作便会被编译器拒绝执行。
同样的const成员不能更改,所以p=s操作时尝试更改p.objectValue,编译器也会拒绝执行。
另外,如果base classes将copy assignment声明为private时,编译器为拒绝为derived classes生成copy assignment操作符。


编译器可以暗自为class创建defalut构造函数、copy构造函数、copy assignment操作符以及析构函数

条款06:若不想使用编译器自动生成的函数,就该明确拒绝。

如果不想使用编译器支持copy或赋值等功能,可以将copy构造函数和copy assignment操作符声明为private。

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

但是这样写会出现问题,比如在member函数或friend函数中这么做时,会引起不必要的错误。所以更合适的写法是定义一个不可复制到base class,然后继承它,如下:

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

class HomeForSale : private Uncopyable{
};

条款07:为多态基类声明virtual析构函数

如果不这么做,会造成派生类没有被析构,产生不可预估的错误。

#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;
}

输出

derived hello
~Base

可以看到,派生类没有被析构,如果派生类里需要在析构函数里释放内存,就是造成内存泄漏。

但是不是说所有的类都要把析构函数定义为虚函数,因为虚函数需要维持一个虚表,可能会带来额外的内存使用。

带多态性质的基类应该声明一个virtual析构函数
如果class设计的目的不是作为基类或多态性,就不该声明virtual析构函数

条款08:别让异常逃离析构函数

class Widget{
public:
	...
	~Widget(){}   //假设这里会吐出一个异常
};
void  dosomthing(){
	std::vector<Widget> v;
	...      //v 在函数结束时自动销毁
}

这样就会造成vector里的Widget析构时产生多个异常,而C++在两个异常同时存在时会结束执行或产生未知错误。
最好的做法就是把异常交给客户处理,并且在析构函数里上双保险,捕获异常。

class DBConn{
public:
...
void close(){
	db.close();
	closed = true;
}
~DBConn(){
	if(!closed){
		try{
			db.close();
		}catch(...){
			//记录异常并关闭程序或吞下异常
		}
	}
}
private:
	DBConnction db;
	bool closed;
};

析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获它并处理。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数执行该操作。

条款09:绝不在构造或析构函数中调用virtual函数

在构造和析构期间不要调用virtual函数,因为这类调用从不下降至派生类

条款10:令operator=返回一个reference to *this

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

为了实现连锁赋值,赋值操作符需要返回一个reference执行操作符的左侧实参。

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

令assignment操作符返回一个reference to *this

条款11:在operator= 中处理“自我赋值”

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

如果参数rhs等于this,也就是发生了自我赋值时,就会造成pb在第一步就被释放掉了。最终this返回的对象指向了一个空指针,发生未知错误。
可以修改为这样

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;
};

如果是自我赋值则直接返回。
或者

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;
};

运用临时变量,先报错原来的指针,重新生成对象后,再删除原来的指针。

再或者使用swap

class Bitmap{...};
class Widget{
public:
	...
	Widget& operator=(const Widget& rhs){
		Widget temp(rhs);
		swap(temp);   //将*this 数据与temp进行交换
		return *this;
	}
private:
	Bitmap* pb;
};

确保当对象自我赋值时operator=有良好的行为
确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确

条款12:复制对象时勿忘其每一个成分

比如:

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;
	}
};

这样在派生类被调用拷贝时,会忘记复制基类的成员变量,造成错误。
正确的做法是:

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;
	}
};

Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”
不要尝试以某个Copying函数实现另一个Copying函数。应该将共同机能放进第三个成员函数中,并由两个Copying函数共同调用。

标签:...,const,运算,rhs,public,函数,析构,class,赋值
来源: https://blog.csdn.net/qq_36383272/article/details/116665355

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有