2011-08-23 3 views
7

La lettura della risposta this relativa alla restituzione dei riferimenti di valore dalla funzione mi ha fatto pensare, come posso scrivere una funzione id in C++ 0x.Funzione "id" in C++ 0x

Fondamentalmente, voglio che la funzione id non funzioni, una funzione che non ha effetti osservabili sul programma.

Il mio primo tentativo è la seguente:

#include <iostream> 

class X 
{ 
public: 
    X(std::string&& s) : s(std::move(s)) {}; 
    X(const std::string& s) : s(s) {}; 
    std::string s; 
    ~X() { std::cout << "Destroying: " << s << std::endl; } 
private: 
    X(const X&) {}; 
    X(X&&) {}; 
}; 

template <class T> 
T&& id(T&& x) { return static_cast<T&&>(x); } 

int main() 
{ 
    auto&& x1 = X("x1"); 
    std::cout << "Line 1" << std::endl; 
    auto&& x2 = id(X("x2")); 
    std::cout << "Line 2" << std::endl; 
} 

Tuttavia, temo che in questo caso, x2 è un riferimento penzoloni, come X("x2") viene distrutto prima di "Linea 2" esegue.

Quindi, qui chiaramente, id ha un effetto osservabile.

Come posso scrivere una funzione id in C++ 0x, che funziona in particolare per i tipi senza costruttori move/copy.

+0

Il problema è che una dichiarazione di funzione non comunica se il riferimento restituito si riferisce ancora allo stesso oggetto che è stato passato ad esso. E non è ragionevole forzare i compilatori a verificare l'implementazione della funzione al fine di determinare cosa esattamente ritorna e fare in modo che la vita di un temporaneo dipenda da questa analisi. – sellibitze

risposta

7

Non è possibile. Di norma, non dovresti scrivere funzioni che restituiscono riferimenti di valore e, come hai indicato correttamente, non puoi estendere abbastanza a lungo la durata del temporaneo.

0

Che cosa si vuole fare è chiamato perfetta inoltro, e c'è una funzione nella STL che lo fa:

template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept 
{ 
    return static_cast<T&&>(t) 
} 
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept 
{ 
    return static_cast<T&&>(t) 
} 

è necessario il remove_reference per evitare il collasso di riferimento . E quando lo si utilizza, si dovrà specificare il tipo di oggetto che si sta tentando di trasmettere:

std::forward<X>(X("x2")); 
+1

Credo che sia una vecchia specifica e non quello che è attualmente Standard. Inoltre, * non * risolve il problema. – Puppy

0

La maggior parte delle cose in linguaggi di programmazione non sono completamente e totalmente gratuito. A meno che non si stia scrivendo solo codice in fase di compilazione, è improbabile che la scrittura di una funzione di identità sia gratuita.

Diamo rielaborazioni il codice un po ':

#include <algorithm> 
#include <iostream> 

template <typename T> 
T id1(T&& t) 
{ 
    return t; 
} 

template <typename T> 
T id2(T&& t) 
{ 
    return std::move(t); 
} 


class X 
{ 
public: 
    X() 
    { output0("Xdef"); } 
    X(std::string const& s) : label_(s) 
    { output1("Xstr",s); } 
    X(X const& x) : label_(x.label_) 
    { output1("Xcopy", x); } 
    X(X&& x) : label_(std::move(x.label_)) 
    { output1("Xmove", x); } 

    X& operator =(X const& x) 
    { 
    output1("operator =copy", x); 
    label_ = x.label_; 
    return *this; 
    } 

    X& operator =(X&& x) 
    { 
    using std::swap; 
    output1("operator =move", x); 
    swap(label_, x.label_); 
    return *this; 
    } 

    ~X() 
    { output0("~X"); } 

private: 
    void output_id() const 
    { 
    std::cout << this << '[' << label_ << "]"; 
    } 

    void output0(std::string const& name) const 
    { 
    output_id(); 
    std::cout << ": " << name << "()" << std::endl; 
    } 

    void output1(std::string const& name, std::string const& str) const 
    { 
    output_id(); 
    std::cout 
     << ": " << name 
     << "(\"" << str 
     << "\")" << std::endl; 
    } 

    void output1(std::string const& name, X const& arg) const 
    { 
    output_id(); 
    std::cout << ": " << name << '('; 
    arg.output_id(); 
    std::cout << ')' << std::endl; 
    } 

    std::string label_; 
}; 

int main() 
{ 
    { 
    std::cout << "CASE A:\n"; 
    auto x = X("x1"); 
    } 
    std::cout << "\n"; 
    { 
    std::cout << "CASE B:\n"; 
    auto x = id1(X("x2")); 
    } 
    std::cout << "\n"; 
    { 
    std::cout << "CASE C:\n"; 
    auto x = id2(X("x3")); 
    } 
    std::cout << "\n"; 
    { 
    std::cout << "CASE D:\n"; 
    X x = id1(X("x4")); 
    } 
    std::cout << "\n"; 
    { 
    std::cout << "CASE E:\n"; 
    X x = id2(X("x5")); 
    } 
}  

e quando eseguirlo uscite (usando uno snapshot GCC v4.8):

$ ./a.out 
CASE A: 
0x7fff411fc530[x1]: Xstr("x1") 
0x7fff411fc530[x1]: ~X() 

CASE B: 
0x7fff411fc540[x2]: Xstr("x2") 
0x7fff411fc520[x2]: Xcopy(0x7fff411fc540[x2]) 
0x7fff411fc540[x2]: ~X() 
0x7fff411fc520[x2]: ~X() 

CASE C: 
0x7fff411fc540[x3]: Xstr("x3") 
0x7fff411fc520[x3]: Xmove(0x7fff411fc540[]) 
0x7fff411fc540[]: ~X() 
0x7fff411fc520[x3]: ~X() 

CASE D: 
0x7fff411fc540[x4]: Xstr("x4") 
0x7fff411fc520[x4]: Xcopy(0x7fff411fc540[x4]) 
0x7fff411fc540[x4]: ~X() 
0x7fff411fc520[x4]: ~X() 

CASE E: 
0x7fff411fc540[x5]: Xstr("x5") 
0x7fff411fc520[x5]: Xmove(0x7fff411fc540[]) 
0x7fff411fc540[]: ~X() 
0x7fff411fc520[x5]: ~X() 
$ 

Caso A invoca semplicemente il costruttore per X. Il = in questo caso equivale a passare il lato destro di = a X, ovvero non si tratta di un compito.

Caso B invoca id1() che non sposta l'argomento di ritorno. Poiché il valore restituito non è stato definito nello stack di chiamate di id() e il valore è un lvalue (in possesso di un valore rval), non è stato spostato automaticamente al momento del reso e pertanto è stato copiato.

Il caso C invoca id2() che richiama il costruttore di spostamento al ritorno.

I casi D ed E sono gli stessi dei casi B e C, ad eccezione del fatto che lo auto non viene utilizzato se si era scettici su tale argomento.

Le mosse dovrebbero essere viste come copie ottimizzate e altrettanto brutte delle copie nel caso peggiore (anche se spesso saranno molto meglio).Anche una mossa ottimale ha un costo (ad es. Copiare alcuni dati (di solito) da un frame di stack a un altro). L'unico modo in cui le copie/le mosse sono completamente evitate nel codice di runtime è quando l'ottimizzazione del valore di ritorno e la copia ellison sono valide per il compilatore da utilizzare.