cen's blog cen's blog
首页
  • 编程文章

    • markdown使用
  • 学习笔记

    • C++学习
    • C++数据结构
    • MySQL
    • Linux
    • 网络编程
算法
  • Git
  • ProtoBuf
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

cen

十年饮冰,难凉热血
首页
  • 编程文章

    • markdown使用
  • 学习笔记

    • C++学习
    • C++数据结构
    • MySQL
    • Linux
    • 网络编程
算法
  • Git
  • ProtoBuf
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • 类和对象
  • 内存管理
  • 泛型模板
  • string
  • vector
  • list
  • stack和queue
  • priority_queue
  • 继承
  • 多态
  • set和map
  • bitset
  • C++11
  • 异常
  • 智能指针
  • 特殊类设计
  • function和bind
  • 右值引用和移动语义
    • 左值与右值
    • 右值引用的产生
    • 引用折叠 & 完美转发
  • thread
  • C++学习笔记
cen
2025-08-15
目录

右值引用和移动语义

# 左值与右值

左值引用主要引用左值,左值通常可以修改,可以取地址,比如变量。

右值引用主要引用右值,右值通常不可以修改,比如常量、函数的返回值及临时变量(通过隐式类型转换得到的对象等...)等,C++11 还将右值区分为纯右值和将亡值。无论左值引用还是右值引用,本质都是给对象取别名。

  • 纯右值:基本类型的常量或临时对象,比如:a+b, 100
  • 将亡值:自定义类型的临时对象,比如:表达式的中间结果、函数按照值的方式进行返回

左值引用不能直接引用右值,但是const + 左值引用可以引用右值;右值引用不能直接引用左值,但是可以引用move后的左值,move 就是返回传入的实参的右值引用类型,做了一个类型强转。

    // 左值引用
    int a = 100;
    int& b = a;
    // 右值引用
    int&& c = 100;

    const int& d = 100;
    int&& e = move(a);
    return 0;
1
2
3
4
5
6
7
8
9

# 右值引用的产生

  • 左值引用的使用场景:
  1. 左值引用做参数,防止传参时进行拷贝操作
  2. 左值引用做返回值,防止返回时对返回对象进行拷贝操作

用左值引用做返回值,并不能完全避免函数返回对象时不必要的拷贝操作。如果函数返回的对象是一个局部变量,该变量出了函数作用域就被销毁了,这种情况下不能用左值引用作为返回值,只能以传值的方式返回,这就是左值引用的短板。

例如:

class Stack
{
public:
	// size表示栈初始的内存大小
	Stack(int size = 1000)
		:msize(size), mtop(0)
	{
		cout << "Stack(int)" << endl;
		mpstack = new int[size];
	}
	// 栈的析构函数
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[]mpstack;
		mpstack = nullptr;
	}
	// 栈的拷贝构造函数,防止默认拷贝构造浅拷贝,程序出错
	Stack(const Stack &src)
		:msize(src.msize), mtop(src.mtop)
	{
		cout << "Stack(const Stack&)" << endl;
		mpstack = new int[src.msize];
		memcpy(mpstack, src.mpstack, sizeof(int)*mtop);
	}
	// 栈的赋值重载函数
	Stack& operator=(const Stack &src)
	{
		cout << "operator=" << endl;
		if (this == &src)
			return *this;

		delete[]mpstack;

		msize = src.msize;
		mtop = src.mtop;
		mpstack = new int[src.msize];
		memcpy(mpstack, src.mpstack, sizeof(int)*mtop);
		return *this;
	}
	// 返回栈的长度
	int getSize()const { return msize; }
private:
	int *mpstack;
	int mtop;
	int msize;
};
Stack GetStack(Stack &stack)
{
	// 这里构造新的局部对象tmp
	Stack tmp(stack.getSize());
	/*
	因为tmp是函数的局部对象,不能出函数作用域,
	所以这里tmp需要拷贝构造生成在main函数栈帧上
	的临时对象,因此这里会调用拷贝构造函数,完成
	后进行tmp局部对象的析构操作
	*/
	return tmp;
}
int main()
{
	Stack s;
	/*
	GetStack返回的临时对象给s赋值,该语句结束,临时对象
	析构,所以此处调用operator=赋值重载函数,然后调用
	析构函数
	*/
	s = GetStack(s);
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

提升效率:

  1. 为什么不能把 tmp 持有的内存资源直接给临时对象呢?非得给临时对象重新开辟内存拷贝一份数据,然后 tmp 的资源又没有什么用处,而且马上就要析构,这样只能造成代码运行效率低下
  2. 临时量对象给 s 赋值完成后,马上就析构了,为什么不能把临时对象的资源直接给 s 呢?如果这样做的话,效率就很高了,省了内存的开辟和大量数据的拷贝时间了

在这里,C++11 中的解决方式是提供带右值引用参数的拷贝构造函数和 operator=赋值重载函数:

// 带右值引用参数的拷贝构造函数
Stack(Stack &&src)
	:msize(src.msize), mtop(src.mtop)
{
	cout << "Stack(Stack&&)" << endl;
	/*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
	mpstack = src.mpstack;
	src.mpstack = nullptr;
}

// 带右值引用参数的赋值重载函数
Stack& operator=(Stack &&src)
{
	cout << "operator=(Stack&&)" << endl;
	if(this == &src)
	    return *this;

	delete[]mpstack;

	msize = src.msize;
	mtop = src.mtop;
	/*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
	mpstack = src.mpstack;
	src.mpstack = nullptr;
	return *this;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

新的例子:

class String {
public:
    // 构造函数
    String(const char* data = "") {
        length_ = strlen(data);
        data_ = new char[length_ + 1];
        strcpy(data_, data);
        cout << "构造函数" << endl;
    }

    // 拷贝构造函数
    String(const String& e) {
        length_ = e.length_;
        data_ = new char[length_ + 1];
        strcpy(data_, e.data_);
        cout << "拷贝构造函数" << endl;
    }

    // 赋值重载
    String& operator=(const String& e) {
        if (this != &e) {
            delete[] data_;
            length_ = e.length_;
            data_ = new char[length_ + 1];
            strcpy(data_, e.data_);
        }
        cout << "赋值重载" << endl;
        return *this;
    }

    // 移动构造函数
    String(String&& e) noexcept {
        data_ = e.data_;
        length_ = e.length_;
        e.data_ = nullptr;
        e.length_ = 0;
        cout << "移动构造函数" << endl;
    }

    // 移动赋值
    String& operator=(String&& e) noexcept {
        if (this != &e) {
            delete[] data_;
            data_ = e.data_;
            length_ = e.length_;
            e.data_ = nullptr;
            e.length_ = 0;
            cout << "移动赋值" << endl;
        }
        return *this;
    }

    char operator[](int pos) {
        assert(pos < length_ && pos >= 0);
        return data_[pos];
    }

    // 析构函数
    ~String() {
        if (data_) {
            delete[] data_;
            data_ = nullptr;
        }
        length_ = -1;
        cout << "析构函数" << endl;
    }

private:
    char* data_;
    int length_;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 引用折叠 & 完美转发

引用折叠,就是 int && + &&折叠成 int&&,除此之外,都折叠成 int&:

template <typename T>
void func(T&& val) {
    cout << "01 val:" << val << endl;
    T tmp = val;
    tmp++;
    cout << "02 val:" << val << " tmp:" << tmp << endl;
}
int main() {
    int a = 10;
    int& b = a;
    int&& c = 10;

    cout << "func(10):" << endl;
    func(10);  // 10是右值,引用类型是int&&,T&&推导过程是int&&+&&折叠成int&&,所以T是int,下同
    cout << "func(a):" << endl;
    func(a);  // a是左值,不可能用右值引用来引用,所以func推导T为int&,那么T&&->int&+&&折叠成int&
    cout << "func(std::move(a)):" << endl;
    func(std::move(a));  // std::move(a)是把a转成右值类型,右值引用类型是int&&,所以func推导T为int
    cout << "func(b):" << endl;
    func(b);  // b是左值,不可能用右值引用来引用,所以func推导T为int&,那么T&&->int&+&&折叠成int&
    cout << "func(c):" << endl;
    func(c);  // c是左值,不可能用右值引用来引用,所以func推导T为int&,那么T&&->int&+&&折叠成int&

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

输出结果:

PS D:\C++_projects\26_3\output> & .\'main.exe'
func(10):
01 val:10
02 val:10 tmp:11
func(a):
01 val:10
02 val:11 tmp:11
func(std::move(a)):
01 val:11
02 val:11 tmp:12
func(b):
01 val:11
02 val:12 tmp:12
func(c):
01 val:10
02 val:11 tmp:11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。

所谓完美:函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理。

// 容器里面元素的类型
class A
{
public:
	A(){}
	// 带左值引用参数的赋值函数
	A& operator=(const A &src)
	{
		cout << "operator=" << endl;
		return *this;
	}
	// 带右值引用参数的赋值函数
	A& operator=(A &&src)
	{
		cout << "operator=(A&&)" << endl;
		return *this;
	}
};
// 容器的类型
template<typename _Ty>
class Vector
{
public:
	// 引用左值的push_back函数
	void push_back(const _Ty &val)
	{
		addBack(val);
	}
	// 引用右值的push_back函数
	void push_back(_Ty &&val)
	{
		// 这里传递val时,要用move转换成右值引用类型,
		// 因为val本身是左值,有名字有地址
		addBack(std::move(val));
	}
private:
	enum { VEC_SIZE = 10 };
	_Ty mvec[VEC_SIZE];
	int mcur;

	template<typename _Ty>
    void addBack(_Ty &&val) {
        // 引用折叠:& + && = &;&& + && = &&
        // 这里val本身永远是左值
        mvec[mcur++] = val;
    }
};

int main()
{
	Vector<A> vec;
	A a;
	vec.push_back(a); // 调用A的左值引用的赋值函数
	vec.push_back(A()); // 理应调用A的右值引用参数的赋值函数,却调用了左值引用的赋值函数
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

使用完美转发:

template<typename _Ty>
void addBack(_Ty &&val) {
    // 引用折叠:& + && = &;&& + && = &&
    // 完美转发:保持其左右值
    mvec[mcur++] = std::forward<_Ty>(val);
}
1
2
3
4
5
6
上次更新: 2026/03/20, 11:19:56
function和bind
thread

← function和bind thread→

最近更新
01
Cmake
11-29
02
thread
11-15
03
动态规划
11-08
更多文章>
Theme by Vdoing | Copyright © 2024-2026 京ICP备2020044002号-3 京公网安备11010502056119号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式