C++11中rvalue references的使用

Rvalue references are a feature of C++ that was added with the C++11 standard. The syntax of an rvalue reference is to add && after a type.

为了支持移动操作,C++11引入了一种新的引用类型----右值引用(rvalue reference)。所谓右值引用就是必须绑定到右值的引用。通过&&而不是&来获得右值引用。右值引用有一个重要的性质----只能绑定到一个将要销毁的对象。因此,可以自由地将一个右值引用的资源”移动”到另一个对象中。
一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。
类似任何引用,一个右值引用也不过是某个对象的另一个名字而已。对于常规引用(为了与右值引用区分开来,可以称之为左值引用(lvalue reference)),不能将其绑定到要求转换的表达式、字面常量或是返回右值的表达式。右值引用有着完全相反的绑定特性:可以将一个右值引用绑定到这类表达式上,但不能将一个右值引用直接绑定到一个左值上。
返回左值引用的函数,连同赋值、下标、解引用和前置递增/递减运算符,都是返回左值得表达式的例子。可以将一个左值引用绑定到这类表达式的结果上。
返回非引用类型的函数,连同算术、关系、位以及后置递增/递减运算符,都生成右值。不能将一个左值引用绑定到这类表达式上,但可以将一个const的左值引用或者一个右值引用绑定到这类表达式上。
左值持久,右值短暂:左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。
由于右值引用只能绑定到临时对象,可知:所引用的对象将要被销毁;该对象没有其它用户。这两个特性意味着,使用右值引用的代码可以自由地接管所引用的对象的资源。
变量是左值:变量可以看作只有一个运算对象而没有运算符的表达式。
变量是左值,因此不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不行。
虽然不能就将一个右值引用直接绑定到一个左值上,但可以显示地将一个左值转换为对应的右值引用类型。还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件utility中。move调用告诉编译器:有一个左值,但希望像一个右值一样处理它。在调用move之后,我们不能对移后源对象的值做任何假设。
我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值。

In C++, there are rvalues and lvalues. An lvalue is an expression whose address can be taken,a locator value--essentially, an lvalue provides a (semi)permanent piece of memory. rvalues are not lvalues. An expression is an rvalue if it results in a temporary object.

Every C++ expression is either an lvalue or an rvalue. An lvalue refers to an object that persists beyond a single expression. You can think of an lvalue as an object that has a name. All variables, including nonmodifiable (const) variables, are lvalues. An rvalue is a temporary value that does not persist beyond the expression that uses it.

an "rvalue reference", that will let you bind a mutable reference to an rvalue, but not an lvalue. In other words, rvalue references are perfect for detecting if a value is temporary object or not. Rvalue references use the && syntax instead of just &, and can be const and non-const, just like lvalue references, although you'll rarely see a const rvalue reference.

Rvalue references solve at least two problems: Implementing move semantics; Perfect forwarding.

The original definition of lvalues and rvalues from the earliest days of C is as follows: An lvalue is an expression e that may appear on the left or on the right hand side of an assignment, whereas an rvalue is an expression that can only appear on the right hand side of an assignment.

If X is any type, then X&& is called an rvalue reference to X. For better distinction, the ordinary reference X& is now also called an lvalue reference.

rvalue references enable us to distinguish an lvalue from an rvalue.

In C++11,however, the rvalue reference lets us bind a mutable reference to an rvalue,but not an lvalue. In other words, rvalue references are perfect for detecting whether a value is a temporary object or not.

Important rvalue reference properties:

(1)、For overload resolution, lvalues prefer binding to lvalue references and rvalues prefer binding to rvalue references. Hence why temporaries prefer invoking a move constructor / move assignment operator over a copy constructor / assignment operator.

(2)、rvalue references will implicitly bind to rvalues and to temporaries that are the result of an implicit conversion. i.e. float f = 0f; int&& i = f; is well formed because float is implicitly convertible to int; the reference would be to a temporary that is the result of the conversion.

(3)、Named rvalue references are lvalues. Unnamed rvalue references are rvalues. This is important to understand why the std::move call is necessary in: foo&& r= foo(); foo f = std::move(r).

Rvalue references enable you to distinguish an lvalue from an rvalue. Lvalue references and rvalue references are syntactically and semantically similar,but they follow somewhat different rules.

右值引用是C++11中最重要的新特性之一,它解决了C++中大量的历史遗留问题,使C++标准库的实现在多种场景下消除了不必要的额外开销(如std::vector, std::string),也使得另外一些标准库(如std::unique_ptr, std::function)成为可能。即使你并不直接使用右值引用,也可以通过标准库,间接从这一新特性中受益。

右值引用的意义通常解释为两大作用:移动语义和完美转发。

右值引用可以使我们区分表达式的左值和右值。

右值引用它实现了移动语义(Move Sementics)和完美转发(Perfect Forwarding)。它的主要目的有两个方面:(1)、消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率;(2)、能够更简洁明确地定义泛型函数。

右值引用主要就是解决一个拷贝效率低下的问题,因为针对于右值,或者打算更改的左值,我们可以采用类似与auto_ptr的move(移动)操作,大大的提高性能(move semantics)。另外,C++的模板推断机制为参数T&&做了一个例外规则,让左值和右值的识别和转向(forward)非常简单,帮助我们写出高效并且简捷的泛型代码(perfect forwarding)。

左值的声明符号为”&”, 为了和左值区分,右值的声明符号为”&&”。

下面是从其他文章中copy的测试代码,详细内容介绍可以参考对应的reference:

#include "rvalue_references.hpp"
#include <iostream>
#include <string>
#include <utility>

//
// reference: http://en.cppreference.com/w/cpp/language/reference
void double_string(std::string& s)
{
	s += s; // 's' is the same object as main()'s 'str'
}

char& char_number(std::string& s, std::size_t n)
{
	return s.at(n); // string::at() returns a reference to char
}

int test_lvalue_references1()
{
	// 1. Lvalue references can be used to alias an existing object (optionally with different cv-qualification):
	std::string s = "Ex";
	std::string& r1 = s;
	const std::string& r2 = s;

	r1 += "ample";           // modifies s
	//  r2 += "!";               // error: cannot modify through reference to const
	std::cout << r2 << '\n'; // prints s, which now holds "Example"

	// 2. They can also be used to implement pass-by-reference semantics in function calls:
	std::string str = "Test";
	double_string(str);
	std::cout << str << '\n';

	// 3. When a function's return type is lvalue reference, the function call expression becomes an lvalue expression
	std::string str_ = "Test";
	char_number(str_, 1) = 'a'; // the function call is lvalue, can be assigned to
	std::cout << str_ << '\n';

	return 0;
}

//
// reference: http://en.cppreference.com/w/cpp/language/reference
static void f(int& x)
{
	std::cout << "lvalue reference overload f(" << x << ")\n";
}

static void f(const int& x)
{
	std::cout << "lvalue reference to const overload f(" << x << ")\n";
}

static void f(int&& x)
{
	std::cout << "rvalue reference overload f(" << x << ")\n";
}

int test_rvalue_references1()
{
	// 1. Rvalue references can be used to extend the lifetimes of temporary objects
	// (note, lvalue references to const can extend the lifetimes of temporary objects too, but they are not modifiable through them):
	std::string s1 = "Test";
	//  std::string&& r1 = s1;           // error: can't bind to lvalue

	const std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime
	//  r2 += "Test";                    // error: can't modify through reference to const

	std::string&& r3 = s1 + s1;      // okay: rvalue reference extends lifetime
	r3 += "Test";                    // okay: can modify through reference to non-const
	std::cout << r3 << '\n';

	// 2. More importantly, when a function has both rvalue reference and lvalue reference overloads,
	// the rvalue reference overload binds to rvalues (including both prvalues and xvalues),
	// while the lvalue reference overload binds to lvalues:
	int i = 1;
	const int ci = 2;
	f(i);  // calls f(int&)
	f(ci); // calls f(const int&)
	f(3);  // calls f(int&&)
	// would call f(const int&) if f(int&&) overload wasn't provided
	f(std::move(i)); // calls f(int&&)

	// This allows move constructors, move assignment operators, and other move-aware functions
	// (e.g. vector::push_back() to be automatically selected when suitable.

	return 0;
}

/
// reference: http://www.bogotobogo.com/cplusplus/C11/5_C11_Move_Semantics_Rvalue_Reference.php
static void printReference(int& value)
{
	std::cout << "lvalue: value = " << value << std::endl;
}

static void printReference(int&& value)
{
	std::cout << "rvalue: value = " << value << std::endl;
}

static int getValue()
{
	int temp_ii = 99;
	return temp_ii;
}

int test_rvalue_references2()
{
	int ii = 11;
	printReference(ii);
	printReference(getValue());  //  printReference(99);
	return 0;
}


// references: https://msdn.microsoft.com/en-us/library/dd293668.aspx
template<typename T> struct S;

// The following structures specialize S by 
// lvalue reference (T&), const lvalue reference (const T&), 
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of 
// the structure and its parameter.
template<typename T> struct S<T&> {
	static void print(T& t)
	{
		std::cout << "print<T&>: " << t << std::endl;
	}
};

template<typename T> struct S<const T&> {
	static void print(const T& t)
	{
		std::cout << "print<const T&>: " << t << std::endl;
	}
};

template<typename T> struct S<T&&> {
	static void print(T&& t)
	{
		std::cout << "print<T&&>: " << t << std::endl;
	}
};

template<typename T> struct S<const T&&> {
	static void print(const T&& t)
	{
		std::cout << "print<const T&&>: " << t << std::endl;
	}
};

// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{
	S<T&&>::print(std::forward<T>(t));
}

// This function returns the constant string "fourth".
const std::string fourth() { return std::string("fourth"); }

int test_rvalue_references3()
{
	// The following call resolves to:
	// print_type_and_value<string&>(string& && t)
	// Which collapses to:
	// print_type_and_value<string&>(string& t)
	std::string s1("first");
	print_type_and_value(s1);

	// The following call resolves to:
	// print_type_and_value<const string&>(const string& && t)
	// Which collapses to:
	// print_type_and_value<const string&>(const string& t)
	const std::string s2("second");
	print_type_and_value(s2);

	// The following call resolves to:
	// print_type_and_value<string&&>(string&& t)
	print_type_and_value(std::string("third"));

	// The following call resolves to:
	// print_type_and_value<const string&&>(const string&& t)
	print_type_and_value(fourth());

	return 0;
}

GitHubhttps://github.com/fengbingchun/Messy_Test

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
右值引用(rvalue references)是C++11引入的一种新的引用类型,用于表示一个临时对象或将要被销毁的对象。与左值引用(lvalue references)不同,右值引用只能绑定到右值(包括临时对象和将要被销毁的对象),不能绑定到左值(即不能绑定到具有名称的对象)。 右值引用使用&&符号表示,语法格式如下: ```c++ T&& var; ``` 其,`T`表示要引用的类型,`var`表示引用的变量名。 右值引用可以用于实现移动语义(move semantics),通过将一个对象的资源(如内存、文件句柄等)从源对象转移到目标对象,避免了不必要的复制和内存分配,提高了程序的效率。 例如,我们可以定义一个带有移动构造函数的类`MyString`: ```c++ #include <iostream> #include <cstring> class MyString { public: // 默认构造函数 MyString() : m_data(nullptr), m_length(0) {} // 带参构造函数 MyString(const char* str) : m_data(nullptr), m_length(0) { if (str == nullptr) { return; } m_length = strlen(str); m_data = new char[m_length + 1]; strncpy(m_data, str, m_length); m_data[m_length] = '\0'; } // 移动构造函数 MyString(MyString&& other) : m_data(other.m_data), m_length(other.m_length) { other.m_data = nullptr; other.m_length = 0; } // 析构函数 ~MyString() { if (m_data != nullptr) { delete[] m_data; m_data = nullptr; } } // 输出字符串 void print() const { if (m_data != nullptr) { std::cout << m_data; } else { std::cout << "null"; } } private: char* m_data; // 字符串数据 int m_length; // 字符串长度 }; ``` 在该类,我们定义了一个移动构造函数`MyString(MyString&& other)`,该函数接受一个右值引用作为参数,将源对象的资源转移给目标对象,并将源对象的指针设置为`nullptr`,以防止资源被重复释放。 然后,我们可以使用如下方式创建一个临时对象并将其转移给另一个对象: ```c++ int main() { MyString str1("hello"); // 调用带参构造函数 MyString str2(std::move(str1)); // 调用移动构造函数 str1.print(); // 输出null str2.print(); // 输出hello return 0; } ``` 在上面的代码,我们首先创建了一个`MyString`对象`str1`,然后将其移动给另一个对象`str2`,最后输出两个对象的值。可以看到,源对象`str1`的值变成了`null`,目标对象`str2`的值变成了`hello`,说明移动构造函数成功地将源对象的资源转移给了目标对象。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值