std :: nota de reenvío

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>::typeserá 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)...);
    }

Supongo que te gusta

Origin blog.csdn.net/adlatereturn/article/details/108732422
Recomendado
Clasificación