Rvaluar referencias, mover constructores y mover

Valor izquierdo y valor derecho

Valor izquierdo y juicio de valor derecho:
1) La expresión que se puede ubicar en el lado izquierdo del número de asignación (=) es el valor izquierdo; de lo contrario, la expresión que solo se puede ubicar en el lado derecho del número de asignación es el valor correcto.
2) La expresión que tiene un nombre y la dirección de almacenamiento que se puede obtener es un valor l; de lo contrario, es un valor r.
P.ej:

int i = 10;
10 = i;
错误,10为右值,不能当左值用
int j = 20;
j = i;
i和j都是左值,但是i可以当右值用

  Tome las variables i y j definidas anteriormente como ejemplo, i y j son nombres de variables, y sus direcciones de almacenamiento se pueden obtener mediante & ay & b, por lo que ayb son valores l; por el contrario, los literales 10 y 20 no tienen ninguno El nombre, la dirección de almacenamiento no se puede obtener (la cantidad literal generalmente se almacena en un registro o se almacena junto con el código), por lo que 10 y 20 son valores r.
  Tenga en cuenta que los dos métodos de evaluación anteriores solo se aplican a la mayoría de los escenarios.

Referencia de rvalue

int i = 10;
int &a = i;
int &b = 10;//错误
const int &c = 10;//正确

El & usamos regular representa referencias de lvalue, las referencias de lvalue ordinarias solo pueden recibir lvalues, no rvalues, pero las referencias de lvalue constantes pueden recibir lvalues ​​y rvalues.

int j = 10;
int &&d = 10;
int &&e = j;//错误

Los dos & representan referencias de rvalue. Las referencias de rvalue regulares se pueden usar para recibir rvalues, pero no se pueden usar para recibir lvalues
Inserte la descripción de la imagen aquí
. La figura anterior describe los tipos de datos que pueden recibir las referencias de lvalue y las referencias de rvalue, incluidas las referencias de rvalue no constantes. De uso común para constructor móvil y reenvío perfecto.

Mover constructor

Mira la siguiente escena:

#include <iostream>
using namespace std;
class demo
{
    
    
public:
	demo() :num(new int(0))
	{
    
    
		cout << "construct!" << endl;
	}
	//拷贝构造函数
	demo(const demo &d) :num(new int(*d.num))
	{
    
    
		cout << "copy construct!" << endl;
	}
	~demo()
	{
    
    
		if(num != nullptr)
		{
    
    
			delete num;
			num = nullptr;
		}
		cout << "class destruct!" << endl;
	}
private:
	int *num;
};
demo get_demo()
{
    
    
	return demo();
}
int main()
{
    
    
	demo a = get_demo();
	return 0;
}

Se puede ver que el programa se ejecuta dos veces para llamar al constructor de copia, la primera vez es en la función get_demo, la segunda vez es cuando se inicializa el objeto a, y estas dos copias son copias profundas, se puede ver en Linux Run result :
Use este comando para compilar el archivo; de lo contrario, no verá el resultado de salida completo

g++ main.cpp -fno-elide-constructors

Inserte la descripción de la imagen aquí
Imagine que si se solicita una gran cantidad de espacio de memoria en el montón durante los dos procesos de copia profunda, se perderá mucho tiempo y la eficiencia del programa será baja. Entonces, cómo optimizar este problema. De hecho, piénselo detenidamente. dos procesos de copia profunda generan todo Un objeto anónimo no puede obtener la dirección a través de &, por lo que es un valor r, y el programa puede optimizarse introduciendo un constructor de movimiento.

#include <iostream>
using namespace std;
class demo
{
    
    
public:
	demo() :num(new int(0))
	{
    
    
		cout << "construct!" << endl;
	}
	demo(const demo &d) :num(new int(*d.num))
	{
    
    
		cout << "copy construct!" << endl;
	}
	//添加移动构造函数
	demo(demo &&d) :num(d.num)
	{
    
    
		d.num = nullptr;
		cout << "move construct!" << endl;
	}
	~demo()
	{
    
    
		if(num != nullptr)
		{
    
    
			delete num;
			num = nullptr;
		}
		cout << "class destruct!" << endl;
	}
private:
	int *num;
};
demo get_demo()
{
    
    
	return demo();
}
int main()
{
    
    
	demo a = get_demo();
	return 0;
}

Verifique los resultados de ejecución del programa anterior:
Inserte la descripción de la imagen aquí
  puede ver que los dos procesos de copia profunda son completados por el constructor de movimientos. En el constructor de movimientos, el parámetro es una referencia de valor de demostración y el puntero del objeto recién generado es directamente El miembro apunta al espacio de pila solicitado por el objeto anónimo, y el puntero de miembro del objeto anónimo está en blanco. Esto evita que cada llamada al constructor de copia solicite nuevo espacio de memoria en el montón, lo que mejora enormemente la eficiencia.
  La denominada semántica de movimiento se refiere a la inicialización de objetos de clase que contienen miembros de puntero moviendo en lugar de copia profunda . En términos simples, la semántica de movimiento se refiere a "moverse a los recursos de memoria usados" que pertenecen a otros objetos (generalmente objetos temporales).

moverse

Por defecto, la inicialización de lvalue de objetos similares solo se puede realizar a través del constructor de copia. Si desea llamar al constructor de movimiento, debe utilizar rvalue para la inicialización. En el estándar C ++ 11, para satisfacer las necesidades de los usuarios que utilizan el constructor move para inicializar objetos similares con lvalues, se ha introducido recientemente la función std :: move (), que puede convertir el lvalue en el rvalue correspondiente . puede utilizar el constructor de movimiento.

#include <iostream>
using namespace std;
class MoveDemo
{
    
    
public:
	MoveDemo() :num(new int(0))
	{
    
    
		cout << "construct!" << endl;
	}
	MoveDemo(const MoveDemo &d) :num(new int(*d.num))
	{
    
    
		cout << "copy construct!" << endl;
	}
	//添加移动构造函数
	MoveDemo(MoveDemo &&d) :num(d.num)
	{
    
    
		d.num = nullptr;
		cout << "move construct!" << endl;
	}
	~MoveDemo()
	{
    
    
		if (num != nullptr)
		{
    
    
			delete num;
			num = nullptr;
		}
		cout << "class destruct!" << endl;
	}
public:
	int *num;
};
MoveDemo get_demo()
{
    
    
	return MoveDemo();
}
int main()
{
    
    
	MoveDemo demo;
	cout << "demo2:\n";
	MoveDemo demo2 = demo;
	//cout << *(demo2.num) << endl;   //可以执行
	cout << "demo3:\n";
	MoveDemo demo3 = std::move(demo);
	//此时 demo.num = NULL,因此下面代码会报运行时错误
	//cout << *(demo.num) << endl;
	return 0;
}

Inserte la descripción de la imagen aquí
Se puede ver que el constructor move se llama en el proceso de inicialización de demo 3. La razón es usar move para forzar el valor de demostración en un valor r.

Supongo que te gusta

Origin blog.csdn.net/bureau123/article/details/112696446
Recomendado
Clasificación