Pase directo (C++)

La transferencia directa ( Ing.  Perfect Forwarding ) es un mecanismo idiomático para transferir atributos de parámetros en procedimientos del código generalizado del lenguaje C++ . Ha sido estandarizado en la edición C++11 con funcionalidad STL y  sintaxis de referencias de reenvío , y unificado para su uso con plantillas variadas [1] [2] .

El paso directo se usa cuando se requiere que funciones y procedimientos de código genérico dejen sin cambios las propiedades fundamentales de sus argumentos parametrizados, es decir [1] :

La implementación práctica del paso directo en el estándar de idioma se implementa mediante una función std::forwarddel archivo de encabezado <utility>[3] [4] . Como resultado, la combinación de reglas de inferencia especiales para &&referencias y su plegado le permite crear una plantilla funcional que acepta argumentos arbitrarios con la fijación de sus tipos y propiedades básicas ( rvalue o lvalue ). Guardar esta información predetermina la capacidad de pasar estos argumentos al llamar a otras funciones y métodos [5] .

Antecedentes

Comportamiento especial de los parámetros - Enlaces temporales

Consideremos el objeto elemental con dos constructores: uno copia un campo de std::string, el segundo se mueve.

objeto de clase { público : Obj ( const estándar :: cadena & x ) : campo ( x ) {} Obj ( std :: string && x ) : field ( std :: move ( x )) {} // std::¡¡se necesita mover!! privado : std :: campo de cadena ; _ }

La primera sobrecarga del constructor es la más común de C++03. Y en el segundo std:: move, y por eso.

El parámetro string&& externo es una referencia temporal (rvalue), y no es posible pasar un objeto con nombre (lvalue). Y dentro de la función, este parámetro se llama (lvalue), es decir, string&. Esto se hace por seguridad: si una función que toma una cadena&& se somete a manipulaciones de datos complejas, es imposible destruir accidentalmente el parámetro cadena&&.

Las preguntas comienzan cuando hay muchos parámetros: debe hacer 4, 8, 16 ... constructores.

clase Obj2 { público : Obj ( const std :: string & x1 , const std :: string & x2 ) : campo1 ( x1 ), campo2 ( x2 ) {} Obj ( const std :: string & x1 , std :: string && x2 ) : field1 ( x1 ), field2 ( std :: move ( x2 )) {} // ...y dos sobrecargas privadas más : std :: cadena campo1 , campo2 ; }

Hay dos formas de no multiplicar entidades, el lenguaje "por valor+mover" y la metaprogramación , y para este último se ha creado un segundo mecanismo C++11.

Pegado de enlaces

El colapso de referencias se explica mejor con este código . 

usando Uno = int && ; usando Dos = Uno & ; // entonces Dos = int&

Al pasar a referencias pasadas, no solo se descubre el tipo de parámetro pasado a la función, sino que también se evalúa si es un valor r o un valor l . Si el parámetro pasado a la función es un lvalue , entonces el valor sustituido también será una referencia al lvalue . Dicho esto, se observa que declarar un tipo de parámetro de plantilla como una &&referencia puede tener efectos secundarios interesantes. Por ejemplo, se hace necesario especificar explícitamente inicializadores para todas las variables locales de un tipo dado, ya que cuando se usan con parámetros lvalue , la inferencia de tipo después de instanciar la plantilla les asignará el valor de una referencia lvalue , que, por requisito de idioma, debe tener un inicializador [6] .

El pegado de enlaces permite los siguientes patrones:

objeto de clase { público : plantilla < claseT > _ Obj ( T && x ) : field ( std :: forward < T > ( x )) {} // avance y hágalo bien private : // a continuación explicamos por qué no puede hacerlo sin una función de avance explícita std :: campo de cadena ; }

Para tales referencias temporales, los compiladores han agregado reglas especiales [7] , por lo que...

  • si T=cadena, seráObj(string&&)
  • si T=cadena&, seráObj(string&)
  • si T=const string&, seráObj(const string&)

Consecuencia: no es posible saber automáticamente si un enlace es temporal

Volvamos al constructor de plantillas Obj::Obj. Si no considera tipos extraños, sino solo cadenas, hay tres opciones posibles.

  • T=cadena, instanciada en , dentro de x=cadena&.Obj(string&&)
  • T=cadena&, instanciado en , dentro de x=cadena&.Obj(string&)
  • T=const string&, instanciado en , dentro de x=const string&.Obj(const string&)

La tercera opción está bien, pero la inferencia de tipo simple no puede distinguir la primera opción de la segunda. Pero en la primera variante, se necesita std::move para obtener el máximo rendimiento, en la segunda es peligroso: la asignación con un movimiento "destripará" la cadena, lo que aún puede ser útil.

Solución: std::forward

Volvamos a nuestro constructor de plantillas.

plantilla < claseT > _ Obj ( T && x ) : campo ( std :: adelante < T > ( x )) {}

La plantilla se usa solo en plantillas (hay suficiente en código que no es plantilla ). Requiere que el tipo se especifique explícitamente (de lo contrario, no se puede distinguir de ), y no hace nada o se expande a . std::forwardstd::moveObj(string&&)Obj(string&)std::move

Modismo "por valor + mover"

La segunda forma de no multiplicar entidades: el parámetro se toma por valor y se pasa a través de . std::move

objeto de clase { público : Obj ( estándar :: cadena x ) : campo ( estándar :: mover ( x )) {} privado : std :: campo de cadena ; _ }

Se usa cuando mover un objeto es significativamente "más fácil" que copiarlo, generalmente en código que no es de plantilla.

Notas

  1. 1 2 Vandewoerd, 2018 , 6.1 En vivo, pág. 125.
  2. Horton, 2014 , Perfect Forwarding, pág. 373.
  3. std::forward Archivado el 19 de enero de 2019 en la referencia de C++ de Wayback Machine .
  4. Vandewoerd 2018 , 15.6.3 En vivo, pág. 333.
  5. Vandewoerd 2018 , 15.6.3 En vivo, pág. 332.
  6. Vandewoerd, 2018 , 15.6.2 Enlaces transferibles, p. 331.
  7. Vandewoerd, 2018 , 6.1 En vivo, pág. 127.

Fuentes

  • D. Vandevoerd, N. Josattis, D. Gregor. Plantillas C++. Referencia del desarrollador = Plantillas de C++. La guía completa. - 2do. - San Petersburgo.  : "Alfa-libro", 2018. - 848 p. - ISBN 978-5-9500296-8-4 .
  • I.Horton. Visual C++ inicial de Ivor Horton ® 2013. - John Wiley & Sons, Inc., 2014. - ISBN 978-1-118-84577-6 .

Enlaces