<C++> C++11 referencia de valor r

Referencia de valor de C++ 11

1. Referencia de valor L y referencia de valor R

Hay una sintaxis de referencia en la sintaxis tradicional de C++ y la nueva característica de sintaxis de referencia de rvalue en C++11, por lo que a partir de ahora, la referencia que aprendimos antes se denomina referencia de lvalue. Independientemente de si se trata de una referencia de lvalue o de rvalue, se trata de un alias para el objeto.

¿Qué es un valor L? ¿Qué es una referencia lvalue?

Un lvalue es una expresión que representa datos (como un nombre de variable o un puntero sin referencia), **podemos obtener su dirección + asignarle un valor, un lvalue puede aparecer en el lado izquierdo de un símbolo de asignación y un rvalue no puede aparecerá en un símbolo de asignación a la izquierda. **El lvalue después del modificador const no se le puede asignar un valor cuando se define, pero se puede tomar su dirección. Una referencia de lvalue es una referencia a un lvalue, que crea un alias para el lvalue.

void test() {
    
    
    // 以下的p、b、c、*p都是左值

    int *p = new int(0);
    int b = 1;
    const int c = 2;
    // 以下几个是对上面左值的左值引用
    int *&rp = p;
    int &rb = b;
    const int &rc = c;
    int &pvalue = *p;
}

¿Qué es un valor r? ¿Qué es una referencia de rvalue?

Un valor r también es una expresión que representa datos, como: 字面常量, 表达式返回值, 函数返回值(esto no puede ser un retorno de referencia de valor l), etc., un valor r puede aparecer en el lado derecho de un símbolo de asignación, pero no puede aparecer en el lado izquierdo de un símbolo de asignación, y un valor r no se puede tomar como dirección.

Una referencia de rvalue es un tipo de referencia introducido en C++11 para enlazar a rvalues. Se declara usando comillas dobles &&. Una referencia de rvalue puede vincularla a un rvalue, lo que permite 移动语义sumar 完美转发.

void test() {
    
    
    double x = 1.1, y = 2.2;
    // 以下几个都是常见的右值
    10;
    x + y;
    fmin(x, y);
    // 以下几个都是对右值的右值引用
    int &&rr1 = 10;
    double &&rr2 = x + y;
    double &&rr3 = fmin(x, y);
    // 这里编译会报错:error C2106: “=”: 左操作数必须为左值
    10 = 1;  
    x + y = 1;
    fmin(x, y) = 1;
}

Cabe señalar que la dirección del rvalue no se puede tomar, pero después de que se le da el alias al rvalue, el rvalue se almacenará en una ubicación específica y se puede tomar la dirección de la ubicación, es decir, para ejemplo: el valor literal 10 no se puede tomar Dirección, pero después de referenciar rr1, se puede tomar la dirección de rr1, y rr1 también se puede modificar. Si no desea que se modifique rr1, puede usar const int&& rr1 para hacer referencia

void test() {
    
    
    double x = 1.1, y = 2.2;
    int &&rr1 = 10;
    const double &&rr2 = x + y;
    rr1 = 20;
    rr2 = 5.5;// 报错
    return 0;
}

const lvalue referencia rvalue

En C++, constlas referencias de lvalue pueden vincularse a rvalues, pero se requieren ciertas condiciones. Después de que C++ 11 introdujo las referencias de rvalue y la semántica de movimiento, los rvalues ​​se pueden vincular a las versiones de las referencias de lvalue const.

Cuando un rvalue está vinculado a constuna referencia de lvalue, el compilador crea un objeto temporal que es una copia del rvalue, y ese objeto temporal tiene la misma vigencia que la referencia de lvalue. De esta forma, constse puede acceder de forma segura al contenido de un rvalue a través de una referencia de lvalue.

void test() {
    
    
    int x = 10;
    cout << x << endl;   // 正确,x 是左值
    cout << 20 << endl;  // 正确,20 是右值
    const int &ref = x;  // 正确,将左值绑定到 const 左值引用
    const int &temp = 30;// 正确,将右值绑定到 const 左值引用,创建一个临时对象
}

constUna referencia de lvalue puede vincularse a un rvalue, pero se crea un objeto temporal para contener la copia del rvalue. Este tipo de enlace permite un acceso seguro a los contenidos de rvalues ​​y, en algunos casos, proporciona una mayor flexibilidad y reutilización del código.

2. Comparación de referencia lvalue y referencia rvalue

Resumen de las referencias de lvalue:

  1. Las referencias de Lvalue solo pueden referirse a lvalues, no a rvalues.
  2. Pero las referencias const lvalue pueden referirse tanto a lvalues ​​como a rvalues.
void test() {
    
    
    // 左值引用只能引用左值,不能引用右值。
    int a = 10;
    int &ra1 = a; // ra为a的别名
    int &ra2 = 10;// 编译失败,因为10是右值
    // const左值引用既可引用左值,也可引用右值。
    // 编译器会生成一个临时的常量对象,并将const左值引用绑定到该临时对象上。
    const int &ra3 = 10;
    const int &ra4 = a;
}

Si una const lvalue hace referencia a un rvalue, el compilador generará un objeto constante temporal y vinculará la referencia const lvalue al objeto temporal.

Resumen de las referencias de rvalue:

  1. Las referencias de valores R solo pueden ser valores R, no valores L.
  2. Pero una referencia de rvalue puede referirse a un lvalue después de un movimiento.
void test() {
    
    
    // 右值引用只能右值,不能引用左值。
    int &&r1 = 10;

    // message : 无法将左值绑定到右值引用
    int a = 10;
    int &&r2 = a;   // error C2440: “初始化”: 无法从“int”转换为“int &&”  
    // 右值引用可以引用move以后的左值
    int &&r3 = std::move(a);
}

3.mover

std::movees una plantilla de función en la biblioteca estándar de C++, ubicada <utility>en el archivo de encabezado. Se utiliza para transferir la propiedad de un objeto de un objeto a otro, normalmente para implementar la semántica de movimiento y evitar operaciones de copia de objetos innecesarias.

std::moveEl prototipo de la función es el siguiente:

template<class T>
constexpr typename std::remove_reference<T>::type&& move(T&& t) noexcept;

Esta función toma un objeto t, lo convierte en una referencia de valor r y devuelve un objeto que apunta a la referencia de valor r convertida. Una referencia de valor indica que la propiedad de un objeto se puede mover o transferir en lugar de copiar. std::moveEsencialmente, convertir un lvalue en una referencia de rvalue le dice al compilador que el objeto se puede mover en lugar de copiar.

El objetivo principal de using std::movees transferir los recursos de un objeto a otros objetos para mejorar la eficiencia al implementar la semántica de movimiento. La semántica de movimiento permite que los recursos (como la memoria asignada dinámicamente o los identificadores de archivos abiertos) se transfieran de un objeto a otro durante la transferencia de la propiedad del objeto sin operaciones de copia innecesarias.

Aquí hay un ejemplo simple que demuestra std::moveel uso de:

#include <iostream>
#include <utility>

class MyClass {
    
    
public:
    MyClass() {
    
    
        std::cout << "无构造" << std::endl;
    }

    MyClass(const MyClass& other) {
    
    
        std::cout << "拷贝构造" << std::endl;
    }

    MyClass(MyClass&& other) noexcept {
    
    
        std::cout << "移动构造" << std::endl;
    }
};

int main() {
    
    
    MyClass obj1;
    MyClass obj2 = std::move(obj1);  // 调用移动构造函数
    return 0;
}

En el ejemplo anterior, obj1y obj2son ambos MyClassobjetos de tipo . Al llamar std::move(obj1), obj1transferimos la propiedad de a obj2, por lo que se llama al constructor de movimiento durante la transferencia. Si lo hace, puede evitar llamar al constructor de copias y mejorar la eficiencia del programa.

Cabe señalar que std::movedespués de usar , el estado del objeto original es indeterminado, puede estar en estado válido, estado vacío o estado no disponible. Por lo tanto, std::movedespués de usar , la operación en el objeto original debe ser cautelosa y, en general, debe evitarse el objeto original.

std::movees una plantilla de función en C++ para transferir la propiedad de los objetos. Convierte un valor l en una referencia de valor r, lo que le dice al compilador que el objeto es elegible para operaciones de movimiento. Al usarlo std::move, se puede realizar la semántica de movimiento, se puede evitar la copia innecesaria de objetos y se puede mejorar la eficiencia del programa.

4. Utilice escenarios y significados de referencias de rvalue

Podemos ver anteriormente que las referencias de lvalue pueden referirse tanto a lvalues ​​como a rvalues, entonces, ¿por qué C++11 propone referencias de rvalue? ¿Es superfluo? ¡Echemos un vistazo a las deficiencias de las referencias de lvalue y cómo las referencias de rvalue compensan esta deficiencia!

La siguiente es la implementación de la clase de cadena

namespace phw {
    
    
    class string {
    
    
    public:
        typedef char *iterator;
        iterator begin() {
    
    
            return _str;
        }
        iterator end() {
    
    
            return _str + _size;
        }
        string(const char *str = "")
            : _size(strlen(str)), _capacity(_size) {
    
    
            //cout << "string(char* str)" << endl;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }
        // s1.swap(s2)
        void swap(string &s) {
    
    
            ::swap(_str, s._str);
            ::swap(_size, s._size);
            ::swap(_capacity, s._capacity);
        }
        // 拷贝构造
        string(const string &s)
            : _str(nullptr) {
    
    
            cout << "string(const string& s) -- 深拷贝" << endl;
            string tmp(s._str);
            swap(tmp);
        }
        // 赋值重载
        string &operator=(const string &s) {
    
    
            cout << "string& operator=(string s) -- 深拷贝" << endl;
            string tmp(s);
            swap(tmp);
            return *this;
        }
        // 移动构造
        string(string &&s)
            : _str(nullptr), _size(0), _capacity(0) {
    
    
            cout << "string(string&& s) -- 移动语义" << endl;
            swap(s);
        }
        // 移动赋值
        string &operator=(string &&s) {
    
    
            cout << "string& operator=(string&& s) -- 移动语义" << endl;
            swap(s);
            return *this;
        }
        ~string() {
    
    
            delete[] _str;
            _str = nullptr;
        }
        char &operator[](size_t pos) {
    
    
            assert(pos < _size);
            return _str[pos];
        }
        void reserve(size_t n) {
    
    
            if (n > _capacity) {
    
    
                char *tmp = new char[n + 1];
                strcpy(tmp, _str);
                delete[] _str;
                _str = tmp;
                _capacity = n;
            }
        }
        void push_back(char ch) {
    
    
            if (_size >= _capacity) {
    
    
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }
            _str[_size] = ch;
            ++_size;
            _str[_size] = '\0';
        }
        //string operator+=(char ch)
        string &operator+=(char ch) {
    
    
            push_back(ch);
            return *this;
        }
        const char *c_str() const {
    
    
            return _str;
        }

    private:
        char *_str;
        size_t _size;
        size_t _capacity;// 不包含最后做标识的\0
    };
}// namespace phw

Casos de uso para referencias de lvalue

Hacer parámetros y hacer valores de retorno puede mejorar la eficiencia.

void func1(phw::string s)
{
    
    }

void func2(const phw::string& s)
{
    
    }

int main()
{
    
    
	phw::string s1("hello world");
	// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
	func1(s1);   //深拷贝
	func2(s1);   //没有深拷贝
    //下面是string类中+=的定义,一个是传值,一个是传引用
	//string operator+=(char ch);  //传值返回存在深拷贝
	//string& operator+=(char ch);  //传左值引用没有拷贝提高了效率
	s1 += '!';
	return 0;
}

Deficiencias de las referencias de lvalue

Cuando el objeto de devolución de la función es una variable local, no existe fuera del alcance de la función, por lo que no puede devolverse por referencia de lvalue y solo puede devolverse por valor. Por ejemplo: puede ver en la función string to_string(int value) que aquí solo se puede usar el retorno por valor, y el retorno por valor causará al menos una construcción de copia (si se trata de algunos compiladores más antiguos, pueden ser construcciones de dos copias ).

string to_string(int value) {
    
    
    bool flag = true;
    if (value < 0) {
    
    
        flag = false;
        value = 0 - value;
    }
    string str;
    while (value > 0) {
    
    
        int x = value % 10;
        value /= 10;
        str += ('0' + x);
    }
    if (flag == false) {
    
    
        str += '-';
    }

    std::reverse(str.begin(), str.end());
    return str;
}
// str是一个局部对象,只能传值返回,因为局部对象出了函数外就销毁了,不能引用

El valor de retorno de esta función es un valor r. En C++, cuando una función devuelve un objeto temporal, el objeto se considera un valor r.

Para el valor de retorno de esta función, es un stringobjeto creado temporalmente y su ciclo de vida se limita al momento posterior al retorno de la función. Dado que este objeto temporal no tiene persistencia, el compilador puede optimizarlo, por ejemplo, moviendo el valor devuelto directamente a la ubicación de la persona que llama en lugar de realizar una operación de copia.

De acuerdo con la especificación de la biblioteca estándar de C++, la función estándar std::reverseno cambia la validez del iterador, por lo que, después de llamar std::reverse, strsigue siendo un valor válido. Por lo tanto, el compilador puede continuar con la optimización, como realizar struna operación de movimiento en lugar de una operación de copia.

int main()
{
    
    
   	string ret1 = to_string(1234);
	string ret2 = to_string(-1234);
 	return 0;
}

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

Como puede ver en la función string to_string(int value), aquí solo se puede usar el retorno por valor, y el retorno por valor causará al menos una construcción de copia (si se trata de algunos compiladores más antiguos, pueden ser dos construcciones de copia). El uso de la construcción de movimientos puede resolver el problema de la construcción de copias múltiples.

5. Mover construcciones y mover semántica

Un constructor de movimiento es un constructor especial que se usa para mover (o más bien robar) recursos de un objeto en lugar de copiarlos. Un constructor de movimiento normalmente toma una referencia de valor r como argumento y transfiere recursos del objeto pasado al objeto que se está construyendo. Un constructor de movimiento puede mejorar el rendimiento mediante el uso de semántica de movimiento porque evita costosas operaciones de copia.

La semántica de movimiento son características del lenguaje que permiten que los recursos se muevan de un objeto a otro, en lugar de copiarlos, cuando corresponda. La implementación de la semántica de movimiento se basa en constructores de movimiento y operadores de asignación de movimiento.

Agregue una estructura móvil a la cadena. La esencia de la estructura móvil es robar los recursos del valor r del parámetro. Si el espacio ya está allí, entonces no hay necesidad de hacer una copia profunda, por lo que se llama móvil estructura, que consiste en robar los recursos de otras personas para construirse a sí misma.

// 拷贝构造
string(const string& s)
	: _str(nullptr) {
    
    
	cout << "string(const string& s) -- 深拷贝" << endl;
	string tmp(s._str);
	swap(tmp);
}

// 移动构造
string(string &&s)
    : _str(nullptr), _size(0), _capacity(0) {
    
    
    cout << "string(string&& s) -- 移动语义" << endl;
    swap(s);
}


int main() {
    
    
    string ret2 = to_string(-1234);
    return 0;
}

Luego ejecute las dos llamadas de to_string arriba, encontraremos que la construcción de copia de copia profunda no se llama aquí, pero se llama la construcción de movimiento. No hay espacio nuevo en la construcción de movimiento para copiar datos, por lo que se mejora la eficiencia.

El valor de retorno de to_string es un valor R. Use este valor R para construir ret2. Si hay una construcción de copia y una construcción de movimiento, la llamada coincidirá con la llamada para mover la construcción, porque el compilador elegirá la llamada de parámetro más coincidente. Así que aquí hay una semántica de movimiento.

6. Asignación móvil

Agregue una función de asignación de movimiento en la clase de cadena y luego llame a to_string(1234), pero esta vez asigne el objeto rvalue devuelto por to_string(1234) al objeto ret1 y llame a la construcción de movimiento en este momento.

// 移动赋值
string &operator=(string &&s) {
    
    
    cout << "string& operator=(string&& s) -- 移动语义" << endl;
    swap(s);
    return *this;
}

int main() {
    
    
    string ret1;
    ret1 = to_string(1234);
    return 0;
}
// 运行结果:
// string(string&& s) -- 移动语义
// string& operator=(string&& s) -- 移动语义

Después de ejecutar aquí, vemos que se llama una construcción de movimiento y una asignación de movimiento. Porque si lo recibe un objeto existente, el compilador no puede optimizarlo. En la función to_string, se generará un objeto temporal utilizando primero la construcción de generación de str, pero podemos ver que el compilador es muy inteligente aquí para reconocer str como un valor r y llamar a la construcción de movimiento. Luego asigne este objeto temporal como el valor de retorno de la función to_string llamada a ret1, la asignación de movimiento llamada aquí.

En C ++ 11, los contenedores en STL han agregado construcción de movimiento y asignación de movimiento

7. Las referencias de valores R se refieren a valores L y algunos análisis en profundidad de escenarios de uso

De acuerdo con la gramática, una referencia de valor r solo puede referirse a un valor r, pero ¿una referencia de valor r no debe referirse a un valor l? Porque: en algunos escenarios, puede ser realmente necesario usar un valor r para hacer referencia a un valor l para lograr la semántica de movimiento. Cuando necesite usar una referencia de valor r para referirse a un valor l, puede usar la función de movimiento para convertir el valor l en un valor r . En C ++ 11, la std::move()función se encuentra <utility>en el archivo de encabezado. El nombre de la función es confuso. No mueve nada. Su única función es forzar un valor l en una referencia de valor r y luego implementar la semántica de movimiento.

Problemas no resueltos por referencias lvalue:

  • Problema de devolución de objeto local (el objeto local se destruye fuera del alcance)
  • Insertar interfaz, problema de copia de objeto (paso de parámetros, se producirá una copia)

Veamos un ejemplo:

template<class T>
T func(){
    
    
    T ret;

    //...
    return ret;
}

T x  = Func();

T es un tipo personalizado:

1. Si T es una clase de copia superficial, aquí está la construcción de copia, porque para las clases de copia superficial, la construcción de movimiento no tiene sentido.

2. Si T es una clase de copia profunda, aquí está la estructura de movimiento. Para la copia profunda, la estructura de movimiento puede transferir recursos de valor sin copiar

int main() {
    
    
    string s1("hello world");
    // 这里s1是左值,调用的是拷贝构造
    string s2(s1);
    // 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
    // 但是这里要注意,一般是不要这样用的,因为我们会发现s1的资源被转移给了s3,s1被置空了。
    string s3(std::move(s1));
    return 0;
}

inserte la descripción de la imagen aquí

La función de interfaz de inserción de contenedor STL también agrega una versión de referencia de rvalue:

void push_back(value_type &&val);
int main() {
    
    
    list<string> lt;
    string s1("1111");
    // 这里调用的是拷贝构造
    lt.push_back(s1);
    // 下面调用都是移动构造
    lt.push_back("2222");
    lt.push_back(std::move(s1));
    return 0;
}
运行结果:
// string(const string& s) -- 深拷贝
// string(string&& s) -- 移动语义
// string(string&& s) -- 移动语义
void Fun(int &x) {
    
     cout << "左值引用" << endl; }
void Fun(const int &x) {
    
     cout << "const 左值引用" << endl; }

inserte la descripción de la imagen aquí

8. Reenvío perfecto

El reenvío perfecto es una técnica que permite que una plantilla de función pase sus argumentos a otras funciones mientras conserva la clase de valor (lvalue o rvalue) del argumento original. Esta es una nueva característica introducida en C++11 mediante la introducción de dos nuevos calificadores de referencia &&.

El objetivo principal del reenvío perfecto es resolver el problema del paso de parámetros en plantillas de funciones. Por lo general, cuando pasamos un parámetro a otra función de una plantilla de función, la categoría de valor del parámetro cambiará, por ejemplo, un valor r puede convertirse en un valor l. Esto puede causar algunos problemas, especialmente cuando se trata de sobrecarga y plantillas.

Al usar el reenvío perfecto, podemos asegurarnos de que la clase de valor del parámetro permanezca sin cambios. Aquí hay un código de muestra que muestra cómo usar el reenvío perfecto:

#include <utility>

// 接受任意参数类型的函数模板
template <typename T>
void forwardFunction(T&& arg){
    
    
    otherFunction(std::forward<T>(arg)); // 完美转发参数
}

// 接受一个左值引用的函数
void otherFunction(int& arg){
    
    
    // 处理左值引用
}

// 接受一个右值引用的函数
void otherFunction(int&& arg){
    
    
    // 处理右值引用
}

void otherFunction(const int& arg){
    
    
    // 处理const左值引用
}

void otherFunction(const int&& arg){
    
    
    // 处理const右值引用
}

int main(){
    
    
    int value = 42;
    const int value2 = 10;
    forwardFunction(value);        // 传递左值
    forwardFunction(123);          // 传递右值
    forwardFunction(value2);       // 传递const左值
    forwardFunction(std::move(value2));   //传递const右值
    
    return 0;
}

En el ejemplo anterior, forwardFunctionhay una plantilla de función que acepta argumentos de cualquier tipo. Al usar T&&como tipo de argumento logramos un reenvío perfecto. Luego, al std::forward<T>(arg)pasar el parámetro a otherFunction, asegúrese de que la clase de valor del parámetro siga siendo la misma.

otherFunctionHay cuatro versiones sobrecargadas que aceptan referencias lvalue y referencias rvalue y las versiones const correspondientes. En forwardFunction, podemos pasar lvalues value​​y rvalues 123​​, así como const lvalues ​​, que serán reenviados correctamente a los correspondientes respectivamente otherFunction.

  • El && en la plantilla no representa una referencia de rvalue, sino una referencia universal, que puede recibir tanto lvalues ​​como rvalues.
  • La referencia universal de la plantilla solo brinda la capacidad de recibir referencias de valor l y referencias de valor r,
  • Pero la única función del tipo de referencia es limitar el tipo recibido y degenera en un valor l en el uso posterior.
  • Queremos poder mantener sus propiedades lvalue o rvalue durante el proceso de transferencia

std::forwardEl reenvío perfecto conserva las propiedades de tipo nativo del objeto durante el paso de parámetros

void Fun(int &x) {
    
     cout << "左值引用" << endl; }
void Fun(const int &x) {
    
     cout << "const 左值引用" << endl; }
void Fun(int &&x) {
    
     cout << "右值引用" << endl; }
void Fun(const int &&x) {
    
     cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T &&t) {
    
    
    Fun(std::forward<T>(t));
}

int main() {
    
    
    PerfectForward(10);// 右值
    int a;
    PerfectForward(a);           // 左值
    PerfectForward(std::move(a));// 右值
    const int b = 8;
    PerfectForward(b);           // const 左值
    PerfectForward(std::move(b));// const 右值
    return 0;
}

Escenarios de uso real de reenvío perfecto :

template<class T>
struct ListNode {
    
    
    ListNode *_next = nullptr;
    ListNode *_prev = nullptr;
    T _data;
};

template<class T>
class List {
    
    
    typedef ListNode<T> Node;

public:
    List() {
    
    
        _head = new Node;
        _head->_next = _head;
        _head->_prev = _head;
    }
    
    void PushBack(T &&x) {
    
    
        //Insert(_head, x);
        Insert(_head, std::forward<T>(x));
    }
    
    void PushFront(T &&x) {
    
    
        //Insert(_head->_next, x);
        Insert(_head->_next, std::forward<T>(x));
    }
    
    void Insert(Node *pos, T &&x) {
    
    
        Node *prev = pos->_prev;
        Node *newnode = new Node;
        newnode->_data = std::forward<T>(x);// 关键位置
        // prev newnode pos
        prev->_next = newnode;
        newnode->_prev = prev;
        newnode->_next = pos;
        pos->_prev = newnode;
    }
    
    void Insert(Node *pos, const T &x) {
    
    
        Node *prev = pos->_prev;
        Node *newnode = new Node;
        newnode->_data = x;// 关键位置
        // prev newnode pos
        prev->_next = newnode;
        newnode->_prev = prev;
        newnode->_next = pos;
        pos->_prev = newnode;
    }

private:
    Node *_head;
};

int main() {
    
    
    List<bit::string> lt;
    lt.PushBack("1111");
    lt.PushFront("2222");
    return 0;
}

El reenvío perfecto se utiliza para pasar parámetros desde PushBacky hacia funciones.PushFrontInsert

En la definición de la clase List, PushBacklas PushFrontfunciones miembro y aceptan parámetros de referencia rvalue T&& x. Al llamar PushBack("1111")a y PushFront("2222"), los literales de cadena se convierten en referencias de valor real.

Luego, en la función PushBackde suma PushFront, Insertse llama a la función y los argumentos xse std::forward<T>(x)envían perfectamente a Insertla función. Se utiliza aquí std::forwardpara garantizar que la clase de valor (lvalue o rvalue) del parámetro permanezca sin cambios.

En Insertla función, el miembro se reenvía al objeto recién creado xmediante un reenvío perfecto nuevamente . Al hacerlo, se evita copiar o mover innecesariamente y se conserva la categoría de valor del parámetro original.std::forward<T>(x)Node_data

Al usar el reenvío perfecto, el programa puede pasar correctamente los parámetros a Insertla función y mantener la categoría de valor de los parámetros sin cambios, evitando así operaciones innecesarias de copiar y mover. Este enfoque mejora la eficiencia del código y permite que los programas manejen argumentos de diferentes tipos (lvalue o rvalue).

Supongo que te gusta

Origin blog.csdn.net/ikun66666/article/details/131320880
Recomendado
Clasificación