C++ 之 重载赋值操作符

下面是一个基类 Bitmap 和派生类 Widget, Widget 中定义了一个私有类型 (private) 指针 pb

class Bitmap { ... };

class Widget {
    ...

private:
    Bitmap *pb; // ptr to a heap-allocated object
};

  当在 Widget 类中重载赋值操作符 "=" 时,需要考虑以下几个方面

1  链式赋值

首先要考虑的是,链式赋值 (chain of assignments) 的情况,如下所示:

int x, y, z;

x = y = z = 15; // chain of assignments

整数 15 首先赋值给 z,得到新值的 z 再赋值给 y,接着得到新值的 y 最后再赋值给 x

x = (y = (z = 15));

为了实现链式赋值,函数的返回值须是一个实例自身的引用,也即 *this; 同理,重载其它的复合赋值运算符 (如 +=, -=, *=, /=),也必须在函数结束前返回 *this

Widget& Widget::operator=(const Widget& rhs)
{
    delete pb;  // stop using current bitmap

    pb = new Bitmap(*rhs.pb);  // start using a copy of rhs‘s bitmap

    return *this;
}

2  自赋值

其次要考虑的是,关于自赋值 (self-assigment) 的情况,虽然显式的自赋值并不常见,但潜在的自赋值仍需注意

Widget  w;
  ...
w = w;  // explict assignment to self

a[i] = a[j];  // potential assignment to self

*px = *py;  // potential assignment to self

解决方法是,在函数内加一个 if 语句,判断当前实例 (*this) 和传入的参数 rhs 是不是同一个实例,也即判断是不是自赋值的情况

如果是自赋值,则不作任何处理,直接返回 *this;如果不是自赋值,首先释放实例自身已有内存,然后再分配新的内存,如下所示:

Widget& Widget::operator=(cosnt Widget& rhs)
{
    if (this == &rhs) return *this; // identity test: if a self-assignment, do nothing

    delete pb;
    pb = new Bitmap(*rhs.pb);

    return *this;
}

3  异常安全

上例中,假如在分配内存时,因内存不足或 Bitmap 的拷贝构造函数异常,导致 "new Bitmap" 产生异常 (exception),则 pb 指向的是一个已经被删除的 Bitmap

因此,除了链式赋值和自赋值外,还需要考虑异常安全的情况,如下代码所示: 假如 "new Bitmap" 抛出一个异常,pb 指针并没有改变

Widget& Widget::operator=(cosnt Widget& rhs)
{    if (this == &rhs) return *this; // identity test
    Bitmap *pOrig = pb; // remember original pb

    pb = new Bitmap(*rhs.pb); // make pb point to a copy
    delete pOrig;  // delete the original pb

    return *this;
}

如果不考虑效率的问题,那么即使没有对自赋值进行判断的 if 语句,其后面的语句也足以应付自赋值的问题

4  拷贝-交换

上例中,因为效率的问题,保留了 if 语句,但实际上,因为自赋值出现的概率很低,所以上述代码看似“高效”,其实并不然

最常用的兼顾自赋值和异常安全 (exception safety) 的方法是 “拷贝-交换” (copy-and-swap),如下所示:

Widget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs);  // make a copy of rhs‘s data

    std::swap(*this, temp); // swap *this‘s data with the copy‘s

    return *this;
}

上述代码使用的是标准库的 swap 函数,当然也可以自定义 swap 函数

小结:

1) 重载类赋值操作符,首先考虑链式赋值 -- 函数返回 *this,其次考虑自赋值和异常安全 -- “拷贝-交换”

2) 被重载的类赋值操作符 "=" 必须定义为成员函数,其它的复合赋值操作符 (如 "+=", "-=" 等) 应该被定义为成员函数

参考资料:

<Effective C++_3rd> item 10, 11

<剑指 offer> 2.2.1

时间: 2024-12-12 23:57:47

C++ 之 重载赋值操作符的相关文章

赋值构造函数(重载赋值操作符)(c++常问问题二)

*什么是赋值构造函数(重载赋值操作符) 下面的代码演示了什么是赋值构造函数,如果不人为定义赋值构造函数,系统将默认给你分配一个浅拷贝的赋值构造函数(下面例子为深拷贝的赋值操作) class cat { public: //构造函数 cat():m_pMyName(NULL),m_unAge(0) { cout<<"cat defult ctor"<<endl; } //子类赋值构造函数(重载赋值操作符) cat& operator=(cat& o

C++重载赋值操作符

1.C++中重载赋值操作函数应该返回什么? 类重载赋值操作符一般都是作为成员函数而存在的,那函数应该返回什么类型呢?参考内置类型的赋值操作,例如 int x,y,z; x=y=z=15; 赋值行为相当于x=(y=(z=15)),也就是赋值操作应该返回左操作数的引用,因此,为了和内置类型兼容,类中重载赋值操作符应该返回左操作数的引用,即*this,如下类A的重载赋值操作函数的声明, class A{}; A& A::operator=(const A&); 2.确保重载赋值操作具有良好的行为

C++ 拷贝构造函数和重载赋值操作符相互调用分析 [转]

结论: 从面相对象编程的角度考虑,拷贝构造函数调用重载赋值操作符,重载赋值操作符调用拷贝构造函数的写法都是没有意义的.应该避免. Don't try to implement one of the copying functions in terms of the other. Instead, put common functionality in a third function that both call. ——Effective C++ Third Edition By Scott M

C++ 数组操作符重载、函数对象分析、赋值操作符

string类型访问单个字符 #include <iostream> #include <string> #include <sstream> using namespace std; //访问 string单个字符 int main() { string s = "1a2b3a4c"; int n = 0; for(int i=0 ; i< s.length() ; i++) { if(isdigit(s[i]))//isdigit 判断字符

【c++】c++中重载输出操作符,为什么要返回引用

针对:ostream & operator <<(ostream & os, const ClassType &object) 说明几点: 1.第一个形参为对ostream对象的引用,在该对象上将产生输出,ostream为非const,因为写入到流会改变流的状态:该形参是一个引用,因为不能复制ostream对象(在c++中定义的标准输入输出流类istream和ostream,其中拷贝构造函数和赋值操作符函数都被放置在了private部分,且只有声明,没有定义). 2.第

拷贝构造函数 和 赋值操作符重载

什么时候需要定义自己的拷贝构造函数: 当类中包含有,动态分配成员 或者 指针 的时候. 如果使用默认构造函数,则新构造出来的 新类 和 旧类 里面的指针成员 指向同一个空间, 而当其中一个类 清空掉那个空间 .另一个类的指针就会变成野指针(因为空间已经被清空) , 也就是说默认构造函数是复制值(地址也是值) ps.基本数据类型的数组可以直接使用默认复制构造函数.也就是说有涉及到 自行开辟额外空间 的数据类型 和 指针 的类就需要 什么时候调用拷贝构造函数: 1.一个对象以 值传递 的方式 传入

C++学习笔记13:运算符重载(赋值操作符2)

移动语义 完成所有权的移交,当拷贝构造和赋值构造时,目标对象的所有权必须移交给我们的新的对象,原始对象将丧失所有权,_p指针将不再指向原来的那个数组: 左值与右值 C原始定义 左值:可以出现在赋值号的左边或者右边 右值:只能出现在赋值号的右边 C++的定义 左值:用于标识非临时对象或者非成员函数的表达式 右值:用于标识临时对象的表达式或与任何对象无关的值(纯右值),或用于标识即将失效的对象的表达式(失效值) 左值引用与右值引用 左值引用:& 右值引用:&& 深拷贝需要频繁分配和释放

C++学习笔记12:运算符重载(赋值操作符1)

为数偶类定义专用的赋值操作符 class Couple { public: Couple(int a = 0, int b = 0) :_a(a), _b(b) {} Couple(const Couple &c):_a(c._a),_b(c._b){} Couple &operator=(const Couple &c); private: int _a, _b; }; Couple & Couple::operator=(const Couple &c) { i

C++复制控制:赋值操作符和析构函数

一.赋值操作符 类定义了该类型对象赋值时会发生什么.与拷贝构造函数一样,如果类没有定义自己的赋值操作符,编译器会合成一个. 1.重载操作符的简单介绍 重载操作符是一些函数,其名字为operator后跟着所定义的操作符的符号,通过定义名为operator=的函数,我们可以对赋值进行定义.操作符函数的形参表必须具有与该操作数数目相同的形参(如果操作符是一个成员,则包括隐式this形参).赋值是二元操作符,对应的两个形参,第一个形参为左操作数,第二个形参为右操作数. 注意: (1)当操作符为成员函数时