C++编程进阶3(如何写出正确的operator=、operator运算符的返回值以及是否应该是成员函数的讨论)

八、如何写出一个安全的operator=

首先,operator=的返回值通常是一个类的引用,这一点需要和C++标准库的容器类的operator=保持一致

其次,如果一个类中含有指针成员,那么要防止自赋值(左值对象和右值对象的指针成员指向同一块内存区域或同一个对象)

仍然以https://blog.csdn.net/Master_Cui/article/details/109532520中的mystring为例

假如operator=的实现如下

mystring & mystring::operator=(const mystring &rval)
{
	cout<<"operator=(const mystring &rval)"<<endl;
	delete this->data_;
	this->data_=new char[strlen(rval.data_)+1];
	strcpy(this->data_, rval.data_);
	return *this;
}

如果发生自赋值,那么delete时,会把左值和右值的指针成员指向的字符串同时释放,在执行strcpy时,就会将一个无效的字符串拷贝到this->data中。

int main(int argc, char const *argv[])
{
	mystring c1="1234";
	c1=c1;
	cout<<c1<<endl;
}

上述情况的解决办法有三种

1、添加处理自赋值

mystring & mystring::operator=(const mystring &rval)
{
	cout<<"operator=(const mystring &rval)"<<endl;
	if (this == &rval) return *this;//如果发现了自赋值,直接返回,不作任何内存操作
	delete this->data_;
	this->data_=new char[strlen(rval.data_)+1];
	strcpy(this->data_, rval.data_);
	return *this;
}

 

2、调整operator=中delete的时机

mystring & mystring::operator=(const mystring &rval)
{
	cout<<"operator=(const mystring &rval)"<<endl;
	char *pt=this->data_;
	char *rpt=rval.data_;
	this->data_=new char[strlen(rval.data_)+1];
	strcpy(this->data_, rpt);
	delete pt; 
	return *this;
}

输出结果同上

上述代码中,因为有可能是自赋值,所以,在new之前,需要保留右值的字符串,否则,new之后,this->data_和rval.data_都是空,strcpy也就没用了

 

3、copy and swap

见博客https://blog.csdn.net/Master_Cui/article/details/109532520

因为重新拷贝了一份原先的对象,所以在处理自赋值时,也是处理两个对象,所以不会有问题,上述三种方式推荐使用第三种

九、在一个类中添加了一个新的对象时,需要考虑是否需要更新构造函数和拷贝构造函数的初始化列表,还要考虑是否更新operator=的函数体。如果一个子类手动实现了拷贝构造函数,那么请显示调用基类的拷贝构造函数,否则,将调用基类的构造函数而不是默认的拷贝构造函数,此外,如果子类实现了operator=,那么在函数体中,也最好要手动实现并调用基类的operator=

具体见博客https://blog.csdn.net/Master_Cui/article/details/109899018

 

十、多使用智能指针管理动态内存

关于智能指针的使用和注意事项见博客https://blog.csdn.net/Master_Cui/article/details/109147470https://blog.csdn.net/Master_Cui/article/details/109264151https://blog.csdn.net/Master_Cui/article/details/109289758

十一、不要在函数传参的过程中执行函数调用

不要把函数调用直接作为函数的实参进行传递,比如像下面代码

int priority();
void process(shared_ptr<test> spt, int pri);
process(shared_ptr<test>(new test), priority());

第三行在调用process时,传递了一个智能指针和一个函数调用,上述代码转成汇编之后,因为CPU的乱序执行,执行过程很有可能是:1.执行 "new test"。2.调用priority。3.调用shared_ptr构造函数。但是如果在执行第二步的时候,priority函数如果出现了异常,那么就会导致shared_ptr的构造函数没有执行,从而导致test的内存无法释放。

解决办法有两个:

1、不要在函数传参的时候进行函数调用,在调用process前,单独调用priority函数,用一个变量接受priority的返回值,然后将该变量传入process。

int res=priority(); 
process(shared_ptr<test>(new test),  res);

2、将shared_ptr<test>(new test)所产生的对象去初始化一个shared_ptr<test>对象。然后再进行传参

shared_ptr<test> spt(new test); 
process(spt,  priority());

 

十二、如果函数把自定义的类作为函数的形参,请按引用传参而不是值传,因为可以提高效率并且防止基类和子类之间的切割。但是内置类型、指针、迭代器以及函数对象值传递和引用传递都OK

 

十三、关于namespace

namespace 和 classes 不同,前者可跨越多个源码文件而后者不能

C++标准库的组织方式就是有数十个头文件,而每个头文件声明std的某些机能。如果客户只想使用 vector相关机能,他不需要include<memory>。这允许程序员只对他们所用的那一小部分系统形成编译相依。

以此种方式切割机能并不适用于class成员,因为一个 class 必须整体定义,不能被跨文件分割。

 

十四、关于operator的返回值以及是否应该是成员函数

如果一个类的operator函数需要更新自身的某些成员,那么,返回的是*this的引用。如果一个类的operator函数不需要更新自身的某些成员,那么返回的对象的副本或者其他即可。

https://blog.csdn.net/Master_Cui/article/details/109515376中的分数类为例

其中只要是返回*this的,那么返回的一定是*this的引用,因为这样才能更新自身(左值)的成员,返回*this的引用的有operator=,operator+=(-=,*=,/=)以及前置operator++和--

其余的比较运算符返回的都是bool,而算数运算符返回的是类对象的副本,这两类运算符不涉及更新类对象本身,所以不用返回对象的引用。因为比较运算符和算符运算符都是二元运算符,操作对象的个数是两个,所以通常二元运算符设置为非成员函数,并且操作对象的构造函数一般都不是explicit的,将二元operator运算符设置为非成员函数并且操作对象的构造函数不是explicit的,不仅可以支持隐式转换,还可以像string那样操作一个字符串常量和一个string对象,扩展性增强

关于explicit见博客https://blog.csdn.net/Master_Cui/article/details/106885137

关于string的operator+运算见博客https://blog.csdn.net/Master_Cui/article/details/106363667

 

参考

《Effective C++》

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值