C++11 对象复制、右值、std::move、完美转发、万能函数包装器

1 对象之间的复制

同一个类的对象之间是可以进行复制的,即将一个对象的数据成员赋值给另外一个对象的相应数据成员。

定义 = 运算符 就是重载 “operator =” 成员函数

(1)如果在两个已创建的对象之间赋值,调用= 运算符

MyClass s1,s2;
....
s1 = s2 ;//将s2 的所有数据成员赋值给s1的相应的数据成员

 (2)如果创建新的对象并赋值,调用拷贝构造函数

Class A
{
    int m;
  public:
    A(){}  //默认构造函数
    A(int n) { m = n;}  //重载构造函数
    A(const A &b) {}  //拷贝构造函数
    A & operator = (const A &b) {} // 拷贝构造函数
    ~A{}  //析构函数
}

void main()
{
    A a(2),b(a),c;
    A d = a;
}

 A a(2) 调用重载构造函数;b(a)  调拷贝构造函数 创建对象b;c 调用默认构造函数 ;d = a 调用拷贝构造函数创建对象d  即调用 operator = ;

1.1 浅拷贝

当两个对象之间进行复制时,若完成复制后它们还共享某些资源(内存空间),其中一个对象的销毁会影响另一个对象,这种对象之间的复制称为浅复制。

class Student
{
    int no;
    char *pname;
     Student() {} //默认构造函数
     Student(int n,char *) {  //重载构造函数
        no = n;
        pname = new char [10]; //用new 分配内存空间
        strcpy(pname,p);
     }
    Student(Student &s){ //拷贝构造函数
        no = s.no;
        pname = s.pname;
    }
 
}

void main()
{
    Student s(10,"Mary"),t(s);
}

 在main 函数中建立一个对象s,通过拷贝构造函数由s建立t对象。拷贝之后,两个对象的 数据成员 pname 均指向相同的内存空间。

1.2 深拷贝

当两个对象之间进行复制时,当复制完成后它们不会共享任何资源(内存空间),其中一个对象的销毁不会影响到另一个对象,这种对象之间的复制称为深复制。

class Student
{
    int no;
    char *pname;
     Student() {} //默认构造函数
     Student(int n,char *) {  //重载构造函数
        no = n;
        pname = new char [10]; //用new 分配内存空间
        strcpy(pname,p);
     }
    Student(Student &s){ //拷贝构造函数
        no = s.no;
        pname = new char[strlen(s.pname) + 1];
        strcpy(pname, s.pname);
    }
 
}

void main()
{
    Student s(10,"Mary"),t(s);
}

 由对象s复制产生对象t,两个对象的pname成员分别指向不同的内存单元。

2 右值引用

2.1 左值与右值

1、在赋值表达式中,左值是指表达式结束依然存在的持久对象,右值指的是表达时结束后就不存在的临时对象

左值举例:

  • 函数名和变量名
  • 返回左值引用的函数调用
  • 前置自增/自减运算符连接的表达式++i/–i  (++i对i加1后再赋给i,最终的返回值就是i,所以,++i的结果是具名的,名字就是i)
  • 由赋值运算符或复合赋值运算符连接的表达式(a=b、a+=b、a%=b)
  • 解引用表达式*p
  • 字符串字面值”abc”

2、判断左值与右值的方法:可以对表达式取地址&的为左值,不可以对表达式取地址&的则为右值。

3、右值又分将亡值(xvalue),纯右值。

(1)纯右值:

  • 描述: 满足下列条件之一的:
    • 1)本身就是赤裸裸的、纯粹的字面值,如3、false;
    • 2)求值结果相当于字面值或是一个不具名的临时对象。
  • 举例:
    • 除字符串字面值以外的字面值
    • 返回非引用类型的函数调用
    • 后置自增/自减运算符连接的表达式i++/i–  (对于i++而言,是先对i进行一次拷贝,将得到的副本作为返回结果,然后再对i加1,由于i++的结果是对i加1前i的一份拷贝,所以它是不具名的
    • 算术表达式(a+b、a&b、a<b)   注释:a+b得到的是不具名的临时对象,而a&&b和a==b的结果非true即false,相当于字面值

(2)将亡值:

在C++11中,用左值去初始化一个对象或为一个已有对象赋值时,会调用拷贝构造函数或拷贝赋值运算符来拷贝资源,而当用一个右值(包括纯右值和将亡值)来初始化或赋值时,会调用移动构造函数或移动赋值运算符来移动资源,从而避免拷贝,提高效率。当该右值完成初始化或赋值的任务时,它的资源已经移动给了被初始化者或被赋值者,同时该右值也将会马上被销毁(析构)。也就是说,当一个右值准备完成初始化或赋值任务时,它已经“将亡”了。这种右值常用来完成移动构造或移动赋值的特殊任务,扮演着“将亡”的角色,所以C++11给这类右值起了一个新的名字——将亡值

举例:

  • 将要被移动的对象
  • T&& 函数的返回值
  • std::move返回值
  • 转换为T&&的类型的转换函数的返回值

2.2 右值引用

1、右值引用

&&(右值引用):右值引用是对一个右值进行引用的类型,右值不具名,只能通过引用的方式找到。

右值引用指向的是将要销毁的对象,也就是前面提到的a+2产出的临时对象,在对象销毁之前该引用可以接管其资源

2、universal references

T&& 不一定为右值,它绑定的类型是未定的,可以为右值,也可以为左值。

template<typename T>
void f(T&& param)

f(10);//10是右值
int x = 10;
f(x);//x是左值

param的类型取决于它的初始化,&& 被一个左值初始化,则为左值。

参考文献:

C++11 中的左值、右值和将亡值_guoxiaojie_415的博客-CSDN博客

T&&类型推导

3 std::move

3.1 std::move

std::move 将一个左值转换为右值引用类型。move 是将对象的状态或所有权从一个对象移动到另一个对象,只是转移,没有内存的拷贝。

int a = 1;
int&& b = std::move(a);//左值转换为右值

 输出结果:

a = 1

b =1

std::move 的实现:

template<typename T> 
decltype(auto) move(T&& param)
{
    using ReturnType = remove_reference_t<T>&&;
    return static_cast<ReturnType>(param);
}

详解C++移动语义std::move()_子木呀的博客-CSDN博客_c++ std::move

4 完美转发

4.1 完美转发

定义:完美转发是指函数模板在向其他函数传递自身形参时,如果相应实参是左值,则被转发为左值;同理如果相应实参为右值,则被转换为右值

std::forward 函数为转发函数,不管参数时T&& 这种未定的引用还是明确的左值引用或右值引用,它都会按照参数本来的类型转发。

void PrintT(int &t)
{
    cout << "lvalue" << endl;
}

void PrintT(T &&t)
{
    cout << "rvalue" << endl;
}

template<typename T>
void TestForward(T && v)
{
    PrintT(v);
    PrintT(std::forward<T>(v));
    PrintT(std::move(v));
}


Test()
{
    TestForward(1);
    int x =1;
    TestForward(x);
    TestForward(std::forward<int>(x));
}

输出结果

lvalue

rvalue

rvalue

(1)分析:

TestForward(1),1是右值,T&& 被初始化为一个右值,调用PrintT(v) 时,v是一个具名变量被转换为左值,输出左值

 PrintT(std::forward<T>(v)),v还是右值,则调用函数void TestForward(T && v),输出右值;

调用PrintT(std::move(v)),v还是右值,输出右值

lvalue

lvalue

rvalue

(2)分析

v是左值,调用PrintT(v) 和 PrintT(std::forward<T>(v))输出左值;

调用PrintT(std::move(v)),.左值转换为右值,输出右值

lvalue

rvalue

rvalue
(3)分析

同(2)

完整用例:

#include <iostream>
#include <memory>
#include <utility>
#include <array>

struct A {
    A(int&& n) { std::cout << "rvalue overload, n=" << n << "\n"; }
    A(int& n)  { std::cout << "lvalue overload, n=" << n << "\n"; }
};

class B {
public:
    template<class T1, class T2, class T3>
    B(T1&& t1, T2&& t2, T3&& t3) :
        a1_{std::forward<T1>(t1)},
        a2_{std::forward<T2>(t2)},
        a3_{std::forward<T3>(t3)}
    {
    }

private:
    A a1_, a2_, a3_;
};

template<class T, class U>
std::unique_ptr<T> make_unique1(U&& u)
{
    return std::unique_ptr<T>(new T(std::forward<U>(u)));
}

template<class T, class... U>
std::unique_ptr<T> make_unique(U&&... u)
{
    return std::unique_ptr<T>(new T(std::forward<U>(u)...));
}

int main()
{   
    auto p1 = make_unique1<A>(2); // rvalue
    int i = 1;
    auto p2 = make_unique1<A>(i); // lvalue

    std::cout << "B\n";
    auto t = make_unique<B>(2, i, 3);
}
//输出:
rvalue overload, n=2
lvalue overload, n=1
B
rvalue overload, n=2
lvalue overload, n=1
rvalue overload, n=3

4.3 std::move 与std::forward

std::move和std::forward本质就是一个转换函数,std::move执行到右值的无条件转换,std::forward执行到右值的有条件转换,在参数都是右值时,二者就是等价的。其实std::move和std::forward就是在C++11基本规则之上封装的语法糖。



 

5  万能函数包装器

右值引用、完美转发、可变参数模板可编写一个万能的函数包装器。

5.1 可变参数模板

1、可变参数模板概念

C++11 中的可变参数模板允许模板定义中包含0到任意个模板参数。声明可变参数模板时,需要在typename或class 后面带上省略号“...”。

省略号的作用:

  • 声明一个参数包,这个参数包可以包含0到任意个模板参数;
  • 在模板定义的右边,可以将参数包展开成一个一个独立的参数

2、可变参数模板函数

template<class ...T>
void f(T... args)
{

}

有两种展开参数包的方法:

  • 递归函数方式展开参数包
  • 逗号表达式和初始化列表方式展开参数包

(1)递归函数方式展开参数包

通过递归函数展开参数包,需要提供一个参数包展开的函数一个递归终止函数。递归终止函数是用来终止递归的。

#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <algorithm>
using namespace::std;

//递归终止函数
void print()
{
    cout << "empty" << endl;
}
//展开函数
template <class T,class ... Args>
void print(T head,Args... rest)
{
    cout << "parameter " << head << endl;
    print(rest...);//递归调用自己,每一次调用参数包中的参数就会减少一个,直到所有的参数展开位置
    //当没有参数是,则调用终止函数
}
int main()
{
    print(1,2,3,4);
    return 0;
}

 

(2)逗号表达式和初始化列表方式展开参数包

省略

参考:

可变参数模板

【1】C++11 std::move和std::forward:C++11 std::move和std::forward - 简书

【2】C++11新特性之 std::forward(完美转发)C++11新特性之 std::forward(完美转发) - yfceshi - 博客园

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值