一、左值和右值
1. 左值
左值就是非临时的,有名字的,可以取地址的变量,可以在多条语句中使用
2. 右值
右值是指临时的对象,它们只在当前的语句中有效,包括函数返回的临时变量,常量值如 1,'a',true,lambda
表达式
二、右值引用
右值引用是 C++ 新标准中引入的新特性 , 主要用于避免使用临时对象构造和赋值新对象时的不必要拷贝,避免了临时对象分配,拷贝,释放的系统开销,提高了效率
移动构造函数接受一个”右值引用”参数,可以理解成临时对象的引用
在移动构造函数中,将新对象里的指针成员指向该临时对象分配的空间,而把临时对象的指针成员设为 nullptr
,这样临时对象析构的时候不会释放掉分配的空间
这种直接使用临时对象已申请的空间,并在其析构函数中取消对空间的释放,这样既能节省空间,又能节省资源申请和释放的时间,相当于复用临时对象申请的空间,延长该空间的生命周期,这正是定义移动语义的目的
三、示例代码
- 不使用右值引用
#include <iostream>
#include <vector>
using namespace std;
class iString {
public:
iString() {
m_data = nullptr;
m_len = 0;
cout << "构造函数: nullptr" << endl;
}
iString(const char* s) {
m_len = strlen(s);
init_data(s);
cout << "构造函数: " << m_data << endl;
}
iString(const iString& str) {
m_len = str.m_len;
init_data(str.m_data);
cout << "拷贝构造函数: " << m_data << endl;
}
iString& operator=(const iString& str) {
if (this != &str) {
this->m_len = str.m_len;
init_data(str.m_data);
}
cout << "等号运算符重载函数: " << m_data << endl;
return *this;
}
~iString() {
if (m_data) {
cout << "析构函数: " << m_data << endl;
free(m_data);
}
}
void printString() {
cout << "打印字符串: " << m_data << endl;
}
private:
void init_data(const char* s) {
m_data = new char[m_len + 1];
memcpy(m_data, s, m_len);
m_data[m_len] = '\0';
}
char* m_data;
size_t m_len;
};
void swapiString(iString& a, iString& b) {
iString temp(std::move(a)); // 调用拷贝构造函数或者移动构造函数
a = std::move(b); // 调用等号运算符重载函数或者移动等号运算符重载函数
b = std::move(temp);
}
int main() {
vector<iString> vec;
iString a;
a = iString("hello");
vec.push_back(iString("world"));
swapiString(a, vec.back());
a.printString();
vec.back().printString();
return 0;
}
/*
output: 不使用右值引用
构造函数: nullptr
构造函数: hello
等号运算符重载函数: hello
析构函数: hello
构造函数: world
拷贝构造函数: world
析构函数: world
拷贝构造函数: hello
等号运算符重载函数: world
等号运算符重载函数: hello
析构函数: hello
打印字符串: world
打印字符串: hello
析构函数: world
析构函数: hello
*/
总共执行了2次拷贝构造,iString("Hello")
和 iString("World")
都是临时对象,临时对象被使用完之后被立即析构,在析构函数中释放掉申请的内存资源
- 使用右值引用
#include <iostream>
#include <vector>
using namespace std;
class iString {
public:
iString() {
m_data = nullptr;
m_len = 0;
cout << "构造函数: nullptr" << endl;
}
iString(const char* s) {
m_len = strlen(s);
init_data(s);
cout << "构造函数: " << m_data << endl;
}
iString(iString&& str) { // 用临时对象进行对象构造,才会触发移动构造
m_len = str.m_len;
m_data = str.m_data;
str.m_len = 0;
str.m_data = nullptr; // 阻止 str 对象析构函数中释放内存
cout << "移动构造函数: " << m_data << endl;
}
iString& operator=(iString&& str) { // 用临时对象进行赋值操作,才会触发移动赋值运算
if (this != &str) {
this->m_len = str.m_len;
this->m_data = str.m_data;
str.m_len = 0;
str.m_data = nullptr; // 阻止 str 对象析构函数中释放内存
}
cout << "移动等号运算符重载函数: " << m_data << endl;
return *this;
}
~iString() {
if (m_data) {
cout << "析构函数: " << m_data << endl;
free(m_data);
}
}
void printString() {
cout << "打印字符串: " << m_data << endl;
}
private:
void init_data(const char* s) {
m_data = new char[m_len + 1];
memcpy(m_data, s, m_len);
m_data[m_len] = '\0';
}
char* m_data;
size_t m_len;
};
void swapiString(iString& a, iString& b) {
iString temp(std::move(a)); // 调用拷贝构造函数或者移动构造函数
a = std::move(b); // 调用等号运算符重载函数或者移动等号运算符重载函数
b = std::move(temp);
}
int main() {
vector<iString> vec;
iString a;
a = iString("hello");
vec.push_back(iString("world"));
swapiString(a, vec.back());
a.printString();
vec.back().printString();
return 0;
}
/*
output: 使用右值引用
构造函数: nullptr
构造函数: hello
移动等号运算符重载函数: hello
构造函数: world
移动构造函数: world
移动构造函数: hello
移动等号运算符重载函数: world
移动等号运算符重载函数: hello
打印字符串: world
打印字符串: hello
析构函数: world
析构函数: hello
*/
使用右值引用能够直接使用临时对象已经申请的资源,并在其析构函数中取消对资源的释放,这样既能节省资源,又能节省资源申请和释放的时间