首先,operator=的返回值通常是一个类的引用,这一点需要和C++标准库的容器类的operator=保持一致
其次,如果一个类中含有指针成员,那么要防止自赋值(左值对象和右值对象的指针成员指向同一块内存区域或同一个对象)
仍然以https://blog.csdn.net/Master_Cui/article/details/109532520中的mystring为例
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也就没用了
见博客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/109147470、https://blog.csdn.net/Master_Cui/article/details/109264151、https://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++》
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出