Amigos, definitivamente entrarán en contacto con std :: move, std :: forward después de aprender inicialmente la operación de movimiento de C ++, pero ¿realmente comprenden el significado específico de std :: move y std :: forward?
Este artículo solo involucra parte de la explicación del conocimiento de std :: forward
Requisitos para usar std :: forward: parámetros de reenvío de plantillas (reenvío perfecto)
El llamado reenvío consiste en llamar a otra función en la función de plantilla, y los parámetros pasados por la función externa pueden mantener los atributos de valor izquierdo y derecho y pasar a la función interna.
Tiene aproximadamente la siguiente forma:
template<typename fun, typename a, typename b>
void exec(fun f, a tmpa, b tmpb) {
f(a,b);
}
Los siguientes son varios métodos de reenvío incorrectos o incorrectos:
## Reenvío de copia original
template<typename fun, typename a, typename b>
exec(fun f, a tmpa, b tmpb);
La función de plantilla ejecutiva en la Figura 1 a continuación pasa una copia de los objetos de tipo ayb (por ejemplo, se pasa una cadena, aunque aquí se pasa un int), lo cual es ineficiente.
Figura 1
template<typename fun, typename a, typename b>
void exec(fun f, a tmpa, b tmpb) {
f(a,b);
}
int tmp(int a, int b) {
cout << a * b << endl;
}
int main() {
int a = 5;
int b = 6;
exec(tmp, a, b);
}
## Reenvío de valor de función externa
template<typename fun, typename a, typename b>
exec(fun f, a& tmpa, b &tmpb);
La función de plantilla exec en la Figura 2 a continuación pasa las referencias de lvalue de los objetos de tipo ayb, y exec en la función principal no puede pasar directamente el rvalue del número 6.
Figura 2
template<typename fun, typename a, typename b>
void exec(fun f, a& tmpa, b& tmpb) {
f(a,b);
}
int tmp(int a, int b) {
cout << a * b << endl;
}
int main() {
int a = 5;
int b = 6;
exec(tmp, a, 6);
}
## Reenvío de lvalue de función externa, la función interna contiene parámetros de rvalue
template<typename fun, typename a, typename b>
exec(fun f, a& tmpa, b &tmpb);
Figura 3 a continuación, cuando el parámetro de tmp es una referencia de rvalue, dado que tmpb en exec es un lvalue, tmp no se puede dar como parámetro
imagen 3
template<typename fun, typename a, typename b>
void exec(fun f, a& tmpa, b& tmpb) {
f(a,b);
}
int tmp(int &a, int&& b) {
cout << a * b << endl;
}
int main() {
int a = 5;
int b = 6;
exec(tmp, a, b);
}
## Reenvío de referencia de valor r de función externa
template<typename fun, typename a, typename b>
void exec(fun f, a &&tmpa, b &&tmpb);
La siguiente figura 4 ha resuelto el problema de que exec pase el rvalue, pero el tmpb pasado a tmp sigue siendo un lvalue (el rvalue se referencia como un lvalue). Si usamos directamente
std :: move (tmpb) para pasar el rvalue a la función tmp, De hecho, es factible en este momento, pero no estamos seguros de si los valores tmpa y tmpb pasados por el mundo exterior a exec son de valor izquierdo o derecho, y el reenvío que necesitamos debe mantener los atributos de valor izquierdo y derecho de los parámetros. En este momento, no tenemos una buena manera de Para lidiar con la forma en que las funciones de plantilla llaman a funciones internas.
Figura 4
template<typename fun, typename a, typename b>
void exec(fun f, a &&tmpa, b &&tmpb) {
f(a,std::move(tmpb));
}
int tmp(int &a, int &&b) {
cout << a * b << endl;
}
int main() {
int a = 5;
int b = 6;
exec(tmp, a, 6);
}
Como enfoque unificado, necesitamos mantener el lvalue pasado a exec y enviarlo a tmp, y convertir el parámetro lvalue que contiene el rvalue de exec, como tmpb, en un rvalue y enviarlo a tmp, pero esto no es bueno por sí mismo. Implementación, pero la biblioteca estándar implementa inteligentemente este proceso std::forward<T>()
, la siguiente función de plantilla de la figura 5 solo necesita una función de llamada unificadaf(std::forward<a>(tmpa), std::forward<b>(tmpb));
Figura 5
template<typename fun, typename a, typename b>
void exec(fun f, a &&tmpa, b &&tmpb) {
f(std::forward<a>(tmpa), std::forward<b>(tmpb));
}
int tmp(int &a, int &&b) {
cout << a * b << endl;
}
int main() {
int a = 5;
int b = 6;
exec(tmp, a, 6);
}
Todo el mundo sentirá mucha curiosidad por el principio de implementación del avance.
Como se muestra en la Figura 6
std :: forward código fuente
/**
* @brief Forward an lvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{
return static_cast<_Tp&&>(__t); }
/**
* @brief Forward an rvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
Si el _Tp entrante es int o int & o int &&
std::remove_reference<_Tp>::type
será int
std::remove_reference<_Tp>::type&
será int &,
std::remove_reference<_Tp>::type&&
será int &&
Los parámetros tmpa y tmpb de exec son ambos lvalues (las referencias de rvalue son lvalues). En este momento, usar forward solo llamará a la versión lvalue de la función forward, tempa pasa int &, tempb pasa int &&, y se pliega por referencia. El tipo de a se deduce como int &, y b se deduce como int,
por lo que
forward (tmpa) es forward <int &> (int &) En la
función forward _Tp = int &, _ Tp && = int &,
por lo que return static_cast<_Tp&&>(__t)
devuelve int &, que es un lvalue
forward<b>(tmpb)
Es decir, forward<int>(int&) forward函数内_Tp=int&,_Tp&&=int&, 因此
return static_cast <_Tp &&> (__ t) `devuelve int &&, que es un rvalue
Por supuesto, cuando el argumento de avance es un valor r, se llamará a otra función sobrecargada y, finalmente, se devolverá el valor r.
Por lo tanto, se puede decir que el reenvío debe coincidir con los parámetros + && de la plantilla para lograr un reenvío perfecto
Por ejemplo, la función de puntero inteligente std :: make_shared usa el modelo de reenvío perfecto El misterio del código fuente en C ++ es realmente infinito.
template<typename _Tp, typename... _Args>
inline shared_ptr<_Tp>
make_shared(_Args&&... __args)
{
typedef typename std::remove_const<_Tp>::type _Tp_nc;
return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(),
std::forward<_Args>(__args)...);
}