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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我应该删除移动构造函数和智能指针的移动分配吗?相关的知识,希望对你有一定的参考价值。

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

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

这是我的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. 如果你有一个可复制的类而你不想要移动成员,那么就不要声明它们(包括不删除它们)。删除的成员仍然被声明。已删除的成员参与重载解析。不在场的成员不会。当您使用有效的复制构造函数和已删除的移动成员创建类时,您无法通过函数的值返回它,因为重载决策将绑定到已删除的移动成员。
  2. 有时人们想说:这个类既不可移动也不可复制。删除副本和移动成员是正确的。但是,只删除复制成员就足够了(只要未声明移动成员)。声明(甚至删除)复制成员禁止编译器声明移动成员。因此,在这种情况下,删除的移动成员只是多余的。

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

不正确的情况:

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

以上是关于我应该删除移动构造函数和智能指针的移动分配吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何允许移动构造并禁止分配和复制类的构造

智能指针的原理和简单实现

C++ 浅析智能指针

移动智能指针两次与复制

C++的RAII和智能指针小结

智能指针的原理及其应用