0
点赞
收藏
分享

微信扫一扫

c++左值和右值


文章目录

  • ​​左值lvalue​​
  • ​​右值rvalue​​
  • ​​左值右值的转换​​
  • ​​左值引用​​
  • ​​右值引用​​
  • ​​最佳实践​​
  • ​​移动构造和移动赋值函数加nonexcept​​
  • ​​显式删除拷贝和构造​​
  • ​​隐式删除拷贝和构造​​
  • ​​RAII (Resource Acquisition Is Initialization)​​

​​原文地址​​

左值lvalue

有具体地址,可以通过地址访问的对象。(​​以后可能都不应该说变量了。应该c++中的所有变量都是对象,连int都有构造函数的​​​)
原本所有的左值都可以被赋值(=左边),自从有了const,出现了不可修改的左值。

右值rvalue

不是左值的对象都是右值。
可以认为右值不在内存中具有地址,暂时储存在寄存器中。

左值右值的转换

左值可以转右值:

int a = 1;     // a is an lvalue
int b = 2; // b is an lvalue
int c = a + b; // + needs rvalues, so a and b are converted to rvalues
// and an rvalue is returned

显然右值不可以转左值:因为违背了左值有内存地址的定义,
但是,可以从右值生成左值:使用解引用符​​​*​

int arr[] = {1, 2};
int* p = &arr[0];
*(p + 1) = 10; // OK: p + 1 is an rvalue, but *(p + 1) is an lvalue

取地址符&左值转右值:

int var = 10;
int* bad_addr = &(var + 1); // ERROR: lvalue required as unary '&' operand
int* addr = &var; // OK: var is an lvalue
&var = 40; // ERROR: lvalue required as left operand
// of assignment

左值引用

正常的左值引用比较常见。子函数的形式参数定义为引用。避免拷贝。
如下实现了函数返回一个左引用,因此函数的返回值可以被赋值。

int globalvar = 20;

int& foo()
{
return globalvar;
}

int main()
{
foo() = 10;
return 0;
}

右值引用

c++11出现的特性,用处很多。暂时先只了解用于类中移动赋值函数的用途。
拷贝构造函数:一个构造函数,​​​Dog dog = Dog()​​​调用
拷贝赋值函数:一个运算符重载函数,
移动构造函数:
移动赋值函数:

例如,考虑一个动态“整数向量”的简单实现:

class  Intvec 
{ public :
显式Intvec(size_t num = 0 )
: m_size(num), m_data( new int [m_size])
{
log( "constructor" );
}
~Intvec()
{
日志( “析构函数” );
if (m_data) {
删除[] m_data;
m_data = 0 ;
}
}
INTVEC(const的INTVEC&其他)
:m_size(other.m_size),M_DATA(新INT


[m_size])
{
log( "复制构造函数" );
for (size_t i = 0 ; i < m_size; ++i)
m_data[i] = other.m_data[i];
}

Intvec& operator =( const Intvec& other)
{
log( "复制赋值运算符" );
Intvec tmp(其他);
std::swap(m_size, tmp.m_size);
std::swap(m_data, tmp.m_data); 返回*这个;
}私人:
无效日志(常量字符* MSG)


{
cout << "[" << this << "] " << msg << "\n" ;
}

size_t m_size; int * m_data;
};

因此,我们定义了通常的构造函数、析构函数、复制构造函数和复制赋值运算符[4],所有这些都使用日志函数来让我们知道它们何时被实际调用。

将v1的内容复制到v2 中:


Intvec v1( 20 );
Intvec v2;

cout << "分配左值...\n" ;
v2 = v1;
cout << "结束赋值左值...\n" ;

这打印的是:

Intvec v1( 20 ); 
Intvec v2;

cout << "分配左值...\n" ;
v2 = v1;
cout << "结束赋值左值...\n" ;

分配右值:

cout << "分配右值...\n" ; 
v2 = Intvec( 33 );
cout << "结束分配右值...\n" ;

虽然在这里我只是分配了一个新构造的向量,但这只是一个更一般情况的演示,其中正在构建一些临时右值然后分配给v2(例如,这可能发生在某些返回向量的函数中)。现在打印的是这样的:

分配右值... 
[0x28ff08] 构造函数
[0x28fef8] 复制赋值运算符
[0x28fec8] 复制构造函数
[0x28fec8] 析构函数
[0x28ff08] 析构函数
结束分配右值...

它有一对额外的构造函数/析构函数调用来创建然后销毁临时对象。这是一种耻辱,因为在复制赋值运算符内部,正在创建和销毁另一个临时副本。那是额外的工作,毫无意义。

C++11 为我们提供了右值引用,我们可以用它来实现“移动语义”,特别是“移动赋值运算符” [5]。让我们向Intvec添加另一个operator=,重载另一个=函数:

& operator =(Intvec&& other) 
{
log( "移动赋值运算符" );
std::swap(m_size, other.m_size);
std::swap(m_data, other.m_data); 返回*这个;
}

该&&语法是新的右值引用。为我们提供了对右值的引用,该引用将在调用后销毁。我们可以使用这个事实来“窃取”右值的内部结构!这打印:

... 
[0x28ff08] 构造函数
[0x28fef8] 移动赋值运算符
[0x28ff08] 析构函数
结束分配右值...

这里发生的是我们新的移动赋值运算符被调用,因为一个右值被分配给v2。Intvec(33)创建的临时对象仍然需要​​构造函数和析构函数​​调用,但不再需要赋值运算符内部的另一个临时对象。操作符简单地将右值的内部缓冲区与它自己的缓冲区进行切换,对其进行排列,以便右值的析构函数将释放我们对象自己的不再使用的缓冲区。整洁的。

我将再次提到这个例子只是移动语义和右值引用的冰山一角。正如您可能猜到的那样,这是一个复杂的主题,需要考虑许多特殊情况和问题。我的观点是展示 C++ 中左值和右值之间差异的一个非常有趣的应用。编译器显然知道某个实体何时是右值,并且可以安排在编译时调用正确的构造函数。

最佳实践

移动构造和移动赋值函数加nonexcept

Always declare your moves as noexcept
Failing to do so can make your code slower
Consider: push_back in a vector

显式删除拷贝和构造

explicitly deleted copies and moves
We may not want a type to be copyable / moveable
If so, we can declare fn() = delete

class T {
T(const T&) = delete;
T(T&&) = delete;
T& operator=(const T&) = delete;
T& operator=(T&&) = delete;
};

隐式删除拷贝和构造

Destructor
Copy constructor
Copy assignment
Move assignment
Move constructor
这五个任一定义了,那么所有的都应该定义,

RAII (Resource Acquisition Is Initialization)

c++左值和右值_右值


c++左值和右值_构造函数_02


举报

相关推荐

左值和右值

0 条评论