文章目录
一、左值/右值?
1.1 定义
左值与右值(lvalue/rvalue)这两概念是从 c 中传承而来的。
在 c 中,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式)。
右值指的则是只能出现在等号右边的变量(或表达式)。
右值不能当成左值使用,但左值可以当成右值使用。
C++中其实是左值表达式与右值表达式。其中:表达式是有值的,值是有类型的,值是动态的,类型是静态的。例如:
int &a = b; a 是一个左值,他的类型是左值引用,指向一个左值。
int &&c = fun(); c是一个左值,他的类型是右值引用,指向一个右值。
如果一个函数的返回类型是左值引用,那么调用这个函数得到的返回值将是一个 lvalue[N3690,3.10.1],之所以特别地说这个事,是因为如果一个函数的返回值不是引用类型,那调用这个函数得到的结果将是一个临时变量,是个右值,而且是纯右值(prvalue)
1.2 右值能修改吗?
下图运算操作所产生的中间结果是右值;
但是string类型却是可以的。这是C++相较C独有的。因为string 不是基础类型而是一个类,其实只是调用了成员函数”operator=“。
定义如下:
-
对于基础类型,右值是不可被修改的,也不可被 const, volatile 所修饰
-
对于自定义的类型,右值却允许通过它的成员函数进行修改。
其实就是:右值调用成员函数是被允许的,但成员函数有可能不是 const 类型,因此通过调用右值的成员函数,也就可能会修改了该右值。不知道是不是设计初衷。//疑问点
二、左值引用/右值引用?
2.1引入右值引用、常量引用的用法;
引入右值引用之前:引用只能指向左值,常量引用可以指向右值和左值,这就导致没有专门为右值做的引用。即下图对比:(右值可以被const 的引用指向,而不可以被引用指向。)
这样就可以用一个基类的引用指向一个子类的临时变量,然后通过这个引用来实现多态,但又不用处理子类的销毁。
class Base
{
public: virtual void print() const{ cout << "base print()" << endl; }
};
class DerOne: public Base
{
public: virtual void print() const { cout << "DerOne print()" << endl; }
};
class DerTwo: public Base
{
public: virtual void print() const { cout << "DerTwo print()" << endl; }
};
Base GetBase()
{
return Base();
}
DerOne GetDerOne()
{
return DerOne();
}
DerTwo GetDerTwo()
{
return DerTwo();
}
int main()
{
const Base& ref1 = GetBase();
const Base& ref2 = GetDerOne();
const Base& ref3 = GetDerTwo();
ref1.print();
ref2.print();
ref3.print();
return 0;
}
实现效果如下:
注意:const 对象只能调用const 成员函数。
自从C++ 11 引入右值引用以后,以前的引用就称之为左值引用。
int &a = b; a 是一个左值,他的类型是左值引用,指向一个左值。
int &&c = fun(); c是一个左值,他的类型是右值引用,指向一个右值。
2.2 引入右值引用的意义/目的
为了解决一系列的问题C++ 11引入了右值引用,用&&表示。从而引入了move(), forward() 等。
目的有两个:
一个是为自定义类型实现 move 语义;一个是配合 forwarding reference 来实现完美转发。
移动语义可以减少无谓的内存拷贝,要想实现移动语义,需要实现移动构造函数和移动赋值函数。
三、move/forward?
- std::move()将一个左值转换成一个右值,强制使用移动拷贝和赋值函数,这个函数本身并没有对这个左值什么特殊操作。
- std::forward()和universal references通用引用共同实现完美转发。
- 用empalce_back()替换push_back()增加性能。
- std::move() 的主要作用是将一个左值转为 xvalue, 它的实现,本质上就是一个 static_cast<>。
- 而 std::forward() 则是用来配合 forwarding reference 实现完美转发,它主要的作用是将一个类型为引用(左值引用或右值引用)的左值(引用这个变量本身),转化为它的类型所对应的值类型
3.1、move 语义的使用,实现资源让渡
- move 语义的使用。实现了资源让渡的过程
std::string foo = "foo-string";
std::string bar = "bar-string";
std::vector<std::string> myvector;
myvector.push_back(foo); // copies
myvector.push_back(std::move(bar)); // moves
std::cout << "myvector contains:";
for (std::string& x : myvector) std::cout << ' ' << x;
std::cout << '\n';
cout << foo << endl;
cout << bar << endl;
- 实现结果:发现
bar
中的便来给你已经被让渡到vector
中去了
3.2、move 语义配合 移动构造 与 移动赋值运算符
-
move 兼顾了深拷贝与浅拷贝的优点;
-
带move 的是调用了移动构造、移动赋值运算符
String s1("Hello"); // 构造函数
cout << s1 << endl;
String s2 = s1; // 调用拷贝构造函数
String s2(s1); // 调用拷贝构造函数
cout << s2 << endl;
String s2A(std::move(s1)); // 移动构造函数
cout << s2A << endl;
String s3; // 无参构造函数
cout << s3 << endl;
s3 = s2; // 调用赋值函数
cout << s3 << endl;
String s3A; // 无参构造函数
s3A = std::move(s2A); // 移动赋值运算符
cout << s3A << endl;
- 移动构造和移动赋值运算符 的实现:
- delete 自己的堆空间;(构造的话,本身不存在数据,少了一步delete)
- 将指针指向需要的空间中的地址块;
- 将对方的指针指向NULL;
// 移动构造函数
String::String(String&& other)
{
if (other.m_data != NULL)
{
// 资源让渡
m_data = other.m_data;
other.m_data = NULL;
}
}
// 移动赋值运算符
String& String::operator=(String&& rhs)noexcept
{
if(this != &rhs)
{
delete[] m_data;
m_data = rhs.m_data;
rhs.m_data = NULL;
}
return *this;
}
3.3、move 语义配合unique_ptr 智能指针
- 简单的过继内存空间;实现资源让渡;
int main() {
// foo bar p
// --- --- ---
std::unique_ptr<int> foo; // null
std::unique_ptr<int> bar; // null null
int* p = nullptr; // null null null
foo = std::unique_ptr<int>(new int(10)); // (10) null null
bar = std::move(foo); // null (10) null
p = bar.get(); // null (10) (10)
*p = 20; // null (20) (20)
p = nullptr; // null (20) null
foo = std::unique_ptr<int>(new int(30)); // (30) (20) null
p = foo.release(); // null (20) (30)
*p = 40; // null (20) (40)
std::cout << "foo: ";
if (foo) std::cout << *foo << '\n'; else std::cout << "(null)\n";
std::cout << "bar: ";
if (bar) std::cout << *bar << '\n'; else std::cout << "(null)\n";
std::cout << "p: ";
if (p) std::cout << *p << '\n'; else std::cout << "(null)\n";
std::cout << '\n';
delete p; // the program is now responsible of deleting the object pointed to by p
// bar deletes its managed object automatically
return 0;
}
- 简单的切换作用域:
unique_ptr<string> p0;
{
unique_ptr<string> p1(new string());
p0 = std::move(p1);
}