C++ 惯用法之 Copy-Swap 拷贝交换

这篇具有很好参考价值的文章主要介绍了C++ 惯用法之 Copy-Swap 拷贝交换。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C++ 惯用法之 Copy-Swap 拷贝交换

这是“C++ 惯用法”合集的第 3 篇,前面 2 篇分别介绍了 RAII 和 PIMPL 两种惯用法:

  • RAII: Resouce Acquistion Is Initialization
  • PIMPL:Pointer To Implemetation

正式介绍 Copy-Swap 之前,先看下《剑指 Offer》里的第☝️题:

如下为类型 CMyString 的声明,请为该类型添加赋值运算符函数。

class CMyString {
public:
  CMyString(char* pData = nullptr);
  CMyString(const CMyString& str);
  ~CMyString();

private:
  char* m_pData;
};

这道题目虽然基础,但考察点颇多,有区分度:

  • 返回值类型应为引用类型,否则将无法支持形如 s3 = s2 = s1 的连续赋值
  • 形参类型应为 const 引用类型
  • 无资源泄露,正确释放赋值运算符左侧的对象的资源
  • 自赋值安全,能够正确处理 s1 = s1 的语句
  • 考虑异常安全

解法 1

CMyString& operator=(const CMyString& str)
{
    if(this == &str)
        return *this;

    delete[] m_pData;
    m_pData = nullptr;
    m_pData = new char[strlen(str.m_pData) + 1];
    strcpy(m_pData, str.m_pData);
    return *this;
}

上面代码有些细节需要注意:

  • 删除数组使用 delete[] 运算符
  • strlen 计算长度不含字符串末尾的结束符 \0
  • strcpy 会拷贝结束符 \0

解法 1 满足考察点中除异常安全外的所有要求:new 的时候可能由于内存不足抛异常,但此时赋值运算符左侧的的对象已被释放,m_pData 为空指针,导致左侧对象处于无效状态。

解决方案:只要先 new 分配空间,再 delete 释放原来的空间即可。这样可以保证即使 new 失败抛异常,赋值运算符左侧对象也尚未修改,仍处于有效状态。

解法 2

《剑指 Offer》中给出了更好的解法:先创建赋值运算符右侧对象的一个临时副本,然后交换赋值运算符左侧对象和该临时副本的 m_pData,当临时对象 strTemp 离开作用域时,自动调用其析构函数,释放 m_pData 指向的资源(即赋值运算符左侧对象原来的内存):

CMyString& operator=(const CMyStirng& str)
{
    if(this != &str)
    {
        CMyString strTemp(str);
        char* pTemp = m_pData;
        m_pData = strTemp.m_pData;
        strTemp.m_pData = pTemp;
    }
    return *this;
}

解法 2 巧妙地利用了类原本的拷贝构造、析构函数自动进行资源管理,同时又不涉及底层的 new[]/delete[] 操作,可读性更强,也不容易出错。

解法 2 是 Copy-Swap 的雏形。C++ 中管理资源类通常会定义自己的 swap 函数,与其他拷贝控制成员(拷贝/移动构造、拷贝/移动赋值运算符、析构)不同,swap 不是必须,但却是重要的优化手段,以下是使用 Copy-Swap 惯用法的解法:

解法 3

class CMyString {
    friend void Swap(CMyString& lhs, CMyString& rhs) noexcept
    {
        // 对 CMyString 的成员逐一交换
        std::swap(lhs.m_pData, rhs.m_pData);
    }
    // ...
};

CMyString(CMyString&& str) : CMyString()
{
    Swap(*this, str);
}

CMyString& operator=(CMyStirng str)
{
    Swap(*this, str);
    return *this;
}

这里有几点需要注意:

  • 拷贝赋值运算符的形参类型不再是 const 引用,因为 Copy-Swap 需要先对赋值运算符右侧对象进行拷贝,这里直接使用值传递。这样一来,也使得 Copy-Swap 天然地异常安全、自赋值安全。
    • 异常安全:进入函数 operator=() 之前,先进行拷贝
    • 自赋值安全:形参是一个新创建的临时对象,永远不可能是对象自身
  • 不需要额外实现移动赋值运算符:如果赋值运算符右侧是一个右值,则自动调用 CMyString 的移动构造来构造形参

这还没完...

标准库 std::swap 及 ADL

C++ 标准库也提供了 swap 函数,理论上需要一次拷贝,两次赋值:

void swap(CMyString& lhs, CMyString& rhs)
{
    CMyString tmp(lhs);
    lhs = rhs;
    rhs = tmp;
}

其中 CMyString tmp(lhs) 会调用 CMyString 的拷贝构造进行深拷贝,效率上不如 CMyString 类自己实现的直接交换指针的效率高。

在进行 swap(v1, v2) 的调用时,如果类实现了自己的 swap 版本,其匹配程度优于标准库的版本。如果类没有定义自己的 swap,则使用标准库的 swap。这种查找匹配方式被称为 ADL(Argument-Dependent Lookup)。

注意不能使用 std::swap 形式,因为这样会强制使用标准库的 swap。正确的做法是提前使用 using std::swap 声明,而后续所有的 swap 都应该是不加限制的(这一点刚好和 std::move 相反):文章来源地址https://www.toymoban.com/news/detail-533977.html

void swap(Bar& lhs, Bar& rhs)
{
    using std::swap;
    swap(lhs.m1, rhs.m1);
    swap(lhs.m2, rhs.m2);
    swap(lhs.m3, rhs.m3);
}

最终的结果

class CMyString {
    friend void swap(CMyString& lhs, CMyString& rhs) noexcept
    {
        // 对 CMyString 的成员逐一交换
        using std::swap;
        swap(lhs.m_pData, rhs.m_pData);
    }
    // ...
};

CMyString(CMyString&& str) : CMyString()
{
    swap(*this, str);
}

CMyString& operator=(CMyStirng str)
{
    swap(*this, str);
    return *this;
}

到了这里,关于C++ 惯用法之 Copy-Swap 拷贝交换的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • python-浅拷贝(copy)与深拷贝(deepcopy)

    目录 一、前言: 二、深拷贝与浅拷贝的异同:         1.相同点:                2.不同点:         3.形象说明:         注意: 三、浅拷贝:  3.1.1浅拷贝示意图: 3.1.2示意图说明         1.对象与子对象(元素)的关系:         2.对象:         3.元

    2024年02月11日
    浏览(34)
  • Dockerfile 指令 COPY 拷贝文件夹

    网上查了查资料,这里记录一下。         今天在编写 dockerfile 时使用 COPY 拷贝文件夹时遇到了意料之外的情况。在此记录一下正确的使用方法。         今天在通过 dockerfile 将文件夹拷贝到镜像的时候发现,是把文件夹下的内容拷贝进去了。 dockerfile 如下:     

    2024年01月18日
    浏览(50)
  • C++ 中的 Pimpl 惯用法

    Pimpl(Pointer to Implementation)是一种常见的 C++ 设计模式,用于隐藏类的实现细节,从而减少编译依赖和提高编译速度。本文将通过一个较为复杂的例子,展示如何使用智能指针(如 std::unique_ptr )来实现 Pimpl 惯用法。 Pimpl 是 “Pointer to Implementation” 的缩写,这个模式可以帮助

    2024年02月10日
    浏览(36)
  • Kafka的零拷贝技术Zero-Copy

    流程步骤: (1)操作系统将数据从磁盘文件中读取到内核空间的页面缓存; (2)应用程序将数据从内核空间读入用户空间缓冲区; (3)应用程序将读到数据写回内核空间并放入socket缓冲区; (4)操作系统将数据从socket缓冲区复制到网卡接口,此时数据才能通过网络发送

    2024年02月08日
    浏览(36)
  • Secure Copy Protocol or SCP - 安全拷贝协议

    Secure Copy (remote file copy program) The SCP program is a software tool implementing the SCP protocol as a service daemon or client. It is a program to perform secure copying. The SCP server program is typically the same program as the SCP client. The SCP Server software can be installed on a Regular Machine and be configured to only accept SCP Traffic on

    2024年04月14日
    浏览(44)
  • 详解python列表等对象的赋值和复制(浅拷贝copy()及深拷贝deepcopy()的使用区别与示例)

    python虽然没有指针的概念,但是对象、引用、地址这些内容还是存在的,尤其是像列表对象、某类型对象的引用上,搞清楚对象变量的复制和赋值很有必要,不然容易出现“莫名其妙”的错误。 python中给一个变量 a 赋值列表实际上是创建了一个列表对象,并将该列表的地址赋

    2024年02月04日
    浏览(46)
  • ubuntu20.04 扩大交换空间swap

    检查当前swap情况 关闭现有的swap 创建一个新的swap文件 设定正确的权限 下面这个指令会把我们的空间变成可用的swap空间 启用swap文件 检查是否设置成功 参考: https://www.8kiz.cn/archives/11147.html

    2024年01月16日
    浏览(39)
  • Linux中内存交换空间(swap)之创建

    对于服务器而言,由于不知道何时会有大量请求的到来,因此,需要预留一部分swap来缓冲一下系统的内存用量。对于实际解决方法主要有两个: 添加硬盘,对硬盘进行分区,并格式化后用于内存交换空间。(将添加的硬盘当做swap partition使用) 不需要使用额外的硬盘,在已有的

    2024年02月03日
    浏览(37)
  • CAS是“Compare and Swap“(比较并交换)

    CAS是\\\"Compare and Swap\\\"(比较并交换)的缩写,是一种多线程同步的原子操作。它基于硬件的原子性保证,用于解决并发环境下的数据竞争和线程安全问题。 CAS操作包括三个参数:内存地址V、旧的预期值A和新的值B。它的执行步骤如下: 从内存中读取V的当前值; 比较当前值与预

    2024年02月08日
    浏览(54)
  • linux swap交换区满了怎么办(已解决)

    swap交换区满了怎么办 free -m 或free -h查看占用情况 使用如下指令来查看占用swap的前十进程 kill -9 pid杀死占用过多的进程 dd 可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出。 用到的参数如下: if=文件名:输入文件名,默认为标准

    2024年02月15日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包