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

目录
  • 一、引言
  • 二、左值和右值
  • 三、深拷贝构造函数
  • 四、右值引用
  • 五、移动构造函数
  • 六、std::move()
  • 七、参考资料

一、引言

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

显然,第二种方法很浪费时间,但这正是有些深拷贝构造函数的做法,而移动构造函数便能像第一种做法一样省时,第一种做法在 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 移动构造函数的使用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++11中std::move、std::forward、左右值引用、移动构造函数的测试问题

    关于C++11新特性之std::move.std::forward.左右值引用网上资料已经很多了,我主要针对测试性能做一个测试,梳理一下这些逻辑,首先,左值比较熟悉,右值就是临时变量,意味着使用一次就不会再被使用了.针对这两种值引入了左值引用和右值引用,以及引用折叠的概念. 1.右值引用的举例测试 #include <iostream> using namespace std; ​ //创建一个测试类 class A { public: A() : m_a(55) { } ​ int m_a;

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

    目录 一.引言 二.左值和右值 三.深拷贝构造函数 四.右值引用 五.移动构造函数 六.std::move() 七.参考资料 一.引言 移动构造函数是什么?先举个例子,你有一本书,你不想看,但我很想看,那么我有哪些方法可以让我能看这本书?有两种做法,一种是你直接把书交给我,另一种是我去买一些稿纸来,然后照着你这本书一字一句抄到稿纸上. 显然,第二种方法很浪费时间,但这正是有些深拷贝构造函数的做法,而移动构造函数便能像第一种做法一样省时,第一种做法在 C++ 中叫做完美转发. 二.左值和右值 何为

  • C++11中移动构造函数案例代码

    目录 1. 拷贝构造函数中的深拷贝问题 2. C++移动构造函数(移动语义的具体实现) 1. 拷贝构造函数中的深拷贝问题 在 C++ 98/03 标准中,如果想用其它对象初始化一个同类的新对象,只能借助类中的拷贝构造函数.拷贝构造函数的实现原理很简单,就是为新对象复制一份和其它对象一模一样的数据.需要注意的是,当类中拥有指针类型的成员变量时,拷贝构造函数中需要以深拷贝(而非浅拷贝)的方式复制该指针成员. 举个例子: #include <iostream> using namespace std

  • 整理Java编程中字符串的常用操作方法

    字符 一般情况下,当我们处理字符时,我们用原始数据类型 char. 示例 char ch = 'a'; // Unicode for uppercase Greek omega character char uniChar = '\u039A'; // an array of chars char[] charArray ={ 'a', 'b', 'c', 'd', 'e' }; 然而在开发中,我们会遇到需要使用对象而不是原始数据类型的情况.为了达到这个需求.Java 为原始数据类型 char

  • JavaScript中的正则表达式简明总结

    一.定义正则表达式的方法 定义正则表达式的方法有两种:构造函数定义和正则表达式直接量定义.例如: 复制代码 代码如下: var reg1 = new RegExp('\d{5, 11}'); // 通过构造函数定义var reg2 = /\d{5, 12}/; // 通过直接量定义 正则表达式直接量字符       \o:NUL字符(\u0000)       \t:制表符(\u0009)       \n:换行符(\u000A)       \v:垂直制表符(\u000B)       \f:

  • C++中的new/delete、构造/析构函数、dynamic_cast分析

    1,new 关键字和 malloc 函数区别(自己.功能.应用): 1,new 关键字是 C++ 的一部分: 1,如果是 C++ 编译器,则肯定可以用 new 申请堆空间内存: 2,malloc 是由 C 库提供的函数: 1,如果没有相应的库,malloc 将不能使用: 2,有些特殊的嵌入式开发中,少了 C 库,则就不能动态内存分配: 3,new 以具体类型为单位进行内存分配: 1,面向对象中一般用 new,不用 malloc: 4,malloc 以字节为单位进行内存分配: 5,new 在申请内

  • 27个JavaScript数组常见方法汇总与实例说明

    1. push() 概括:数组末位增加 参数:需要增加的数据 返回值:数组更新后的长度 let arr = [1,2,3] arr.push(1) // 返回新增后的数组长度 4 arr.push() // 不传参数默认不新增 4 arr.push(1,2,3) // 新增多条数据时,返回新增完成后的数组长度 7 2. pop() 概括:数组末位删除 参数:无 返回值:删除的数据 let arr = [3] arr.pop() // 返回已删除的数据 3 arr.pop() // 当数组数据为空

  • 关于C++11的统一初始化语法示例详解

    前言 本文主要给大家介绍了C++11统一初始化语法的相关内容,关于在当前新标准C++11的语法看来,变量合法的初始化器有如下形式: X a1 {v}; X a2 = {v}; X a3 = v; X a4(v); 其实,上面第一种和第二种初始化方式在本质上没有任何差别,添加=则是一种习惯上的行为.使用花括号进行的列表初始化语法,其实早在C++98时代就有了,只不过历史上他们只是被用来对数组元素进行初始化操作,以及初始化自定义POD类型的数据(简单理解就是可以memcpy复制对象的类型).比如:

  • 详解C++11中的右值引用与移动语义

    C++11的一个最主要的特性就是可以移动而非拷贝对象的能力.很多情况都会发生对象的拷贝,有时对象拷贝后就立即销毁,在这些情况下,移动而非拷贝对象会大幅度提升性能. 右值与右值引用 为了支持移动操作,新标准引入了一种新的引用类型--右值引用,就是必须绑定到右值的引用.我们通过&&而不是&来获得右值引用.右值引用一个重要的特性就是只能绑定到将要销毁的对象. 左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值.一般而言,一个左值表达式表示的是一个对象的身份,而右

  • 浅谈C++ Explicit Constructors(显式构造函数)

    C++ 为类(Class)提供了许多默认函数.如果自己没有申明,编译器会为我们提供一个copy构造函数.一个copy assignment操作符和一个析构函数.此外,如果没有申明任何构造函数,编译器会为我们申明一个default构造函数.很像下面的Empty类: class Empty{ public: Empty(); Empty(const Empty &rhs); ~Empty(); Empty& operator=(const Empty &rhs); }; 就像Effec

随机推荐