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++》

 

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

<think>嗯,用户问的是C++中赋值运算符重载为什么要返回引用。首先,我得回忆一下赋值运算符的基本概念。赋值运算符用于将一个对象的值赋给另一个对象,比如a = b。在C++中,如果我们不自己重载赋值运算符,编译器会生成一个默认的,但有时候默认的浅拷贝不够,特别是涉及到动态内存的时候,这时候需要自己重载。 那用户的问题是为什么返回引用呢?根据之前的引用资料,比如引用[3]和[4],提到返回类型是对象类型的引用,并且返回*this。可能的理由有几个:首先是支持链式赋值,比如a = b = c。如果返回的是值而不是引用,那么每次赋值都会创建临时对象,增加不必要的拷贝开销。返回引用的话,可以直接返回当前对象的引用,避免复制。 然后,考虑效率问题。返回引用避免了不必要的拷贝构造,尤其是当对象比较大时,这样可以提高效率。另外,保持与内置类型行为一致,比如int类型在赋值时也是返回左值引用,这样用户自定义类型的行为和内置类型一致,符合直觉。 另外,根据引用[2],赋值运算符的参数可以有其他参数,但必须有默认值,所以重载时参数通常是常量引用,这样可以避免拷贝,同时防止修改原对象。返回引用的话,可以确保连续赋值的正确性。 可能用户也会疑惑,如果不返回引用会怎样。比如,如果返回的是值,那么a = b = c实际上会变成a = (b = c),而(b = c)会生成一个临时对象,然后a被赋予这个临时对象的值。虽然功能上可能没问题,但效率不高,而且如果涉及到资源管理,可能会有多次不必要的拷贝甚至资源泄漏。 所以总结起来,返回引用的主要原因是为了支持链式调用,提高效率,保持与内置类型行为一致。同时,确保赋值后的对象可以被继续操作,比如作为左值使用。 用户可能还想知道如何正确实现赋值运算符重载,比如处理自赋值、释放旧资源等,但当前问题主要关注返回引用的原因。需要确保回答中涵盖这些关键点,并引用提供的资料来支持解释。</think>### C++赋值运算符重载函数返回引用的原因及作用 #### 1. **支持链式赋值操作** C++的赋值操作符允许连续赋值,例如:`a = b = c`。若赋值运算符返回的是**对象引用**(如`ClassName&`),则`b = c`的返回值可以直接作为`a = ...`的操作数,避免中间临时对象的额外拷贝。若返回值而非引用,链式赋值会产生多次拷贝构造和析构开销[^3][^4]。 #### 2. **保持与内置类型行为一致** 内置类型(如`int`、`double`)的赋值操作符返回**左值引用**,例如`(a = b) = c`是合法的。通过返回引用,用户自定义类型的行为与内置类型一致,符合开发者直觉[^1]。 #### 3. **避免不必要的拷贝开销** 返回引用时直接操作原对象,无需调用拷贝构造函数生成临时对象。例如: ```cpp Date& operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; // 返回当前对象的引用 } ``` 若返回`Date`而非`Date&`,每次赋值会触发一次拷贝构造,效率降低。 #### 4. **允许对赋值后的对象进行再操作** 返回引用时,赋值后的对象仍可作为左值使用。例如: ```cpp (obj1 = obj2).doSomething(); // 对赋值后的obj1调用成员函数 ``` 若返回临时对象(值传递),则无法进行后续操作[^3][^4]。 --- ### § 相关问题 § 1. 如何避免赋值运算符重载中的自赋值问题? 2. 赋值运算符重载与拷贝构造函数的区别是什么? 3. 为什么C++默认生成的赋值运算符可能不适用于动态内存管理?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值