2015-09-24 10 views
8

Ho una classe con copia & spostato.std :: tupla per oggetto non copiabile e non mobile

struct A 
{ 
    A(int a):data(a){} 
    ~A(){ std::cout << "~A()" << this << " : " << data << std::endl; } 

    A(A const &obj) = delete; 
    A(A &&obj) = delete; 

    friend std::ostream & operator << (std::ostream & out , A const & obj); 

    int data; 
}; 

E voglio creare una tupla con oggetti di questa classe. Ma il seguente non viene compilato:

auto p = std::tuple<A,A>(A{10},A{20}); 

D'altra parte, il seguente fa di compilazione, ma dà una potenza sorprendente.

int main() { 
    auto q = std::tuple<A&&,A&&>(A{100},A{200}); 
    std::cout << "q created\n"; 
} 

uscita

~A()0x22fe10 : 100 
~A()0x22fe30 : 200 
q created 

significa che dtor per oggetti viene chiamato non appena termina la linea di costruzione tupla. Quindi, qual è il significato di una tupla di oggetti distrutti?

+3

'std :: tuple (A {100}, {A 200});' è una tupla di riferimenti ciondolanti. Penso 'std :: tuple p (100, 200);' dovrebbe funzionare invece –

+1

@PiotrSkotnicki Non si compila su clang o g ++: http://coliru.stacked-crooked.com/a/1fe2146b7881e241 – NathanOliver

+0

@NathanOliver it fa, o usa 'libC++' o un più recente 'libstdC++' –

risposta

10

Questo è un male:

auto q = std::tuple<A&&,A&&>(A{100},A{200}); 

si sta costruendo un tuple di riferimenti rvalue per provvisori che vengono distrutti alla fine dell'espressione, così si è lasciato con riferimenti penzoloni.

L'istruzione corretta sarebbe:

std::tuple<A, A> q(100, 200); 

Tuttavia, fino a poco tempo fa, quanto sopra è stato non supportato dallo standard. Nel N4296, la formulazione intorno il costruttore rilevante per tuple è [tuple.cnstr]:

Richiede: sizeof...(Types) == sizeof...(UTypes). is_constructible<Ti, Ui&&>::value è true per tutti i.
Effetti: Inizializza gli elementi nella tupla con il valore corrispondente in std::forward<UTypes>(u).
Nota: Questo costruttore non deve partecipare a risoluzione di sovraccarico a meno che ogni tipo in UTypes è implicitamente convertibile al suo tipo corrispondente Types.

Quindi, questo costruttore non partecipava alla risoluzione di sovraccarico a causa int non è implicitamente convertibile in A. Questo è stato risolto con l'adozione di Improving pair and tuple, che ha affrontato proprio il vostro caso d'uso:

struct D { D(int); D(const D&) = delete; };  
std::tuple<D> td(12); // Error 

La nuova formulazione di questo costruttore è, da N4527:

Note: Questo costruttore non deve partecipare alla risoluzione di sovraccarico a meno che sizeof...(Types) >= 1 e is_constructible<Ti, Ui&&>::value sia vero per tutto i.Il costruttore è esplicito se e solo se is_convertible<Ui&&, Ti>::value è false per almeno uno i.

E is_constructible<A, int&&>::value è vero.

Per presentare la differenza in un altro modo, ecco un'implementazione tupla estremamente ridotta:

struct D { D(int) {} D(const D&) = delete; }; 

template <typename T> 
struct Tuple { 
    Tuple(const T& t) 
    : T(t) 
    { } 

    template <typename U, 
#ifdef USE_OLD_RULES 
       typename = std::enable_if_t<std::is_convertible<U, T>::value> 
#else 
       typename = std::enable_if_t<std::is_constructible<T, U&&>::value> 
#endif 
       > 
    Tuple(U&& u) 
    : t(std::forward<U>(u)) 
    { } 

    T t; 
}; 

int main() 
{ 
    Tuple<D> t(12); 
} 

Se USE_OLD_RULES è definito, il primo costruttore è l'unico costruttore efficiente e quindi il codice non compila dal D è noncopyable. Altrimenti, il secondo costruttore è il miglior candidato possibile e quello è ben formato.


L'adozione era abbastanza recente che né gcc 5.2, né clang 3.6 in realtà si compilare questo esempio ancora. Quindi avrai bisogno di un compilatore più recente di quello (gcc 6.0 funziona) o di un design diverso.

+0

Ho ragione nel pensare che 'std :: tuple td = std :: make_tuple (12);' dovrebbe funzionare anche? – Yakk

+1

non è la funzione template tuple (UTypes && ... args); 'costruttore disponibile in C++ 11? –

+0

@Yakk No, il costruttore è 'explicit'. Ma 'std :: tuple td {std :: make_tuple (12)}' inoltre non funzionerebbe prima dell'adozione di quel documento. – Barry

1

Il tuo problema è che hai chiesto esplicitamente una tupla di riferimenti rvalue, e un riferimento di rvalue non è così lontano da un puntatore.

Così auto q = std::tuple<A&&,A&&>(A{100},A{200}); crea due oggetti A, richiederà (rvalue) riferimenti ad essi, a costruire la tupla con i riferimenti ... e distrugge gli oggetti temporanei, lasciando con due riferimenti penzoloni

Anche se si dice che essere più sicuro del buon vecchio C e dei suoi puntatori penzolanti, il C++ consente comunque al programmatore di scrivere programmi sbagliati.

In ogni caso, il seguente avrebbe senso (l'utilizzo nota di A & e non A & &):

int main() { 
    A a(100), b(100); // Ok, a and b will leave as long as main 
    auto q = tuple<A&, A&>(a, b); // ok, q contains references to a and b 
    ... 
    return 0; // Ok, q, a and b will be destroyed 
} 
Problemi correlati