C++中的左值,右值和移动语义详解
2023-12-26 05:09:56
左值,右值与移动语义
- 左值表示了一个占据内存中某个可识别位置的对象。右值是无法寻址的临时对象或表达式的值。左值可以隐式地转换为右值,而右值不可。右值可以显式地赋值给左值。
- 赋值运算符、取地址符、内置解引用运算符、下标运算符、内置类型和迭代器的递增递减运算符,都需要左值。
- 右值引用与移动语义是C++11中引入的两个重要概念,它们旨在提高代码的性能,尤其是在涉及到资源管理和对象转移的场景中。
右值引用:
右值引用是对临时对象(即右值)的引用,它允许开发者安全地获取临时对象的资源。右值引用使用两个&&符号表示,例如int&&。它与左值引用(使用单个&符号,例如int&)不同,左值引用只能绑定到持久性对象上。右值引用使得编译器能够区分哪些对象是临时的,从而可以安全地转移其资源。
移动语义:
移动语义允许对象的资源(如动态分配的内存)被转移而非复制。这通常通过实现移动构造函数和移动赋值运算符来实现。当一个对象被移动时,它的资源被转移到新对象中,原对象则处于一个有效但不确定的状态,通常被设置为一个不再持有资源的状态。这意味着移动操作通常比复制操作更快,因为它避免了不必要的资源复制。以下例子来自于:
#include <iostream>
#include <utility> // for std::swap
using std::cout;
class Intvec
{
public:
explicit Intvec(size_t num = 0)
: m_size(num), m_data(new int[m_size])
{
log("constructor");
}
~Intvec()
{
log("destructor");
delete[] m_data;
}
Intvec(const Intvec& other)
: m_size(other.m_size), m_data(new int[m_size])
{
log("copy constructor");
for (size_t i = 0; i < m_size; ++i)
m_data[i] = other.m_data[i];
}
Intvec(Intvec&& other)
: m_size(0), m_data(nullptr)
{
log("move constructor");
// swap the members of this class with the members of other
std::swap(m_size, other.m_size);
std::swap(m_data, other.m_data);
}
Intvec& operator=(const Intvec& other)
{
log("copy assignment operator");
Intvec tmp(other);
std::swap(m_size, tmp.m_size);
std::swap(m_data, tmp.m_data);
return *this;
}
Intvec& operator=(Intvec&& other)
{
log("move assignment operator");
std::swap(m_size, other.m_size);
std::swap(m_data, other.m_data);
return *this;
}
private:
void log(const char* msg)
{
cout << "[" << this << "] " << msg << "\n";
}
size_t m_size;
int* m_data;
};
int main(){
Intvec v1(20);
Intvec v2;
cout << "assigning lvalue...\n";
v2 = v1;
cout << "ended assigning lvalue...\n";
cout << "assigning rvalue...\n";
v2 = Intvec(33);
cout << "ended assigning rvalue...\n";
return 0;
}
运行结果:
[0x61ff00] constructor
[0x61fef8] constructor
assigning lvalue...
[0x61fef8] copy assignment operator
[0x61fec8] copy constructor
[0x61fec8] destructor
ended assigning lvalue...
assigning rvalue...
[0x61ff08] constructor
[0x61fef8] move assignment operator
[0x61ff08] destructor
ended assigning rvalue...
[0x61fef8] destructor
[0x61ff00] destructor
在main函数中,首先创建了一个Intvec对象v1,然后创建了另一个空的Intvec对象v2。接下来,执行了两次赋值操作:
- v2 = v1;:这是一个左值赋值,因为v1是一个左值。因此,调用了拷贝赋值运算符,创建了v1的一个副本给v2。
- v2 = Intvec(33);是一个右值赋值,因为Intvec(33)是一个临时对象,即一个右值。如果实现了移动赋值运算符,编译器会选择这个运算符来进行赋值,从而转移临时对象的资源给v2。如果没有实现移动赋值运算符,编译器会回退到使用拷贝赋值运算符,且v2
=Intvec(33);的运行结果如下:
assigning rvalue...
[0x61ff08] constructor
[0x61fef8] copy assignment operator
[0x61fec8] copy constructor
[0x61fec8] destructor
[0x61ff08] destructor
ended assigning rvalue...
在第二次赋值操作中,如果实现了移动赋值运算符,临时对象的资源会被转移给v2,这通常比拷贝快得多,因为它避免了资源的复制。如果没有实现移动赋值运算符,则会调用拷贝赋值运算符,进行资源的复制。
同时可见,当同时存在拷贝赋值运算符(operator=)和移动赋值运算符(operator=)时,C++编译器会根据赋值操作的右侧操作数(即要赋值的对象)是左值还是右值来自动选择合适的赋值运算符。
文章来源:https://blog.csdn.net/qq_46348508/article/details/135211343
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!