我应该删除智能指针的移动构造函数和移动分配吗? [英] Should I delete the move constructor and the move assignment of a smart pointer?

查看:107
本文介绍了我应该删除智能指针的移动构造函数和移动分配吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在实现一个简单的智能指针,基本上可以跟踪对其处理的指针的引用数量.

I'm implementing a simple smart pointer, which basically keeps track of the number of references to a pointer that it handles.

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

I know I could implement move semantics, but I don't think it makes sense as copying a smart pointer is very cheap. Especially considering that it introduces opportunities to produce nasty bugs.

这是我的C ++ 11代码(我省略了一些无关紧要的代码).也欢迎提出一般评论.

Here's my C++11 code (I omitted some inessential code). General comments are welcome as well.

#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成员是多余的(既无害也无益).

In typical code (such as in your question), there are two motivations to delete the move members. One of those motivations produces incorrect code (as in your example), and for the other motivation the deletion of the move members is redundant (does no harm nor good).

  1. 如果您有一个可复制的类,并且您不希望移动成员,则只需不声明它们(包括不删除它们).仍然声明已删除的成员.删除的成员参与重载解决方案.不在场的成员不参加.当您使用有效的副本构造函数和已删除的move成员创建一个类时,由于重载解析将绑定到已删除的move成员,因此无法通过函数的值返回它.

  1. If you have a copyable class and you don't want move members, simply don't declare them (which includes not deleting them). Deleted members are still declared. Deleted members participate in overload resolution. Members not present don't. When you create a class with a valid copy constructor and a deleted move member, you can't return it by value from a function because overload resolution will bind to the deleted move member.

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

Sometimes people want to say: this class is neither movable nor copyable. It is correct to delete both the copy and the move members. However just deleting the copy members is sufficient (as long as the move members are not declared). Declared (even deleted) copy members inhibit the compiler from declaring move members. So in this case the deleted move members are simply redundant.

如果您声明已删除的Move成员,即使您碰巧选择了多余且并非不正确的情况,每次有人读取您的代码时,他们都需要重新发现您的情况是否多余或不正确.使您的代码阅读者更轻松,并且永远不会删除move成员.

If you declare deleted move members, even if you happen to pick the case where it is redundant and not incorrect, every time someone reads your code, they need to re-discover if your case is redundant or incorrect. Make it easier on readers of your code and never delete the move members.

不正确的情况:

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

以下是您可能希望与CopyableButNotMovble一起使用的示例代码,但在编译时将失败:

Here is example code you probably expected to work with CopyableButNotMovble but will fail at compile time:

#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内部的许多讨厌的错误消息)

(many nasty error messages from deep inside your std::lib)

执行此操作的正确方法是:

The correct way to do this is:

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个特殊成员分组在类声明的顶部附近,并且以相同的顺序跳过不想要声明的成员,则它会有所帮助.通过这种做法,代码阅读者可以更轻松地快速确定您是否有意未声明任何特定的特殊成员.

It helps if you make a practice of always grouping all 6 of your special members near the top of your class declaration, in the same order, skipping those you don't want to declare. This practice makes it easier for readers of your code to quickly determine that you have intentionally not declared any particular special member.

例如,这是我遵循的模式:

For example, here is the pattern I follow:

class X
{
    // data members:

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

    // Constructors
    // ...
};

此处是对该声明样式的更深入的说明.

这篇关于我应该删除智能指针的移动构造函数和移动分配吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆