C++11的右值引用、移动语义、完美转发的简单理解总结

1 右值引用

  • 函数返回过程:
    函数内一个非static的临时变量的生命周期就是在函数调用结束的时候,这样就会出现一个矛盾,函数内如何返回一个出了函数体就会消失的对象,可以推理出来,函数返回对象时会进行一次拷贝,也就是说,上层的函数内拷贝了一份下层函数返回的对象,然后下层函数内的临时对象就会被析构,而上层拷贝出来的新对象继续使用

  • 举个例子:

	class A 
	{
	  public:
	    int a;
	};
	A getTemp()
	{
	    return A();
	}
	int main()
	{
		A maina = getTemp();
	}

上面的代码的调用步骤:

  • 构造步骤:
  • 1.getTemp函数调用了A的构造函数构造出了一个临时对象
  • 2.构造一个临时对象返回给getTemp函数时拷贝一份作为getTemp()中的临时对象。
  • 3.从getTemp()返回的时候把函数内的临时对象又拷贝一份给main()函数中的maina对象(这也是临时对象,只不过有了个名字)
  • 析构步骤:
  • 1.最开始调用的A的构造函数返回给getTemp()就析构掉了。
  • 2.getTemp()的临时对象在出了getTemp()的作用域之后就会被析构掉。
  • 3.maina在出了main()函数的作用域以后也会被析构掉。
    所以可以看出上面的代码一共调用构造函数1次,拷贝了两次A析构了三次。

如果对象很大的话,不断进行临时对象的构造跟析构,对于程序来说开销很大,而且临时变量的产生和销毁对于程序员说是没有感觉的,因为并不影响程序的正确性。只会偷偷影响你程序的性能。(真坏)

C++11对此进行了优化,加一个移动构造函数,把上面代码的getTemp()生成的临时对象,直接给予到上层的main()函数,而不是让main()函数再去拷贝一份。然后在main()函数中接受到的这个临时对象,给他个名字maina。
这样就减少了临时对象的不断进行拷贝构造,然后再被析构掉。

C++11的移动构造函数参数是一个右值引用,先了解一下什么是左值右值。
在这里插入图片描述

  • 区分左右值可以看表达式是否可以用&符号取地址,可以取地址的为左值,不可以取地址的为右值。
		int i = 0;// i是左值, 0是右值
		&i; // 正确
		&0; // 错误

右值引用可以理解为对临时对象的引用,临时对象是程序员无法直观在代码上看到的,无法直观感受到临时对象的构造跟析构,给临时对象加一个引用,就相当于给临时对象的内存加了个名字
例如:A && a = getTemp();就是把getTemp() 里面的临时对象加了个名字a;

引用的区别:
左值引用只能绑定左值
右值引用只能绑定右值
但是常量左值引用是一个万能的引用,可以绑定非常量左值常量左值常量右值,只是绑定以后不能修改,只能读取

 int a = 0;
 int &b = 1; // 不对
 int &&c = a; // 不对
 const int d = 1;
 const int& e = a; //可以
 const int& f = 1; // 可以
 const int& g = d; // 可以

2.移动构造

C++类中有几种构造相关函数:默认构造函数普通构造函数拷贝构造函数移动构造函数
拷贝赋值函数移动赋值函数(重载运算符"=")

class A
{
public:
    // 默认构造函数
    A() 
    {
        str_ = new char[1];
        *str_ = '\0';
    };
    //普通构造函数
    A(const char* cstr)
    {
        if (cstr)
        {
            str_ = new char[strlen(cstr) + 1];
            strcpy(str_, cstr);
        }
        else
        {
            str_ = new char[1];
            *str_ = '\0';
        }
    };
     //拷贝构造函数
    A(const A& a)
    {
        str_ = new char[strlen(a.str_) + 1];
        strcpy(str_, a.str_);
    }
     //拷贝赋值函数
    A& operator = (const A& a)
    {
        if (this == &a)
            return *this;
        delete[] str_;
        str_ = new char[strlen(a.str_) + 1];
        strcpy(str_, a.str_);
        return *this;
    }
    //移动构造函数
    A(A&& a)noexcept
    {
        str_ = a.str_;
        a.str_ = nullptr;
    }
    // 移动赋值函数
    A& operator = (A&& a) noexcept
    {
        if (this == &a)
            return *this;
        delete[] str_;
        str_ = a.str_;
        this->str_ = a.str_;
        return *this;
    }
    ~A()
    {
        delete[] str_;
    }
    char* get_str() const { return str_; }
private:
    char* str_;
};
  • 移动构造函数与拷贝构造函数的区别
    拷贝构造的参数是const A&,是常量左值引用,而移动构造的参数是A&&右值引用,临时对象是个右值的时候,优先进入移动构造函数而不是拷贝构造函数
    就比如下面代码
	vector<A> vc;
    for (int i = 0; i < 100; i++) 
    {
        vc.push_back(A("aaa"));
    }

"aaa"是个右值调用构造函数构造出来一个临时对象以后,调用移动构造函数把构造出来的临时值直接移动到vector里面,而不是再拷贝一份到vector里面。

如果按照下面这样写:

	A s("aaa");
    for (int i = 0; i < 100; i++) 
    {
        vc.push_back(s);
    }

s是一个左值,就会调用拷贝构造函数,拷贝出来一份到vector里面,再把临时值析构掉。

注意:移动构造函数需要把移动之前原来的指针置空,要不然析构的时候就被析构掉移动过的数据了。。

那么如果希望使用左值的时候也可以调用移动构造函数,减少拷贝,需要怎么做呢?
C++11提供的移动语义std::move就可以解决上述问题

3.移动语义 std::move

std::move的唯一功能就是将一个左值强制转换成一个右值
那么我们这样写,就可以调用的是移动构造函数,而不是拷贝构造函数了。

	A s("aaa");
    for (int i = 0; i < 100; i++) 
    {
        vc.push_back(std::move(s));
    }
  • 注意:我们用移动语义调用一个移动构造函数抢夺其他的对象资源,那么被抢夺的对象会发生什么事情
    A s("aaa");
    A a(std::move(s));
    std::cout << s.get_str() << std::endl;

上面的代码s调用普通构造函数构造自己,然后a用move语义把s这个左值强制变成右值调用移动构造函数,那么s自己已经失效了,再去调用s获取数据的指针,肯定会发生错误的,因为原有指针在构造函数中已经被置空了。
在这里插入图片描述

4.区分浅拷贝、深拷贝、移动语义

浅拷贝:只拷贝指针
注意:可能会造成重复析构一个资源
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oIJTVWLv-1646488484366)(./1646486874179.png)]

深拷贝:拷贝内存,新的指针指向新的内存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JBzC4xlC-1646488484366)(./1646486770949.png)]

移动语义:抢夺内存资源为己用
注意:原来的指针没作用了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HgbCQsqw-1646488484367)(./1646486867041.png)]

5.完美转发 std::forward

  • 定义: 在函数模板中,完全依照模板的参数类型(保留参数的左右值属性),将参数传递给函数模板中调用的另外一个函数。
template<typename T>
void func(T & val)
{
	print(val);
}

比如上述的函数模板,传入1,按照func(1);调用 1是右值,但是在进入func内部以后1变成了个左值val,就会变成调用print(val);val是个左值
如果我想在调用print函数的时候,是调用的print(1)而不是print(val)那就可以使用std::forward函数进行完美转发。
就可以这样写:

template<typename T>
// 如果写T&,就不支持传入右值,而写T&&,既能支持左值,又能支持右值
void func(T&& val)
{
    print(std::forward<T>(val));
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值