2010-08-11 7 views
12

Sto cercando la migliore pratica di gestione degli oggetti non copiabili.Come gestire gli oggetti non copiabili quando si inseriscono nei contenitori in C++

Ho una classe mutex, che ovviamente non dovrebbe essere copiabile. Ho aggiunto un costruttore di copia privata per applicarlo.

Che ha rotto il codice: alcuni punti dovevano semplicemente essere corretti, ma ho un problema generico in cui una classe, che utilizza il mutex come membro dati o per ereditarietà, viene inserita in un contenitore.

Questo di solito accade durante l'inizializzazione del contenitore, quindi il mutex non è ancora inizializzato, ed è quindi ok, ma senza un costruttore di copia non funziona. La modifica dei contenitori per contenere i puntatori non è accettabile.

Qualche consiglio?

+5

Le domande che dicono che "X non è accettabile" non sono accettabili. –

+1

Eventuali puntatori o solo puntatori grezzi? Che ne dici di shared_ptrs? – SCFrench

risposta

4

Se un oggetto non è copiabile, di solito c'è una buona ragione. E se c'è una buona ragione, non devi sovvertirla inserendola in un contenitore che tenta di copiarla.

+5

Questo è un buon consiglio generale, quindi non sto facendo downvoting. Ma è anche una non risposta, quindi sono fortemente tentato di farlo. – Omnifarious

+5

In azione, è una risposta. Quello che il DBBD ha chiesto è sbagliato. Visage gli dice cosa non va: rivendicare oggetti non è copiabili ma usarli in un contenitore che li copia. Non ha senso Inoltre, DBBD non dice nemmeno perché usare i puntatori non è accettabile, mentre è il modo in cui dovrebbe essere, (molto probabilmente). –

+1

@Onniversario: alcune persone fanno solo domande sbagliate. Visage è IMHO giusto. – wilx

2

I contenitori STL fanno molto affidamento sul fatto che il loro contenuto possa essere copiato, quindi o renderli copiabili o non metterli in un contenitore.

4

Se non sono copiabili, il contenitore deve memorizzare puntatori (intelligenti) a tali oggetti, o wrapper di riferimento, ecc. Sebbene con C++ 0x, gli oggetti non copiabili possono essere ancora mobili (come i thread di boost), quindi che possono essere conservati in contenitori così come sono.

invia esempi: involucro riferimento (alias boost :: ref, un puntatore sotto il cofano)

#include <vector> 
#include <tr1/functional> 
struct Noncopy { 
private: 
     Noncopy(const Noncopy&) {} 
public: 
     Noncopy() {} 
}; 
int main() 
{ 
     std::vector<std::tr1::reference_wrapper<Noncopy> > v; 
     Noncopy m; 
     v.push_back(std::tr1::reference_wrapper<Noncopy>(m)); 
} 

C++ 0x, controllato con gcc:

#include <vector> 
struct Movable { 
private: 
     Movable(const Movable&) = delete; 
public: 
     Movable() {} 
     Movable(Movable&&) {} 
}; 
int main() 
{ 
     std::vector<Movable> v; 
     Movable m; 
     v.emplace_back(std::move(m)); 
} 

EDIT: Nevermind, C++ 0x FCD dice, sotto 30.4.1/3,

Un tipo di Mutex non può essere copiato né mobile.

Quindi stai meglio con i puntatori a loro. Intelligente o altrimenti avvolto secondo necessità.

+0

Per la maggior parte dei contenitori C++ 0x, gli oggetti non devono nemmeno essere mobili; 'emplace' può costruirli sul posto. Devono essere mobili per 'vector' e' deque', poiché questi devono ridistribuire la memoria man mano che crescono. –

+1

Inoltre, il metodo C++ 0x accettato di rimuovere il costruttore di copie è 'Moveable (const Movable &) = delete;'. – Omnifarious

+0

Questo è davvero interessante, ma ho il sospetto che se un mutex viene spostato mentre è trattenuto, qualcosa di veramente brutto potrebbe accadere con alcune implementazioni. – Omnifarious

1

L'opzione migliore è utilizzare i puntatori o un tipo di classe wrapper che utilizza i puntatori sotto il cofano. Ciò consentirebbe che questi vengano copiati in modo corretto e che effettivamente facciano ciò che una copia dovrebbe fare (condividi il mutex).

Ma, dal momento che non hai detto alcun suggerimento, c'è un'altra opzione. Sembra che i Mutex siano "a volte copiabili", forse dovresti scrivere un costruttore di copie e un operatore di assegnazione e chiedere loro di lanciare un'eccezione se un mutex viene mai copiato dopo essere stato inizializzato. Il lato negativo è che non c'è modo di sapere che lo stai facendo male fino al runtime.

11

Tre soluzioni qui:

1. utilizzare i puntatori - La soluzione rapida è quello di renderlo un contenitore di puntatori - per esempio a shared_ptr.

Questa sarebbe la soluzione "buona" se i tuoi oggetti non sono veramente realizzabili e non è possibile utilizzare altri contenitori.

2. Altri contenitori - In alternativa, è possibile utilizzare contenitori non di copia (che utilizzano la costruzione sul posto), tuttavia non sono molto comuni e ampiamente incompatibili con STL. (Ho provato per un po ', ma semplicemente non va bene)

Questa sarebbe la soluzione "dio" se i tuoi oggetti sono veramente non calcolabili e l'uso di puntatori non è possibile.

[modifica] Con C++ 13, std :: vector consente la costruzione inplace (emplace_back) e può essere utilizzato per oggetti non copiabili che implementano la semantica del movimento. [/ modifica]

3. risolvere il tuo copyability - Se la classe è copiabile in quanto tale, e il mutex non è, è "semplicemente" bisogno di fissare il costruttore di copia e operatore di assegnamento.

scriverle è un dolore, dal momento che di solito si devono copiare & assegnare tutti i membri tranne il mutex, ma che spesso può essere semplificata:

template <typename TNonCopyable> 
struct NeverCopy : public T 
{ 
    NeverCopy() {} 
    NeverCopy(T const & rhs) {} 

    NeverCopy<T> & operator=(T const & rhs) { return *this; } 
} 

E si cambia membro mutex per

NeverCopy<Mutex> m_mutex; 

Sfortunatamente, usando quel modello perdi costruttori speciali di Mutex.

[modifica] Avviso: "Correzione" della copia CTor/assegnazione richiede spesso di bloccare il lato destro sul costrutto di copia e bloccare entrambi i lati sull'assegnazione. Sfortunatamente, non c'è modo di ignorare il codice copia/assegnazione e chiamare l'implementazione predefinita, quindi il trucco NeverCopy potrebbe non funzionare per te senza blocco esterno. (Esistono altre soluzioni alternative con i propri limiti.)

0

L'utilizzo di un mutex in una classe non significa necessariamente che la classe debba essere non copiabile. È possibile (quasi) sempre implementare in questo modo:

C::C (C const & c) 
// No ctor-initializer here. 
{ 
    MutexLock guard (c.mutex); 

    // Do the copy-construction here. 
    x = c.x; 
} 

Anche se questo rende un po 'possibile copiare le classi con mutex, probabilmente non dovrebbe farlo. È probabile che il tuo design sia migliore senza mutex peristanza.

1

Usa puntatori intelligenti come boost :: shared_ptr o usa un altro contenitore, come boost :: intrusive. Entrambi richiedono di modificare il codice, tramite.

2

Non esiste una risposta reale alla domanda dato come l'hai inquadrata. Non c'è modo di fare quello che vuoi. La risposta effettiva è che il contenitore contenga puntatori e hai detto che non va bene per qualche ragione non specificata.

Alcuni hanno parlato di cose che sono mobili e utilizzano C++ 0x in cui i container spesso richiedono che i loro elementi siano mobili, ma non richiedono che siano copiabili. Penso che anche questa sia una soluzione scadente, perché sospetto che i mutex non debbano essere spostati mentre sono trattenuti, e questo rende effettivamente impossibile spostarli.

Quindi, l'unica vera risposta rimanente è puntare i mutex. Utilizzare ::std::tr1::shared_ptr (in #include <tr1/memory>) o ::boost::shared_ptr per indicare i mutex. Ciò richiede la modifica delle definizioni delle classi che contengono i mutex, ma sembra che lo stiate facendo comunque.

+0

Lo spostamento di un oggetto come un mutex è una questione di spostamento di alcuni puntatori forniti dal sistema operativo, niente Di Più. Non è possibile spostare un mutex effettivo, ma è banale spostare l'handle fornito dal sistema operativo su di esso. – Puppy

+0

@DeadMG - Se il mutex è trattenuto o conteso, probabilmente significa che ci sono indicatori "this" che puntano alla posizione dell'oggetto originale. Inoltre, è possibile che il sistema operativo identifichi il mutex tramite il suo indirizzo di memoria. – Omnifarious

0

Utilizzo di C++ 11 su Ubuntu 14.04 (che include emplace_back), ho ottenuto questo per funzionare.

ho scoperto che emplace_back funzionava bene, ma cancellazione (e probabilmente inserto) non ha funzionato perché, quando il vettore è stato mescolando gli elementi insieme per colmare il divario, si mas utilizzando:

*previous = *current; 

ho trovato il trucco stava permettendo l'assegnazione mossa nella mia classe di risorse:

Watch& operator=(Watch &&other); 

Questo è il mio inotify_watch classe, che può vivere in un std :: vector:

class Watch { 
private: 
    int inotify_handle = 0; 
    int handle = -1; 
    // Erases all knowledge of our resources 
    void erase() { 
    inotify_handle = 0; 
    handle = -1; 
    } 
public: 
    Watch(int inotify_handle, const char *path, uint32_t mask) 
     : inotify_handle(inotify_handle), 
     handle(inotify_add_watch(inotify_handle, path, mask)) { 
    if (handle == -1) 
     throw std::system_error(errno, std::system_category()); 
    } 
    Watch(const Watch& other) = delete; // Can't copy it, it's a real resource 
    // Move is fine 
    Watch(Watch &&other) 
     : inotify_handle(other.inotify_handle), handle(other.handle) { 
    other.erase(); // Make the other one forget about our resources, so that 
        // when the destructor is called, it won't try to free them, 
        // as we own them now 
    } 
    // Move assignment is fine 
    Watch &operator=(Watch &&other) { 
    inotify_handle = other.inotify_handle; 
    handle = other.handle; 
    other.erase(); // Make the other one forget about our resources, so that 
        // when the destructor is called, it won't try to free them, 
        // as we own them now 
    return *this; 
    } 
    bool operator ==(const Watch& other) { 
    return (inotify_handle == other.inotify_handle) && (handle == other.handle); 
    } 
    ~Watch() { 
    if (handle != -1) { 
     int result = inotify_rm_watch(inotify_handle, handle); 
     if (result == -1) 
     throw std::system_error(errno, std::system_category()); 
    } 
    } 
};