2016-05-07 19 views
16

Sto implementando un semplice puntatore intelligente, che in pratica tiene traccia del numero di riferimenti a un puntatore che gestisce.Devo cancellare il costruttore di movimento e l'assegnazione di spostamento di un puntatore intelligente?

So che I potrebbe implementare la semantica del movimento, ma non penso abbia senso poiché copiare un puntatore intelligente è molto economico. Soprattutto se si considera che introduce opportunità per produrre cattivi bug.

Ecco il mio codice C++ 11 (ho omesso qualche codice inessenziale). Anche i commenti generali sono benvenuti.

#ifndef SMART_PTR_H_ 
#define SMART_PTR_H_ 

#include <cstdint> 

template<typename T> 
class SmartPtr { 
private: 
    struct Ptr { 
     T* p_; 
     uint64_t count_; 
     Ptr(T* p) : p_{p}, count_{1} {} 
     ~Ptr() { delete p_; } 
    }; 
public: 
    SmartPtr(T* p) : ptr_{new Ptr{p}} {} 
    ~SmartPtr(); 

    SmartPtr(const SmartPtr<T>& rhs); 
    SmartPtr(SmartPtr<T>&& rhs) =delete; 

    SmartPtr<T>& operator=(const SmartPtr<T>& rhs); 
    SmartPtr<T>& operator=(SmartPtr<T>&& rhs) =delete; 

    T& operator*() { return *ptr_->p_; } 
    T* operator->() { return ptr_->p_; } 

    uint64_t Count() const { return ptr_->count_; } 

    const T* Raw() const { return ptr_->p_; } 
private: 
    Ptr* ptr_; 
}; 

template<typename T> 
SmartPtr<T>::~SmartPtr() { 
    if (!--ptr_->count_) { 
     delete ptr_; 
    } 
    ptr_ = nullptr; 
} 

template<typename T> 
SmartPtr<T>::SmartPtr(const SmartPtr<T>& rhs) : ptr_{rhs.ptr_} { 
    ++ptr_->count_; 
} 

template<typename T> 
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<T>& rhs) { 
    if (this != &rhs) { 
     if (!--ptr_->count_) { 
      delete ptr_; 
     } 
     ptr_ = rhs.ptr_; 
     ++ptr_->count_; 
    } 
    return *this; 
} 

#endif // SMART_PTR_H_ 
+3

No. Se si vuole che tutto sia una copia, allora non dichiarare le componenti di movimento; non definirli come cancellati. http://stackoverflow.com/questions/26489837/why-do-deleted-move-semantics-cause-problems-with-stdvector –

+0

Hai esattamente ragione su 'operator *'! Yikes! Corretto. – blazs

+3

'std :: shared_ptr' ha operazioni di spostamento personalizzate perché il meccanismo di conteggio dei riferimenti sottostante è sicuro dalla concorrenza. Le operazioni di spostamento personalizzate possono quindi evitare l'incremento atomico. – dyp

risposta

43

Guideline

Mai eliminare i membri mossa speciale.

Nel codice tipico (come nella domanda), vi sono due motivi per eliminare i membri di spostamento. Una di queste motivazioni produce codice errato (come nel tuo esempio), e per l'altra motivazione la cancellazione dei membri del movimento è ridondante (non fa male né bene).

  1. Se si dispone di una classe copiabile e non si desidera spostare membri, semplicemente non li dichiarano (che non comprende l'eliminazione di loro). I membri eliminati sono ancora dichiarati. I membri eliminati partecipano alla risoluzione di sovraccarico. I membri non presenti non lo fanno. Quando si crea una classe con un costruttore di copie valido e un membro di spostamento eliminato, non è possibile restituirlo per valore da una funzione poiché la risoluzione di sovraccarico si associa al membro di spostamento eliminato.

  2. A volte le persone vogliono dire: questa classe non è né mobile né copiabile. È corretto cancellare sia la copia che i membri del movimento. Tuttavia è sufficiente eliminare i membri della copia (purché i membri del movimento non siano dichiarati). I membri della copia dichiarati (anche cancellati) inibiscono il dal compilatore di dichiarare i membri del movimento. Quindi in questo caso i membri delle mosse eliminate sono semplicemente ridondanti.

Se si dichiarano membri mossa cancellati, anche se vi capita di prendere il caso in cui esso è ridondante e non corretto, ogni volta che qualcuno legge il codice, hanno bisogno di riscoprire se il tuo caso è ridondante o non corretti . Rendi più facile l'accesso ai lettori del tuo codice e non eliminare mai i membri del movimento.

Il caso non corretto:

struct CopyableButNotMovble 
{ 
    // ... 
    CopyableButNotMovble(const CopyableButNotMovble&); 
    CopyableButNotMovble& operator=(const CopyableButNotMovble&); 
    CopyableButNotMovble(CopyableButNotMovble&&) = delete; 
    CopyableButNotMovble& operator=(CopyableButNotMovble&&) = delete; 
    // ... 
}; 

Ecco esempio di codice che probabilmente aspetta di lavorare con CopyableButNotMovble ma avrà esito negativo in fase di compilazione:

CopyableButNotMovble 
get() 
{ 
    CopyableButNotMovble x; 
    return x; 
} 

int 
main() 
{ 
    CopyableButNotMovble x = get(); 
} 

test.cpp:22:26: error: call to deleted constructor of 'CopyableButNotMovble' 
    CopyableButNotMovble x = get(); 
         ^ ~~~~~ 
test.cpp:7:5: note: 'CopyableButNotMovble' has been explicitly marked deleted here 
    CopyableButNotMovble(CopyableButNotMovble&&) = delete; 
    ^
1 error generated. 

Il modo corretto per farlo è:

struct CopyableButNotMovble 
{ 
    // ... 
    CopyableButNotMovble(const CopyableButNotMovble&); 
    CopyableButNotMovble& operator=(const CopyableButNotMovble&); 
    // ... 
}; 

Il caso ridondante:

struct NeitherCopyableNorMovble 
{ 
    // ... 
    NeitherCopyableNorMovble(const NeitherCopyableNorMovble&) = delete; 
    NeitherCopyableNorMovble& operator=(const NeitherCopyableNorMovble&) = delete; 
    NeitherCopyableNorMovble(NeitherCopyableNorMovble&&) = delete; 
    NeitherCopyableNorMovble& operator=(NeitherCopyableNorMovble&&) = delete; 
    // ... 
}; 

Il modo più leggibile per farlo è:

struct NeitherCopyableNorMovble 
{ 
    // ... 
    NeitherCopyableNorMovble(const NeitherCopyableNorMovble&) = delete; 
    NeitherCopyableNorMovble& operator=(const NeitherCopyableNorMovble&) = delete; 
    // ... 
}; 

Aiuta se si effettua una pratica di raggruppare sempre tutti e 6 i membri del speciali vicino alla parte superiore del vostro dichiarazione di classe, nello stesso ordine, saltando quelli che non vuoi dichiarare. Questa pratica rende più facile per i lettori del tuo codice determinare rapidamente che non hai intenzionalmente dichiarato alcun particolare membro speciale.

Per esempio, ecco il modello che segue:

class X 
{ 
    // data members: 

public: 
    // special members 
    ~X(); 
    X(); 
    X(const X&); 
    X& operator=(const X&); 
    X(X&&); 
    X& operator=(X&&); 

    // Constructors 
    // ... 
}; 
+0

Non vedo "produce codice errato (come nel tuo esempio)". Cosa non è corretto nel codice? Devo ignorare questa spiegazione specifica qui? Ti riferisci a "= delete non significa" non usare me, ma usa il prossimo migliore ". Significa piuttosto" non usare me quando hai bisogno di me - invece di essere da solo in libertà. "" Da http : //stackoverflow.com/a/19266022/472245 o qualcos'altro? – towi

+0

@towi: ho aggiunto un esempio per chiarire. Grazie per averlo indicato. –

+0

@HowardHinnant "Quando si crea una classe con un costruttore di copie valido e un membro di spostamento eliminato, non è possibile restituirlo per valore da una funzione poiché la risoluzione di sovraccarico si associa al membro di spostamento eliminato." Non ne avevo idea. Grazie! – Patryk