C++11 右值引用

概念

本文主要讲述右值引用相关概念,讲到右值,当然是相对于左值而言。简单理解,能够取地址的 “对象” 为左值,相反则为右值,这里的 “对象” 包含变量、立即数、函数返回值、类型等一切我们想要它进行操作的 target。

左值引用

typename T;
T a; //a 即是一个左值,对它的引用就是左值引用:
T& p = a;

右值引用

右值可以细分为纯右值和将亡值()。
如前所述,右值对象不能取地址,如:
int b = 1; //b是左值,1是纯右值
class A;
A c = A(); //c 是左值,A()是右值(将亡值)。 该语句会报错,后续解释原因。
对右值的引用,即为右值引用:
int&& b = 1; //这里b是对右值 1 的引用
A&& c = A(); //这里c是对一个右值的引用

常量左值引用

一个左值引用只可以绑定左值,如:int& b = 1; 是非法的。
一个右值引用只可以绑定右值:int a = 1; int&& d = a; 是非法的。
常量左值引用:const value_type&, 比较特殊,它可以绑定左值或右值。

引用是变量的别名,右值没有地址,其内容不能被修改,因此左值引用不能指向右值。

但 const 左值引用是可以指向右值的,因为const 限制了该引用所指对象不能被修改,这也是为什么要使用const &作为函数参数的原因之一,如std::vector 的 push_back:

void push_back(const value_type& val);

push_back 的参数既可以是左值,也可以是一个右值。

通过右值引用减少拷贝次数

std::move(左值)

调用 std::move(左值) 返回该左值对象为右值

class A;
A a;
A&& b = std::move(a);  

移动函数 std::move 将对象 a 转化为右值,其所对应的内存块的所有权转移给了一个右值引用 b,这过程中并没有调用拷贝构造函数。

这里,声明一个测试类 test.h:

#include <iostream>
class test
{
private:
    static int count;
public:
    test();
    test(const test &c);
    void print();
    ~test();
};

类定义 test.cpp

#include "test.h"
test::test()
{
    ++count;
    std::cout << "construct. " << count <<" address:"<< this << std::endl;
}
test::test(const test &c)
{
    ++count;
    std::cout << "copy constructor." << count <<" address:"<< this << std::endl;
}
test::~test()
{
    --count;
    std::cout << "destruct." << count <<" address:"<< this <<std::endl;
}
void test::print()
{
    std::cout << "this is print() in class test." << std::endl;
}
int test::count = 0;

主函数: main.cpp

#include "test.h"
int main()
{
	test a;
	test b = a;
	test&& c = std::move(a);
}

输出结果:
construct. 1 address:0x7ffcef925bb7
address a = 0x7ffcef925bb7
copy constructor.2 address:0x7ffcef925bb6
address b = 0x7ffcef925bb6
address c = 0x7ffcef925bb7
destruct.1 address:0x7ffcef925bb6
destruct.0 address:0x7ffcef925bb7
可以看出,拷贝构造函数只在给 b赋值时被调用一次,右值引用 c 只是接管了a 的所有权。

进一步,添加函数:

test print()
{
    cout << "static void print()." << endl;
    test T;
    cout << "exit static void print()."<< endl;
    return T;
}

修改main.cpp

#include <iostream>
#include "test.cpp"
using namespace std;
static test print();
int main()
{
    cout << "hello,world." << endl;
    test a;
    test b = a;
    b.print();
    test c = print();
    return 0;
}

输出:
hello,world.
construct. 1 address:0x7fff20c8efce
copy constructor.2 address:0x7fff20c8efcd
this is print() in class test.
static void print().
construct. 3 address:0x7fff20c8ef9f
exit static void print().
copy constructor.4 address:0x7fff20c8efcf
destruct.3 address:0x7fff20c8ef9f
copy constructor.4 address:0x7fff20c8efcc
destruct.3 address:0x7fff20c8efcf
destruct.2 address:0x7fff20c8efcc
destruct.1 address:0x7fff20c8efcd
destruct.0 address:0x7fff20c8efce

分析:
如前述所分析test b = a; 调用了拷贝构造函数;
static test print() 函数在返回 对象 T 时一共发生了两次拷贝:
1)通过拷贝构造函数,创建临时对象 tmp,析构T;
2)通过拷贝构造函数,创建对象 c, 析构临时对象 tmp;

修改 static test print()为 static test&& print(), 同时修改main.cpp

test&& print()
{
    cout << "static void print()." << endl;
    test T;
    cout << "exit static void print()."<< endl;
    return move(T);
}

static test&& print();
int main()
{
    cout << "hello,world." << endl;
    test a;
    test b = a;
    b.print();
    test&& c = print();
    return 0;
}

test&& print()
{
    cout << "static void print()." << endl;
    test T;
    cout << "exit static void print()."<< endl;
    return move(T);

输出:
hello,world.
construct. 1 address:0x7fffcf8dbb17
copy constructor.2 address:0x7fffcf8dbb16
this is print() in class test.
static void print().
construct. 3 address:0x7fffcf8dbaef
exit static void print().
destruct.2 address:0x7fffcf8dbaef
destruct.1 address:0x7fffcf8dbb16
destruct.0 address:0x7fffcf8dbb17

由于 std::move()将 T 转化为右值,其所有权被直接赋予右值引用 c,省去了中间的两次拷贝。
注意:
1)此时如果将 main.cpp中的 c 声明为一个左值,将增加一次拷贝,效果等同于:

test c = test();

2)Linux中采用g++编译器编译,为避免编译器自动优化,编译指令需添加参数:
-fno-elide-constructors

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值