我应该删除移动构造函数和智能指针的移动赋值吗?
Should I delete the move constructor and the move assignment of a smart pointer?
我正在实现一个简单的智能指针,它基本上跟踪对其处理的指针的引用数。
我知道我可以实现移动语义,但我认为这没有意义,因为复制智能指针非常便宜。特别是考虑到它引入了产生讨厌的错误的机会。
这是我的 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_
指南
从不删除特招成员
在典型的代码中(例如在您的问题中),删除移动成员有两种动机。其中一个动机会产生不正确的代码(如您的示例所示),而对于另一个动机,删除移动成员是多余的(没有坏处也没有好处)。
如果您有一个可复制的 class 并且您不想移动成员,只需不要声明它们(包括不删除它们)。删除的成员仍然被声明。删除的成员参与重载决议。不在场的成员不参加。当您使用有效的复制构造函数和已删除的移动成员创建 class 时,您不能通过函数的值 return 它,因为重载解析将绑定到已删除的移动成员。
有时候人们想说:这个class既不可移动也不可复制。删除复制和移动成员都是正确的。然而,只需删除复制成员就足够了(只要未声明移动成员)。声明(甚至删除)复制成员禁止 编译器声明移动成员。所以在这种情况下,删除的移动成员只是多余的。
如果你声明删除的move成员,即使你碰巧挑到了case是多余的,不是不正确的,每次有人读你的代码,他们都需要重新发现你的case是多余的还是不正确的。让您的代码的读者更容易阅读,永远不要删除移动成员。
不正确的大小写:
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 声明的顶部,跳过那些您不想声明的成员,这会有所帮助。这种做法使代码的读者更容易快速确定您有意未声明任何特定的特殊成员。
例如,这是我遵循的模式:
class X
{
// data members:
public:
// special members
~X();
X();
X(const X&);
X& operator=(const X&);
X(X&&);
X& operator=(X&&);
// Constructors
// ...
};
Here is a more in-depth explanation of this declaration style.
我正在实现一个简单的智能指针,它基本上跟踪对其处理的指针的引用数。
我知道我可以实现移动语义,但我认为这没有意义,因为复制智能指针非常便宜。特别是考虑到它引入了产生讨厌的错误的机会。
这是我的 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_
指南
从不删除特招成员
在典型的代码中(例如在您的问题中),删除移动成员有两种动机。其中一个动机会产生不正确的代码(如您的示例所示),而对于另一个动机,删除移动成员是多余的(没有坏处也没有好处)。
如果您有一个可复制的 class 并且您不想移动成员,只需不要声明它们(包括不删除它们)。删除的成员仍然被声明。删除的成员参与重载决议。不在场的成员不参加。当您使用有效的复制构造函数和已删除的移动成员创建 class 时,您不能通过函数的值 return 它,因为重载解析将绑定到已删除的移动成员。
有时候人们想说:这个class既不可移动也不可复制。删除复制和移动成员都是正确的。然而,只需删除复制成员就足够了(只要未声明移动成员)。声明(甚至删除)复制成员禁止 编译器声明移动成员。所以在这种情况下,删除的移动成员只是多余的。
如果你声明删除的move成员,即使你碰巧挑到了case是多余的,不是不正确的,每次有人读你的代码,他们都需要重新发现你的case是多余的还是不正确的。让您的代码的读者更容易阅读,永远不要删除移动成员。
不正确的大小写:
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 声明的顶部,跳过那些您不想声明的成员,这会有所帮助。这种做法使代码的读者更容易快速确定您有意未声明任何特定的特殊成员。
例如,这是我遵循的模式:
class X
{
// data members:
public:
// special members
~X();
X();
X(const X&);
X& operator=(const X&);
X(X&&);
X& operator=(X&&);
// Constructors
// ...
};
Here is a more in-depth explanation of this declaration style.