我应该删除智能指针的移动构造函数和移动赋值吗



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

我知道我可以实现移动语义,但我认为这没有意义,因为复制智能指针非常便宜。特别是考虑到它引入了产生讨厌bug的机会。

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

指南

从不删除特殊移动成员。

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

  1. 如果你有一个可复制的类,并且你不想移动成员,那么就不要声明它们(包括不删除它们)。已删除的成员仍在声明中。已删除的成员参与过载解决。没有出席的议员没有出席。当创建具有有效复制构造函数和已删除移动成员的类时,不能通过函数的值返回,因为重载解析将绑定到已删除的移动成员。

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

如果您声明已删除的move成员,即使您碰巧选择了冗余且不正确的案例,每次有人阅读您的代码时,他们都需要重新发现您的案例是冗余还是错误。让代码的读者更容易,永远不要删除移动成员。

错误的情况:

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

以下是对这种声明风格的更深入的解释。

最新更新