我应该删除移动构造函数和智能指针的移动分配吗?

时间:2016-05-07 19:33:42

标签: c++ c++11 smart-pointers move-semantics

我正在实现一个简单的智能指针,它基本上跟踪它处理的指针的引用数。

我知道我可以实现移动语义,但我不认为复制智能指针非常便宜。特别是考虑到它引入了产生令人讨厌的错误的机会。

这是我的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_

1 个答案:

答案 0 :(得分:50)

<强>准则

从不删除特殊移动成员。

在典型代码中(例如在您的问题中),删除移动成员有两种动机。其中一个动机会产生错误的代码(如您的示例所示),而另一个动机是删除移动成员是多余的(没有伤害也没有好处)。

  1. 如果你有一个可复制的课程并且你不想要移动成员,那么就不要声明它们(包括不删除它们)。删除的成员仍然被声明。已删除的成员参与重载解析。不在场的会员不要参加。当您使用有效的复制构造函数和已删除的移动成员创建类时,您无法通过函数的值返回它,因为重载决策将绑定到已删除的移动成员。

  2. 有时人们想说:这个班既不可移动也不可复制。删除副本和移动成员是正确的。但是,只删除复制成员就足够了(只要未声明移动成员)。声明(甚至删除)复制成员禁止编译器声明移动成员。因此,在这种情况下,删除的移动成员只是多余的。

  3. 如果您声明已删除的移动成员,即使您碰巧选择了冗余且不正确的情况,每次有人读取您的代码时,如果您的情况多余或不正确,他们需要重新发现。让代码的读者更容易,不要删除移动成员。

    不正确的情况:

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

    以下是您可能期望使用CopyableButNotMovble但在编译时失败的示例代码:

    #include <algorithm>
    #include <vector>
    
    struct CopyableButNotMovble
    {
        // ...
        CopyableButNotMovble(const CopyableButNotMovble&);
        CopyableButNotMovble& operator=(const CopyableButNotMovble&);
        CopyableButNotMovble(CopyableButNotMovble&&) = delete;
        CopyableButNotMovble& operator=(CopyableButNotMovble&&) = delete;
    
        CopyableButNotMovble(int);
        // ...
        friend bool operator<(CopyableButNotMovble const& x, CopyableButNotMovble const& y); 
    };
    
    int
    main()
    {
        std::vector<CopyableButNotMovble> v{3, 2, 1};
        std::sort(v.begin(), v.end());
    }
    
    In file included from test.cpp:1:
    algorithm:3932:17: error: no
          matching function for call to 'swap'
                    swap(*__first, *__last);
                    ^~~~
    algorithm:4117:5: note: in
          instantiation of function template specialization 'std::__1::__sort<std::__1::__less<CopyableButNotMovble,
          CopyableButNotMovble> &, CopyableButNotMovble *>' requested here
        __sort<_Comp_ref>(__first, __last, __comp);
        ^
    algorithm:4126:12: note: in
          instantiation of function template specialization 'std::__1::sort<CopyableButNotMovble *,
          std::__1::__less<CopyableButNotMovble, CopyableButNotMovble> >' requested here
        _VSTD::sort(__first, __last, __less<typename iterator_traits<_RandomAccessIterator>::value_type>());
               ^
    ...
    

    (来自std :: lib内部的许多令人讨厌的错误消息)

    正确的方法是:

    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
        // ...
    };