文章目录
- 什么是右值?
- 是什么是右值引用?
- 什么是移动语义?
- 什么是完美转发?(右值引用+std::forward)
什么是右值?
在 C++ 中,表达式可以分为左值表达式和右值表达式。左值表达式指的是可以出现在赋值语句左边的表达式,例如变量、数组元素、结构体成员等;右值表达式指的是不能出现在赋值语句左边的表达式,例如常量、临时对象、函数返回值等。
右值是指将要被销毁的临时对象或者没有名字的临时对象。例如,一个返回临时对象的函数调用表达式、一个匿名对象、一个类型转换表达式等都是右值表达式,它们都是将要被销毁的临时对象或者没有名字的临时对象。
右值的特点是它们没有持久的身份,不能被取地址,不能被修改,只能被使用一次。因此,右值引用的主要作用是支持移动语义和完美转发,从而提高程序的效率。
在 C++11 中,引入了右值引用和移动语义的概念,使得程序可以更好地利用右值,提高程序的效率。
下面是一些右值的例子:
int a = 1; // a 是左值,1 是右值
int b = a + 2; // a + 2 是右值,b 是左值
int* p = &a; // &a 是右值,p 是左值
int c = func(); // func() 是右值,c 是左值
int d = std::move(a); // std::move(a) 是右值,d 是左值
在上面的例子中,1、2、&a、func()、std::move(a) 都是右值,它们都是将要被销毁的临时对象或者没有名字的临时对象。左值 a、b、p、c、d 都是可以被取地址、可以被修改、有持久的身份的对象,它们都是左值。
需要注意的是,一个对象既可以是左值,也可以是右值,这取决于它在表达式中的位置。例如,在赋值语句左边的对象是左值,在赋值语句右边的对象是右值。
是什么是右值引用?
右值引用是 C++11 引入的新特性,用于支持移动语义和完美转发。右值引用的语法是在类型名后面加上两个引用符号 &&
,例如 int&&
表示对一个右值 int 对象的引用。
右值引用的主要作用是支持移动语义,即将一个对象的资源(内存、指针等)移动到另一个对象中,从而避免了不必要的内存拷贝和资源分配。移动语义可以提高程序的效率,特别是当对象较大时,避免了不必要的内存拷贝和资源分配,从而提高了程序的性能。
右值引用还可以用于完美转发,即将一个函数的参数以原样转发给另一个函数,从而避免了不必要的拷贝和转换。完美转发可以提高程序的效率,特别是当函数参数较大或者类型较复杂时,避免了不必要的拷贝和转换,从而提高了程序的性能。
需要注意的是,右值引用只能绑定到一个右值对象,不能绑定到一个左值对象。如果尝试将一个左值对象绑定到一个右值引用上,编译器会报错。
下面是一个右值引用的例子:
#include <iostream>
#include <string>
void print(std::string&& str) {
std::cout << str << std::endl;
}
int main() {
std::string s = "Hello, world!";
print(std::move(s)); // 将左值 s 转换为右值引用
return 0;
}
在上面的例子中,print
函数的参数是一个右值引用 std::string&&
,表示对一个右值 std::string
对象的引用。在 main
函数中,我们定义了一个左值 std::string
对象 s
,然后将它转换为右值引用 std::move(s)
,并将它作为参数传递给 print
函数。
由于 std::move(s)
返回的是一个右值引用,因此可以绑定到 print
函数的参数上。在 print
函数中,我们可以使用 str
来访问传递进来的字符串,而不需要进行不必要的拷贝和转换。
需要注意的是,由于 std::move
只是将一个左值对象转换为右值引用,它并不会移动对象的资源。如果需要移动对象的资源,需要在移动构造函数或移动赋值运算符中使用右值引用。
注意:上面这个例子只是展示右值引用,用普通引用也能达到同样效果,具体右值引用的特殊效用,我们需要看下面的移动语义和完美转发。
什么是移动语义?
移动语义是 C++11 引入的一个新特性,它可以将对象的资源(比如内存、文件句柄等)从一个对象转移到另一个对象,避免了不必要的复制和销毁操作,提高了程序的性能。
在移动语义中,右值引用扮演了重要的角色。只有右值引用才能绑定到临时对象或将要销毁的对象,从而实现资源的转移。如果使用左值引用代替右值引用,就无法实现移动语义的效果。
下面是一个使用右值引用实现移动语义的例子:
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
class MyString
{
public:
MyString() : m_data(nullptr), m_size(0) {}
MyString(const char *str) : m_data(new char[strlen(str) + 1]), m_size(strlen(str))
{
strcpy(m_data, str);
}
MyString(MyString &&other) : m_data(other.m_data), m_size(other.m_size)
{
other.m_data = nullptr;
other.m_size = 0;
}
~MyString()
{
delete[] m_data;
}
void print() const
{
std::cout << m_data << std::endl;
}
private:
char *m_data;
size_t m_size;
};
int main()
{
MyString s1("Hello, world!");
s1.print(); // 输出 "Hello, world!"
cout << "------------------" << endl;
MyString s2(std::move(s1)); // 将 s1 转移为右值引用
s1.print(); // 输出空字符串
cout << "------------------" << endl;
s2.print(); // 输出 "Hello, world!"
cout << "------------------" << endl;
return 0;
}
在上面的代码中,我们定义了一个 MyString 类,它包含一个字符数组和一个大小成员变量。在类的构造函数中,我们使用 new 运算符为字符数组分配内存,并将字符串复制到数组中。在移动构造函数中,我们将其他对象的指针和大小成员变量移动到当前对象中,并将其他对象的指针和大小成员变量设置为 null 和 0。这样,我们就实现了将一个 MyString 对象的资源转移到另一个对象的功能。在 main 函数中,我们创建了两个 MyString 对象 s1 和 s2,然后将 s1 转移为右值引用,从而实现了移动语义的效果。
上面代码还有点bug,我已经上知乎问了。。。
什么是完美转发?(右值引用+std::forward)
C++右值引用完美转发是一种技术,用于在函数调用中将参数以原样传递给另一个函数,同时保持参数的值类别(左值或右值)。这种技术可以提高代码的效率和可读性。
在C++11中,引入了右值引用和std::forward函数,使得完美转发成为可能。右值引用是一种新的引用类型,可以绑定到右值(临时对象或表达式的结果),而左值引用只能绑定到左值(具有持久性的对象)。std::forward函数是一个模板函数,用于将参数以原样转发给另一个函数。
使用右值引用完美转发可以避免不必要的对象拷贝和移动,提高代码的效率。同时,它还可以保持参数的值类别,避免了一些潜在的问题,例如在函数模板中传递参数时,如果不使用完美转发,可能会导致参数的值类别发生改变,从而影响函数的行为。
下面是一个使用右值引用完美转发的示例:
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
void bar(int &x)
{
std::cout << "lvalue: " << x << std::endl;
}
void bar(int &&x)
{
std::cout << "rvalue: " << x << std::endl;
}
template <typename T, typename... Args>
void foo(Args &&...args)
{
bar(std::forward<Args>(args)...);
}
int main()
{
int x = 1;
foo<int>(x); // lvalue: 1
foo<int>(2); // rvalue: 2
return 0;
}
在这个示例中,函数foo使用了右值引用完美转发,将参数args以原样传递给函数bar。bar有两个重载版本,一个接受左值引用,一个接受右值引用,因此可以根据参数的值类别选择正确的版本。在main函数中,分别调用了foo<int>(x)
和foo<int>(2)
,分别传递了一个左值和一个右值,输出了对应的结果。