为什么移动操作后observer_ptr
没有归零?
在其默认构造中正确设置为nullptr
,这确实有意义(并防止指向垃圾)。
因此,它应该在std::move()
之后归零,就像std::string
,std::vector
等一样。
这将使它成为原始指针有意义的几个上下文中的一个很好的候选者,以及自动生成对具有原始指针数据成员的类的移动操作,如this case。
修改
@JonathanWakely在评论中指出(这与aforementioned question有关):
如果移动后
observer_ptr
为空,则可以使用它来实现 具有指针成员的类型的归零规则。这是非常的 有用的功能。
答案 0 :(得分:10)
似乎许多人一开始就错过了这个想法的观点和实用性。
考虑:
template<typename Mutex>
class unique_lock
{
Mutex* pm;
public:
unique_lock() : pm() { }
unique_lock(Mutex& m) : pm(&m) { }
~unique_lock() { if (pm) pm->unlock(); }
unique_lock(unique_lock&& ul) : pm(ul.pm) { ul.pm = nullptr; }
unique_lock& operator=(unique_lock&& ul)
{
unique_lock(std::move(ul)).swap(*this);
return *this;
}
void swap(unique_lock& ul) { std::swap(pm, ul.pm); }
};
使用“哑”智能指针,它是null-on-default-construction和null-after-move,你可以默认三个特殊成员函数,所以它变为:
template<typename Mutex>
class unique_lock
{
tidy_ptr<Mutex> pm;
public:
unique_lock() = default; // 1
unique_lock(Mutex& m) : pm(&m) { }
~unique_lock() { if (pm) pm->unlock(); }
unique_lock(unique_lock&& ul) = default; // 2
unique_lock& operator=(unique_lock&& ul) = default; // 3
void swap(unique_lock& ul) { std::swap(pm, ul.pm); }
};
这就是为什么有一个愚蠢的,非拥有的智能指针,它在移动后是空的,如tidy_ptr
但是observer_ptr
只是null-on-default-construction,所以如果它是标准化的,那么声明一个函数来获取一个非拥有的指针会很有用,但它对像上面的那个,所以我仍然需要另一个非拥有的哑指针类型。拥有两个非拥有的哑智能指针类型似乎比没有它更糟糕!
答案 1 :(得分:2)
因此,移动构造函数旨在使复制构造函数在某些情况下更便宜。
让我们写出我们期望这些构造函数和析构函数是什么:(这有点简化,但这对于这个例子来说很好)。
observer_ptr() {
this->ptr == nullptr;
}
observer_ptr(T *obj) {
this->ptr = obj;
}
observer_ptr(observer_ptr<T> const & obj) {
this->ptr = obj.ptr;
}
~observer_ptr() {
}
您建议该类提供一个类似于:
的移动构造函数observer_ptr(observer_ptr<T> && obj) {
this->ptr = obj.ptr;
obj.ptr = null;
}
当我建议现有的复制构造函数可以正常工作,并且比建议的移动构造函数便宜。
std::vector
怎么样?std :: vector在复制时实际上会复制它所支持的数组。因此,std :: vector复制构造函数看起来像:
vector(vector<T> const & obj) {
for (auto const & elem : obj)
this->push_back(elem);
}
std :: vector的move构造函数可以优化它。它可以这样做,因为可以从obj
窃取内存。在std :: vector中,这实际上很有用。
vector(vector<T> && obj) {
this->data_ptr = obj.data_ptr;
obj.data_ptr = nullptr;
obj.size = 0;
}
答案 2 :(得分:0)
除了将移动的observer_ptr
归零的次要性能影响之外(复制而不是移动),主要的理由可能是尽可能地模仿常规指针的行为,遵循principle of least surprise。
但是,可能存在更为重要的性能问题:默认移动功能允许observer_ptr
为trivially copyable。简单的可复制性允许使用std::memcpy
复制对象。如果observer_ptr
不是简单的可复制的,那么任何具有observer_ptr
数据成员的类都不会导致性能损失,这会导致组合类层次结构下降。
我不知道使用std::memcpy
优化可以获得哪些性能改进,但它们可能比上述次要性能问题更重要。