2010-10-04 7 views
9

Digamos que tengo esta clase de excepción:¿Alguien puede explicar las referencias de valor r con respecto a las excepciones?

struct MyException : public std::exception 
{ 
    MyException(const std::exception &exc) : std::exception(exc) 
    { 
     cout << "lval\n"; 
    } 
    MyException(std::exception &&exc) : std::exception(std::forward<std::exception>(exc)) 
    { 
     cout << "rval\n"; 
    } 
}; 

... 
... 

try 
{ 
    throw std::exception("Oh no!"); 
    // above is rvalue since it's got no name, what if the throw is made as 
    // std::exception lvalExc("Oh wierd!"); 
    // throw lvalExc; 
    // if the throw is made thus, how can it be caught by catch(std::exception &&exc)? 
} 
catch(std::exception &&rValRef) 
{ 
    cout << "rValRef!\n"; 
    throw MyException(std::forward<std::exception>(rValRef)); 
} 

Cuando intenté coger por valor o por (const ) ref lvalue. el compilador dice que estos casos ya están manejados por la cláusula rvalue ref catch, lo cual es comprensible, ya que una excepción es un xvalue y quizás la mejor forma de capturar un valor x es una referencia rvalue (corríjame si estoy equivocado). Pero, ¿alguien puede explicar sobre el perfect forwarding en el caso anterior de creación de excepciones? ¿Es correcto? Aunque compila, ¿es significativo o útil? ¿Debería la biblioteca C++ que uso tener un constructor de movimiento implementado para su std::exception para que este tipo de uso sea realmente significativo? Traté de buscar artículos y SO preguntas sobre referencias rvalue con respecto a excepciones, no pude encontrar ninguna.

Respuesta

7

En realidad, el manejo de excepciones tiene reglas especiales con respecto a lvalues ​​y rvalues. El objeto excepción temporal es un lvalue, ver 15.1/3 del proyecto actual:

Un saque expresión inicializa un objeto temporal, llamado el objeto de excepción, el tipo de que se determina por la eliminación de cualquier cv de nivel superior -calificadores del tipo estático del operando de throw y ajustando el tipo de "array of T" o "function returning T" a "puntero a T" o "puntero a función que devuelve T", respectivamente. El temporal es un lvalue y se utiliza para inicializar la variable nombrada en el controlador coincidente (15.3). Si el tipo del objeto de excepción fuera un tipo incompleto o un puntero a un tipo incompleto que no sea (posiblemente calificado para CV) nulo, el programa está mal formado. Excepto por estas restricciones y las restricciones de coincidencia de tipos mencionadas en 15.3, el operando de lanzamiento se trata exactamente como un argumento de función en una llamada (5.2.2) o el operando de una declaración de retorno.

y la captura por referencia rvalue es ilegal, también, ver 15.3/1:

La excepción-declaración en un manejador describe el tipo (s) de excepciones que pueden causar que se introduzca ese manejador . La declaración de excepción no denotará un tipo incompleto o un tipo de referencia rvalue. La declaración de excepción no denotará un puntero o referencia a un tipo incompleto, que no sea void *, const void *, volátil void * o const volatile void *.

Además, parece que no entiende el reenvío perfecto. Su invocación hacia adelante no es mejor que un movimiento. La idea del reenvío perfecto es codificar la categoría de valor del argumento como parte del tipo y dejar que la deducción del argumento de la plantilla lo resuelva. Pero su manejador de excepciones no es ni puede ser una plantilla de función.

Básicamente, el reenvío perfecta se basa en la plantilla argumento de deducción y rvalue referencias:

void inner(const int&); // #1 takes only lvalues or const rvalues 
void inner(int&&);  // #2 takes non-const rvalues only 

template<class T> 
void outer(T && x) { 
    inner(forward<T>(x)); 
} 

int main() { 
    int k = 23; 
    outer(k); // outer<T=int&> --> forward<int&> --> #1 
    outer(k+2); // outer<T=int> --> forward<int> --> #2 
} 

Dependiendo de la categoría de valor del argumento, la deducción plantilla argumend deduce T para ser una referencia de valor-I o un tipo de valor normal. Debido al colapso de referencia, T & & es también una referencia lvalue en el primer caso, o una referencia rvalue en el segundo caso. Si ve T & & y T es un parámetro de plantilla que se puede deducir, básicamente es un "atrapar todo".std :: forward restaura la categoría de valor original (codificada en T) para que podamos reenviar perfectamente el argumento a las funciones internas sobrecargadas y seleccionar la correcta. Pero esto solo funciona porque outer es una plantilla y porque existen reglas especiales para determinar T con respecto a su categoría de valor. Si utiliza una referencia de valor r sin deducción de argumento de plantilla/plantilla (como en el n. ° 2), la función solo aceptará valores r.

+1

+1: Usted dice lo que dije, pero más y mejor. Además, solo para hacer explícito el consejo, una referencia simple no const coincidirá y podrá modificar el objeto de excepción. – Potatoswatter

+0

@Potatoswatter: Perdón por repetir la mitad de tu respuesta. De hecho, me perdí tu referencia al 15.1/3 cuando revisé tu respuesta. – sellibitze

+0

@sellibitze: No tiene sentido que sean mutuamente excluyentes. Puede pedir prestado cualquier cosa siempre que su respuesta sea correcta: v). – Potatoswatter

Cuestiones relacionadas