zoukankan      html  css  js  c++  java
  • C++ 之 重载赋值操作符

      Widget 中,有一个 Bitmap 型指针  pb

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

    1  重载 “op=” 

      在 Widget 类中重载 "=" 时,需考虑以下方面

    1.1  链式赋值

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

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

      相当于

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

    1.2  自赋值

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

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

    1.3  异常安全

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

      考虑异常安全,一个方法是先用 new 分配新内容,再用 delete 释放如下代码的内容,如下所示:当 "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); // 注意:"." 的优先级高于 "*" delete pOrig; // delete the original pb return *this; }

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

    2  拷贝-交换

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

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

    Widget& Widget::operator=(const Widget& rhs)
    {
        Widget temp(rhs);  // make a copy of rhs's data
        
        swap(temp); // swap *this's data with the copy's
        
        return *this;
    }

    2.1  std::swap

      std::swap 属于标准算法,其实现如下:

    namespace std 
    { template
    <typename T> // typical implementation of std::swap void swap(T& a, T& b) // swaps a's and b's values { T temp(a); a = b; b = temp; } }

      以上有三个拷贝:首先拷贝 a 给 temp,然后拷贝 b 给 a,最后拷贝 temp 给 b

    2.2  Widget::swap

      对于 Widget 类,实现两个 Widget 对象的值交换,只需互换 Bitmap *pb 即可,这称为 pimpl (pointer to implementation)

      首先,定义一个 swap 公有成员函数,如下:

    void Widget::swap(Widget& other)
    {
        using std::swap;
        swap(pb, other.pb);  // to swap Widgets, swap their pb pointers
    }

      然后,模板特例化 std::swap 函数,调用上面的 swap 函数,实现指针互换

    namespace std 
    {
        template<>     // revised specialization of std::swap
        void swap<Widget>(Widget& a, Widget& b)        
        {
            a.swap(b); // to swap Widgets, call their swap member function
        }
    }

    3  智能指针

      综上所述,重载赋值操作符,需要考虑链式赋值、自赋值和异常安全,颇为繁琐

      一个简化方法是,在 Widget 类中声明一个智能指针

    class Widget {
        ...
    private:
        std::unique_ptr<Bitmap> pBitmap; // smart pointer
    };

      此时,重载 "op=",则只需考虑链式赋值

    Widget& Widget::operator=(const Widget& rhs) // copy operator=
    {
        *pBitmap = *rhs.pBitmap;  // "." 的优先级高于 "*"
    return *this; }

      理论上应该可行,尚未在实际项目中验证 (留待后续测试...)

    小结:

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

    2) 考虑写一个不抛异常的 swap 函数 (consider support for a non-throwing swap)

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

    4) 类中使用智能指针,可大大简化重载赋值操作符 “op=” 的实现

    参考资料:

     <Effective C++_3rd> item 10, 11, 25

     <剑指 offer> 2.2.1

     <Effective Modern C++> item 22

  • 相关阅读:
    [LeetCode] Insert Interval
    java 可变參数
    谈谈单元測试之(二):測试工具 JUnit 3
    我的csdn博客搬家了
    leetcode 229: Majority Element II
    向MapReduce转换:生成用户向量
    《31天成为IT服务达人》最新文件夹
    SD卡读写之FileNotFoundException: /storage/emulated/0object.txt: open failed: ENOENT (No such file or dir
    当写烂代码的人离职之后....
    JavaSE Map的使用
  • 原文地址:https://www.cnblogs.com/xinxue/p/5448020.html
Copyright © 2011-2022 走看看