2016-03-22 9 views
10

LWG 2424 discute lo stato indesiderato di atomica, mutex e variabili di condizione come trivially copyable in C++ 14. Apprezzo che una correzione sia already lined up, ma std::mutex, std::condition variable et al. sembrano avere distruttori non banali. Per esempio:Perché i mutex e le variabili di condizione sono facilmente copiabili?

30.4.1.2.1 Classe mutex [thread.mutex.class]

namespace std { 
    class mutex { 
    public: 
    constexpr mutex() noexcept; 
    ~mutex(); // user-provided => non-trivial 

    … 
    } 
} 

Non dovrebbe questo li squalificare come banalmente copiabile?

+2

Sembra che questo sia solo qualcosa che Howard ha detto durante la discussione, probabilmente senza pensarci su? – SergeyA

+0

Sei sicuro che 'std :: mutex' è banalmente copiabile? Per lo standard che copiano il costruttore e l'assegnazione della copia sono entrambi contrassegnati come 'delete'. – NathanOliver

+4

@NathanOliver Questo è il punto di LWG 1734. (Anche se hai ragione - il 'mutex' non è banalmente riproducibile.) Il suo distruttore non è banale.) – Columbo

risposta

6

O è stato un mio errore, o sono stato citato in modo errato, e onestamente non ricordo quale.

Tuttavia, ho questo consiglio molto ferma posizione sul tema:

Non utilizzare is_trivialis_trivially_copyable! MAI!!!

Anziché utilizzare uno di questi:

is_trivially_destructible<T> 
is_trivially_default_constructible<T> 
is_trivially_copy_constructible<T> 
is_trivially_copy_assignable<T> 
is_trivially_move_constructible<T> 
is_trivially_move_assignable<T> 

Motivazione:

TLDR: Vedere questo excellent question and correct answer.

Nessuno (me compreso) può ricordare la definizione di is_trivial e is_trivially_copyable . E se ti capita di cercarlo, e poi impiegarlo per 10 minuti ad analizzarlo, può o non può fare ciò che pensi intuitivamente che faccia. E se riesci ad analizzarlo correttamente, CWG potrebbe cambiare la sua definizione con poca o nessuna notifica e invalidare il tuo codice.

Utilizzare is_trivial e is_trivially_copyable sta giocando con il fuoco.

Tuttavia questi:

is_trivially_destructible<T> 
is_trivially_default_constructible<T> 
is_trivially_copy_constructible<T> 
is_trivially_copy_assignable<T> 
is_trivially_move_constructible<T> 
is_trivially_move_assignable<T> 

fanno esattamente quello che suonano come fanno, e non è probabile che mai hanno cambiato la loro definizione. Può sembrare eccessivamente prolisso avere a che fare con ciascuno dei membri speciali individualmente. Ma ripagherà nella stabilità/affidabilità del tuo codice. E se è necessario, impacchetta questi tratti individuali in un tratto personalizzato.

Aggiornamento

Per esempio, clang & gcc compilare questo programma:

#include <type_traits> 

template <class T> 
void 
test() 
{ 
    using namespace std; 
    static_assert(!is_trivial<T>{}, ""); 
    static_assert(is_trivially_copyable<T>{}, ""); 
    static_assert(is_trivially_destructible<T>{}, ""); 
    static_assert(is_destructible<T>{}, ""); 
    static_assert(!is_trivially_default_constructible<T>{}, ""); 
    static_assert(!is_trivially_copy_constructible<T>{}, ""); 
    static_assert(is_trivially_copy_assignable<T>{}, ""); 
    static_assert(!is_trivially_move_constructible<T>{}, ""); 
    static_assert(is_trivially_move_assignable<T>{}, ""); 
} 

struct X 
{ 
    X(const X&) = delete; 
}; 

int 
main() 
{ 
    test<X>(); 
} 

noti che Xè banalmente copiabile, ma non banalmente copiare costruibile. Per quanto ne so, questo è un comportamento conforme.

VS-2015 attualmente dice che X è banalmente copiabile né banalmente copiabile. Credo che questo sia sbagliato secondo le specifiche attuali, ma sicuramente corrisponde a ciò che mi dice il mio buon senso.

Se avevo bisogno di memcpy-inizializzato memoria, mi fiderei is_trivially_copy_constructible sopra is_trivially_copyable mi assicurare che una tale operazione sarebbe stato ok. Se volevo memcpy a inizializzato memoria, vorrei controllare is_trivially_copy_assignable.

+0

Dalla bocca del cavallo! Grazie per il consiglio. Non mi rendevo conto che quei tratti erano in C++ 17. Giusto per essere chiari, consideri lo standard che richiede un distruttore fornito dall'utente nel caso di 'std :: mutex' e compagnia? Chiedo perché le altre risposte non sembrano pensare che sia così. –

+1

Sì, ma questa è un'area sfocata nello standard. La specifica mutex non è stata progettata pensando alle regole "banali". Tale tratto può cambiare. Ma in sintesi, non penso a quello che le specifiche dicono riguardo al fatto che il mutex sia banalmente copiato importante, perché la definizione di "banalmente copiabile" è sia bizzarra che soggetta a cambiamenti. Più importanti sono domande come: Può il mutex essere copiato banalmente? È possibile assegnare banalmente il mutex? Anche se la risposta non è portabile, queste ultime domande possono essere richieste in modo portabile e risposte chiare, con conseguente codice portatile. –

+0

Che cosa è utile per 'is_trivially_copy_constructible'? Di per sé non garantisce copie per byte. In effetti, consigliare 'is_trivially_copy_constructible' invece di' is_trivially_copyable' può essere pericoloso, perché le persone potrebbero (fittamente) dedurre che 'memcpy'ing va bene se il costruttore di copie è banale. – Columbo

3

È importante guardare questo da un punto di vista di un avvocato di lingua.

È praticamente impossibile per un'implementazione implementare mutex, variabili di condizione e simili in un modo che li lasci banalmente copiabili. Ad un certo punto, devi scrivere un distruttore e molto probabilmente quel distruttore dovrà fare un lavoro non banale.

Ma non importa. Perché? Perché lo standard non afferma esplicitamente che tali tipi non saranno banalmente copiabili. E quindi, dal punto di vista dello standard, teoricamente possibile per tali oggetti essere banalmente copiabili.

Anche se nessuna implementazione funzionale può essere, il punto di N4460 è di rendere molto chiaro che tali tipi non saranno mai banalmente copiabili.

+0

Non sono sicuro che abbiano effettivamente preso l'appellativo corretto qui. A mio avviso, l'approccio corretto sarebbe quello di considerare i tipi con operatori di assegnazione cancellati/fotocopiatori come ** non ** - banalmente copiabili.Sì, rompe qualche retrocompatibilità, ma è la ** cosa giusta **. Se qualcosa non può essere copiato con 'operator =', perché è legale copiarlo con 'memcpy()'? – SergeyA

+0

Ma lo standard non specifica un distruttore fornito dall'utente per questi tipi? –

+1

@JosephThomson: specifica che esiste un distruttore; il tipo deve essere 'Destructibe'. Ma nulla nello standard richiede che il distruttore sia non banale. Di nuovo, pensa come un avvocato linguistico; lì "non c'è una regola", quindi è possibile. –

4

Non tutte le implementazioni forniscono un distruttore non banale per mutex. Vedi libstdC++ (e supponiamo che __GTHREAD_MUTEX_INIT è stato definito):

// Common base class for std::mutex and std::timed_mutex 
    class __mutex_base 
    { 
    // […] 
#ifdef __GTHREAD_MUTEX_INIT 
    __native_type _M_mutex = __GTHREAD_MUTEX_INIT; 

    constexpr __mutex_base() noexcept = default; 
#else 
    // […] 

    ~__mutex_base() noexcept { __gthread_mutex_destroy(&_M_mutex); } 
#endif 
    // […] 
    }; 

    /// The standard mutex type. 
    class mutex : private __mutex_base 
    { 
    // […] 
    mutex() noexcept = default; 
    ~mutex() = default; 

    mutex(const mutex&) = delete; 
    mutex& operator=(const mutex&) = delete; 
    }; 

Questa implementazione mutex è sia conforme standard banalmente copiabile (che può essere verificata via Coliru). Allo stesso modo, nulla impedisce a un'implementazione di mantenere condition_variable banalmente distruttibile (vedere [thread.condition.condvar]/6, sebbene non sia stato possibile trovare un'implementazione valida).

La linea di fondo è che abbiamo bisogno di chiare garanzie normative e interpretazioni non intelligenti e sottili di ciò che condition_variable fa o non deve fare (e come deve farlo).

+0

Ho chiesto la stessa domanda nell'altra risposta, ma la presenza di '~ mutex();' nello standard specifica che il distruttore non deve essere impostato come predefinito, o è solo un esempio di ciò che l'interfaccia di classe potrebbe Assomiglia a? –

+0

Ho modificato la domanda per dimostrare la radice del mio possibile equivoco. –

+0

@JosephThomson No, non è implicito. Vedi [\ [functions.within.classes \]/1] (http://eel.is/c++draft/functions.within.classes#1). – Columbo

Problemi correlati