C++11 移动构造函数的使用

 更新时间:2022年01月25日 10:08:04   作者:庭树  
本文主要介绍了C++11 移动构造函数的使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

一、引言

移动构造函数是什么?先举个例子,你有一本书,你不想看,但我很想看,那么我有哪些方法可以让我能看这本书?有两种做法,一种是你直接把书交给我,另一种是我去买一些稿纸来,然后照着你这本书一字一句抄到稿纸上。

显然,第二种方法很浪费时间,但这正是有些深拷贝构造函数的做法,而移动构造函数便能像第一种做法一样省时,第一种做法在 C++ 中叫做完美转发。

二、左值和右值

何为左值?能用取址符号 & 取出地址的皆为左值,剩下的都是右值。

而且,匿名变量一律属于右值。

int i = 1; // i 是左值,1 是右值

int GetZero {
    int zero = 0;
    return zero;
}
//j 是左值,GetZero() 是右值,因为返回值存在于寄存器中
int j = GetZero();

//s 是左值,string("no name") 是匿名变量,是右值
string s = string("no name");

三、深拷贝构造函数

用 g++ 编译器编译下列代码时记得加上参数 -fno-elide-constructors。

#include <iostream>
#include <string>

using namespace std;

class Integer {
public:
    //参数为常量左值引用的深拷贝构造函数,不改变 source.ptr_ 的值
    Integer(const Integer& source)
      : ptr_(new int(*source.ptr_)) {
        cout << "Call Integer(const Integer& source)" << endl;
    }
    
    //参数为左值引用的深拷贝构造函数,转移堆内存资源所有权,改变 source.ptr_ 的值
    Integer(Integer& source)
      : ptr_(source.ptr_) {
        source.ptr_ = nullptr;
        cout << "Call Integer(Integer& source)" << endl;
    }
    
    Integer(int value)
      : ptr_(new int(value)) {
        cout << "Call Integer(int value)" << endl;
    }

    ~Integer() {
        cout << "Call ~Integer()" << endl;
        delete ptr_;
    }

    int GetValue(void) { return *ptr_; }

private:
    string name_;
    int* ptr_;
};

int
main(int argc, char const* argv[]) {
    Integer a(Integer(100));
    int a_value = a.GetValue();
    cout << a_value << endl;
    cout << "-----------------" << endl;
    Integer temp(10000);
    Integer b(temp);
    int b_value = b.GetValue();
    cout << b_value << endl;
    cout << "-----------------" << endl;

    return 0;
}

运行结果如下。

Call Integer(int value)
Call Integer(const Integer& source)
Call ~Integer()
100
-----------------
Call Integer(int value)
Call Integer(Integer& source)
10000
-----------------
Call ~Integer()
Call ~Integer()
Call ~Integer()

在程序中,参数为常量左值引用的深拷贝构造函数的做法相当于引言中的第二种做法,“重买稿纸”相当于再申请一次堆内存资源,“重新抄写”相当于把匿名对象 Integer(100) 的资源拷贝到对象 a 这边;参数为左值引用的深拷贝构造函数的做法则相当于引言中的第一种做法,语句 ptr_(source.ptr_) 和 source.ptr_ = nullptr; 的作用相当于“我直接把书拿给你”。

由运行结果可以看出,当同时存在参数类型为常量左值引用和左值引用的深拷贝构造函数时,匿名对象 Integer(100) 只能选择前者,非匿名对象 temp 可以选择后者,这是因为常量左值引用可以接受左值、右值、常量左值、常量右值,而左值引用只能接受左值。因此,对于匿名变量,参数为任何类型左值引用的深拷贝构造函数都无法实现完美转发。还有一种办法——右值引用。

四、右值引用

右值引用也是引用的一种,参数类型为右值引用的函数只能接受右值参数,但不包括模板函数,参数类型为右值引用的模板函数不在本文讨论的范围内。

五、移动构造函数

移动构造函数是参数类型为右值引用的拷贝构造函数。

在“三”示例程序 Interger 类的定义中添加一个移动构造函数,其余保持原样。

//参数为左值引用的深拷贝构造函数,转移堆内存资源所有权,改变 source.ptr_ 的值
Integer(Integer& source)
  : ptr_(source.ptr_) {
    source.ptr_ = nullptr;
    cout << "Call Integer(Integer& source)" << endl;
}

//移动构造函数,与参数为左值引用的深拷贝构造函数基本一样
Integer(Integer&& source)
  : ptr_(source.ptr_) {
    source.ptr_ = nullptr;
    cout << "Call Integer(Integer&& source)" << endl;
}

Integer(int value)
  : ptr_(new int(value)) {
    cout << "Call Integer(int value)" << endl;
}

运行结果如下。

Call Integer(int value)
Call Integer(Integer&& source)
Call ~Integer()
100
-----------------
Call Integer(int value)
Call Integer(Integer& source)
10000
-----------------
Call ~Integer()
Call ~Integer()
Call ~Integer()

只有第二行跟先前不同,匿名对象 Integer(100) 也能通过移动构造函数实现完美转发。

大家可能会有疑问,上文提及到常量左值引用也可以接受右值,而右值引用也可以接受右值,那一个右值是否有可能会套入一个参数类型为常量左值引用的函数呢?答案是不会,一个右值要套入函数时,会优先选择套入参数类型为右值引用的函数。

可是仔细想想还是有点不满意,如果要让左值和右值的深拷贝都能实现完美转发,就需要写两个内容基本一样的拷贝构造函数,一个参数为(非常量)左值引用,一个参数为右值,那能不能只用一个函数就能实现左值、右值两者的深拷贝完美转发呢?答案就是强制类型转换,将左值强制强制转换为右值,再套入参数类型为右值引用的深拷贝构造函数。

六、std::move()

std::move() 能把左值强制转换为右值。

我们把语句 Integer b(temp); 改为 Integer b(std::move(temp)); 后,运行结果如下。

Call Integer(int value)
Call Integer(Integer&& source)
Call ~Integer()
100
-----------------
Call Integer(int value)
Call Integer(Integer&& source)
10000
-----------------
Call ~Integer()
Call ~Integer()
Call ~Integer()

从“10000”的上一行可以看出,std::move() 确实把左值 temp 转换为右值。

七、参考资料

《从4行代码看右值引用》 博客园用户“qicosmos(江南)” 著

到此这篇关于C++11 移动构造函数的使用的文章就介绍到这了,更多相关C++11 移动构造函数的使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言 scanf的工作原理详解

    C语言 scanf的工作原理详解

    这篇文章主要为大家介绍了C语言 scanf的工作原理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • C/C++中typedef的用法大全

    C/C++中typedef的用法大全

    typedef用法一共七种,分别是:为基本数据类型起别名、为结构体起别名、为指针类型起别名、为数组类型起别名、为枚举类型起别名、为模版函数起别名。本文就来分别讲讲这7个用法的具体实现吧
    2023-04-04
  • C++使用回溯法解决黄金矿工问题

    C++使用回溯法解决黄金矿工问题

    在矩阵中考察回溯算法,分为任意起点、左上角开始等情况。从而有不同的模板,其实区别就是直接开始还是每个坐标都去尝试,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-10-10
  • 深入理解QT多线程编程

    深入理解QT多线程编程

    本文主要介绍了QT多线程编程的深入理解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-06-06
  • C语言实现经典扫雷小游戏完整代码(递归展开 + 选择标记)

    C语言实现经典扫雷小游戏完整代码(递归展开 + 选择标记)

    这篇文章主要介绍了C语言小项目之扫雷游戏带递归展开 + 选择标记效果,本代码中,我们用字符 ! 来标识雷,文中附有完整代码,需要的朋友可以参考下
    2022-05-05
  • 一篇文章带你掌握C++虚函数的来龙去脉

    一篇文章带你掌握C++虚函数的来龙去脉

    虚函数主要通过V-Table虚函数表来实现,该表主要包含一个类的虚函数的地址表,可解决继承、覆盖的问题,下面这篇文章主要给大家介绍了如何通过一篇文章带你掌握C++虚函数的来龙去脉,需要的朋友可以参考下
    2022-10-10
  • 用pybind11封装C++实现的函数库的方法示例

    用pybind11封装C++实现的函数库的方法示例

    这篇文章主要介绍了用pybind11封装C++实现的函数库,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • C++ 汉诺塔问题知识点总结

    C++ 汉诺塔问题知识点总结

    在本篇文章里小编给大家整理的是关于C++ 汉诺塔问题知识点内容,有需要的朋友们可以参考下。
    2020-02-02
  • 在C/C++项目中合理使用宏详解

    在C/C++项目中合理使用宏详解

    这篇文章主要介绍了在C/C++项目中合理使用宏详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • 利用C语言实现简单三子棋游戏

    利用C语言实现简单三子棋游戏

    这篇文章主要为大家详细介绍了利用C语言实现简单三子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-03-03

最新评论