2012-03-02 14 views
9

entiendo que hacer algo como lo siguiente:Evitar que las plantillas de expresión de enlace a rvalue Referencias de

auto&& x = Matrix1() + Matrix2() + Matrix3(); 
std::cout << x(2,3) << std::endl; 

causará un error de ejecución en silencio si las operaciones de la matriz utilizan plantillas de expresión (como boost::ublas).

¿Hay alguna manera de diseñar plantillas de expresión para evitar que el compilador compile tal código que puede resultar en el uso de temporarios caducados en el tiempo de ejecución?

(He intentado, sin éxito, para evitar este problema, el intento es here)

+3

Si prohibió tal encuadernación, 'operator + (expression_template const &, expression_template const &)' no compilaría tampoco. –

+0

@ R.MartinhoFernandes: ¿Por qué 'operator +' toma sus argumentos por 'expression_template const &'? Podría imaginarme que 'operator +' podría tomar sus argumentos a través de algún tipo de proxy que todavía no permitiría que 'const reference's se vincule inseguramente con las plantillas de expresiones. (No digo que sea posible, pero al menos no es trivialmente imposible). – Mankarse

+0

@Mankarse No puede mezclar las conversiones implícitas y la deducción del tipo de plantilla. Como tiene que elegir la deducción de tipo para 'operator +' para trabajar, los argumentos deben ser del tipo de la plantilla de expresión. (A menos que no entienda lo que quiere decir con "algún tipo de proxy") –

Respuesta

7

¿Hay alguna manera de diseñar las plantillas de expresión para evitar que el compilador de compilación de dicho código que puede resultar en el uso de expirados temporales en tiempo de ejecución?

No. Esto fue realmente reconocido antes de la estandarización final de C++ 11, pero no sé si alguna vez fue presentado al comité. No es que una solución hubiera sido fácil. Supongo que lo más simple sería una bandera en tipos que simplemente error si auto intenta deducirlo, pero incluso eso sería complejo porque decltype también lo puede deducir, así como también la deducción del argumento de la plantilla. Y los tres se definen de la misma manera, pero es probable que no quiera que el último falle.

Simplemente documente su biblioteca de forma adecuada y espere que nadie intente capturarlas de esa manera.

+0

¿Hay alguna forma de evitar que static_cast (x) sea válido si x es un 'T &'? Pregunto porque los temporarios nombrados parecen convertirse en '' T & 's '' cuando se usan más tarde, si se puede evitar que sean' 'estándar :: mover'' o que se conviertan en' 'T &&' el agujero puede cerrarse. – Clinton

+1

Los temporales con nombre son l-values, por lo que deben convertirse en referencias de valor l. Y si 'static_cast ' no era válido para todas las 'T &' s, entonces el reenvío y el movimiento fallarían. Entonces, no, no hay forma de romper el reenvío y el movimiento. Nuevamente, tendrá que confiar en que los usuarios no rompan su código. O simplemente no use plantillas de expresión. –

1

Como lo entiendo, el origen de su problema es que la plantilla de expresiones temporales puede tener referencias/punteros a algunos otros temporales. Y mediante el uso de auto & & solo ampliamos la vida útil de la plantilla de expresión en sí misma, pero no la duración de las temporarias a las que tiene referencias. ¿Es correcto?

Por ejemplo, ¿es this su caso?

#include <iostream> 
#include <deque> 
#include <algorithm> 
#include <utility> 
#include <memory> 
using namespace std; 

deque<bool> pool; 

class ExpressionTemp; 
class Scalar 
{ 
    bool *alive; 

    friend class ExpressionTemp; 

    Scalar(const Scalar&); 
    Scalar &operator=(const Scalar&); 
    Scalar &operator=(Scalar&&); 
public: 
    Scalar() 
    { 
     pool.push_back(true); 
     alive=&pool.back(); 
    } 
    Scalar(Scalar &&rhs) 
     : alive(0) 
    { 
     swap(alive,rhs.alive); 
    } 
    ~Scalar() 
    { 
     if(alive) 
      (*alive)=false; 
    } 
}; 
class ExpressionTemp 
{ 
    bool *operand_alive; 
public: 
    ExpressionTemp(const Scalar &s) 
     : operand_alive(s.alive) 
    { 
    } 
    void do_job() 
    { 
     if(*operand_alive) 
      cout << "captured operand is alive" << endl; 
     else 
      cout << "captured operand is DEAD!" << endl; 
    } 
}; 

ExpressionTemp expression(const Scalar &s) 
{ 
    return {s}; 
} 
int main() 
{ 
    { 
     expression(Scalar()).do_job(); // OK 
    } 
    { 
     Scalar lv; 
     auto &&rvref=expression(lv); 
     rvref.do_job(); // OK, lv is still alive 
    } 
    { 
     auto &&rvref=expression(Scalar()); 
     rvref.do_job(); // referencing to dead temporary 
    } 
    return 0; 
} 

Si sí, entonces una de las posibles soluciones, es hacer tipo especial de provisionales plantilla de expresión que poseen recursos movido de los temporales.

Por ejemplo, consulte el enfoque this (puede definir la macro BUG_CASE, para obtener de nuevo el caso de error).

//#define BUG_CASE 

#include <iostream> 
#include <deque> 
#include <algorithm> 
#include <utility> 
#include <memory> 
using namespace std; 

deque<bool> pool; 

class ExpressionTemp; 
class Scalar 
{ 
    bool *alive; 

    friend class ExpressionTemp; 

    Scalar(const Scalar&); 
    Scalar &operator=(const Scalar&); 
    Scalar &operator=(Scalar&&); 
public: 
    Scalar() 
    { 
     pool.push_back(true); 
     alive=&pool.back(); 
    } 
    Scalar(Scalar &&rhs) 
     : alive(0) 
    { 
     swap(alive,rhs.alive); 
    } 
    ~Scalar() 
    { 
     if(alive) 
      (*alive)=false; 
    } 
}; 
class ExpressionTemp 
{ 
#ifndef BUG_CASE 
    unique_ptr<Scalar> resource; // can be in separate type 
#endif 
    bool *operand_alive; 
public: 
    ExpressionTemp(const Scalar &s) 
     : operand_alive(s.alive) 
    { 
    } 
#ifndef BUG_CASE 
    ExpressionTemp(Scalar &&s) 
     : resource(new Scalar(move(s))), operand_alive(resource->alive) 
    { 
    } 
#endif 
    void do_job() 
    { 
     if(*operand_alive) 
      cout << "captured operand is alive" << endl; 
     else 
      cout << "captured operand is DEAD!" << endl; 
    } 
}; 

template<typename T> 
ExpressionTemp expression(T &&s) 
{ 
    return {forward<T>(s)}; 
} 
int main() 
{ 
    { 
     expression(Scalar()).do_job(); // OK, Scalar is moved to temporary 
    } 
    { 
     Scalar lv; 
     auto &&rvref=expression(lv); 
     rvref.do_job(); // OK, lv is still alive 
    } 
    { 
     auto &&rvref=expression(Scalar()); 
     rvref.do_job(); // OK, Scalar is moved into rvref 
    } 
    return 0; 
} 

Sus sobrecargas operador/función puede devolver different types, en función de T & &/const T & argumentos:

#include <iostream> 
#include <ostream> 
using namespace std; 

int test(int&&) 
{ 
    return 1; 
} 
double test(const int&) 
{ 
    return 2.5; 
}; 

int main() 
{ 
    int t; 
    cout << test(t) << endl; 
    cout << test(0) << endl; 
    return 0; 
} 

lo tanto, cuando la plantilla de la expresión temporal no tienen recursos movido de los temporales - es el tamaño no se verá afectado.

+0

Técnicamente, 'auto' * es * la raíz del problema. Normalmente puede ocultar el tipo de plantilla de expresión detrás de los miembros 'privados'. No es "difícil deletrear tipo"; el compilador * evitará * que use explícitamente el tipo. El problema es que 'auto' y' decltype' dan la vuelta a todo el asunto 'público/privado', lo que te permite crear tipos que de otro modo no podrías, siempre y cuando nunca uses el nombre del tipo en sí. –

+0

Ok, veo - auto había roto la capa de protección "privada", que protegía el problema más fundamental. Pero, por ejemplo, en la pregunta formulada: http://ideone.com/7i3yT, auto && puede reemplazarse con ExpressionTemplate &&. –

Cuestiones relacionadas