2012-03-02 6 views
9

Capisco che fare qualcosa di simile al seguente:Prevenire modelli di espressione vincolanti per rvalue fa riferimento

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

causerà un errore di runtime in silenzio se le operazioni di matrice utilizzano modelli di espressione (ad esempio boost::ublas).

Esiste un modo per progettare modelli di espressioni per impedire al compilatore di compilare tale codice che potrebbe comportare l'utilizzo di provvisori scaduti in fase di runtime?

lì (ho tentato senza successo di risolvere questo problema, il tentativo è here)

+3

Se si proibisce tale associazione, 'operator + (espressione_template const &, expression_template const &)' non verrebbe compilato neanche. –

+0

@ R.MartinhoFernandes: Perché 'operator +' accetta i suoi argomenti con 'expression_template const &'? Potrei immaginare che 'operator +' possa prendere i suoi argomenti attraverso una sorta di proxy che ancora non permetterebbe che' const reference's sia legato in modo non sicuro ai template di espressione. (Non sto dicendo che sia possibile, ma almeno non è banalmente impossibile). – Mankarse

+0

@Mankarse Non è possibile combinare conversioni implicite e deduzioni del tipo di modello. Poiché per operare è necessario selezionare la deduzione di tipo per 'operator +', gli argomenti devono essere il tipo del modello di espressione. (A meno che non fraintenda cosa intendi per "una sorta di proxy") –

risposta

7

è alcun modo di progettare modelli di espressione per evitare che il compilatore di compilazione di tale codice che può comportare l'uso di Temporary scaduti in fase di runtime?

No. Questo è stato effettivamente riconosciuto prima della standardizzazione finale del C++ 11, ma non so se è mai stato portato a conoscenza della commissione. Non che una soluzione sarebbe stata facile. Suppongo che la cosa più semplice sarebbe un flag sui tipi che farebbe semplicemente errore se auto prova a dedurlo, ma anche quello sarebbe complesso perché lo può dedurre anche da decltype, così come deduzione argomento template. E tutti e tre sono definiti nello stesso modo, ma probabilmente non vuoi che fallisca.

Basta documentare la libreria in modo appropriato e sperare che nessuno cerchi di catturarli in quel modo.

+0

Esiste comunque la possibilità di impedire che stat_cast (x) sia valido se x è un 'T &'? Chiedo perché i temporaries denominati sembrano diventare 'T & se usati in seguito, se possono essere impediti di essere' std :: move'd o altrimenti convertiti in 'T &&' il buco può essere chiuso. – Clinton

+1

I temporaries denominati sono valori -L, quindi dovrebbero diventare riferimenti di valore l. E se 'static_cast ' non fosse valido per tutti i 'T &' s, quindi l'inoltro e lo spostamento fallirebbero. Quindi no, non c'è modo di interrompere l'inoltro e lo spostamento. Di nuovo, dovrai fare affidamento sugli utenti per non infrangere il tuo codice. O semplicemente non usare i modelli di espressione. –

1

Come ho capito, la radice del problema è che il modello di espressione temporaneo può avere riferimenti/puntatori ad altri temporaries. E usando l'auto & & estendiamo solo la vita del modello di espressione temporaneo, ma non la durata dei temporari a cui ha riferimenti. È giusto?

Per esempio, è this il tuo 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; 
} 

In caso affermativo, una delle possibili soluzioni consiste nel creare un tipo speciale di modello di espressione temporaneo che trattiene le risorse spostate dai provvisori.

Ad esempio, controllare l'approccio this (è possibile definire la macro BUG_CASE per ottenere nuovamente il caso di errore).

//#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; 
} 

vostri sovraccarichi operatore/funzione può restituire different types, a seconda T & &/const T & argomenti:

#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; 
} 

Così, quando il vostro modello di espressione temporanea non hanno risorse spostati da provvisori - è le dimensioni non saranno influenzate.

+0

Tecnicamente, 'auto' * è * la radice del problema. Di norma puoi nascondere il tipo di modello di espressione dietro i membri 'private'. Non è "difficile da digitare"; il compilatore * impedirà * di usare il tipo esplicitamente. Il problema è che 'auto' e' decltype' fanno da sottosopra l'intera cosa 'public/private', permettendoti di creare tipi che altrimenti non potresti, a patto che non usi mai il nome del tipo stesso. –

+0

Ok, capisco - auto aveva rotto il livello di protezione "privata", che proteggeva un problema più fondamentale. Ma ad esempio nella domanda fatta - http://ideone.com/7i3yT, auto && può essere sostituito con ExpressionTemplate &&. –