2016-05-07 13 views
16

私はシンプルなスマートポインタを実装しています。これは、基本的にそれが扱うポインタへの参照の数を追跡します。移動コンストラクタとスマートポインタの移動割り当てを削除する必要がありますか?

私が知っているは移動のセマンティクスを実装することができますが、スマートなポインタをコピーすることは非常に安価であるとは思いません。特に、それは厄介なバグを生み出す機会をもたらしていると考えています。

ここに私のC++ 11のコードがあります(私はいくつかの本質的なコードを省略しました)。一般的なコメントも歓迎します。

#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

いいえあなたはすべてをコピーしたい場合は、移動のメンバを宣言していません。それらを削除済みとして定義しないでください。 http://stackoverflow.com/questions/26489837/why-do-deleted-move-semantics-cause-problems-with-stdvector –

+0

あなたは 'operator *'について正確に正しいです! Yikes!修正されました。 – blazs

+3

'std :: shared_ptr'には、基本的な参照カウントメカニズムが並行性安全であるため、カスタム移動操作があります。したがって、カスタムの移動操作では、原子の増分を避けることができます。 – dyp

答えて

43

ガイドライン

特殊な移動メンバーを削除しないでください。

通常のコード(質問など)では、移動メンバーを削除する動機が2つあります。これらの動機の1つは、(あなたの例のように)間違ったコードを生成し、他の動機のために、移動メンバの削除は冗長です(害でも良いことでもありません)。

  1. コピー可能なクラスがあり、メンバーを移動したくない場合は、メンバーを削除しないことを含めて宣言しないでください。削除されたメンバーはまだ宣言されています。削除されたメンバーは過負荷解決に参加します。存在しないメンバーはそうしない。有効なコピーコンストラクタと削除された移動メンバを持つクラスを作成すると、オーバーロード解決が削除された移動メンバにバインドされるため、関数から値を返すことはできません。

  2. 時々、このクラスは移動可能でもコピー可能でもありません。コピーメンバーと移動メンバーの両方を削除するのは正しいです。ただし、コピーメンバーを削除するだけで十分です(移動メンバーが宣言されていない限り)。宣言された(削除された)コピーメンバコンパイラが移動メンバを宣言することを禁止します。この場合、削除された移動メンバーは単に重複しています。

あなたはそれが間違って冗長でない場合を選ぶことが起こる場合でも、毎回誰かがあなたのコードを読み取って、削除された移動メンバを宣言した場合、彼らはあなたのケースが冗長または正しくない場合、再発見する必要があります。コードを読んだ方が簡単になり、移動メンバーは決して削除されません。

正しくない場合:

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. 

これを行うための正しい方法は次のとおりです。ここで

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

あなたはおそらくCopyableButNotMovbleで動作するように期待されるが、コンパイル時に失敗するサンプルコードです:

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

冗長ケース:

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

これを行うために、より読みやすい方法は次のとおりです。

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

あなたは常にあなたの上部付近にあなたの特別なメンバーの全6をグループ化する練習をする場合には役立ちますあなたが宣言したくないものをスキップして、同じ順序でクラス宣言を行います。この習慣により、コードの読者は、特定の特別なメンバーを意図的に宣言していないことを迅速に判断することができます。

たとえば、ここに私が従うパターンは次のとおりです。

class X 
{ 
    // data members: 

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

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

「あなたの例のように間違ったコードが生成されています」とは表示されません。コードで何が間違っていますか?その具体的な説明はここで見落としていますか?あなたは "=削除は"私を使わず、代わりに次のベストを使う "という意味ではなく、"あなたが私を必要とするときに私を使わないでください。 ://stackoverflow.com/a/19266022/472245または他に何か? – towi

+0

@towi:明確にするための例を追加しました。これを指摘してくれてありがとう。 –

+0

@ HowardHinnant "有効なコピーコンストラクタと削除された移動メンバでクラスを作成すると、オーバーロード解決が削除された移動メンバにバインドされるため、関数から値を返すことはできません。私はこれについて全く知らなかった。ありがとう! – Patryk

関連する問題