移动后为什么observer_ptr没有归零?

时间:2014-03-10 20:41:39

标签: c++ pointers c++11 move

为什么移动操作后observer_ptr没有归零?

在其默认构造中正确设置为nullptr,这确实有意义(并防止指向垃圾)。

因此,它应该在std::move()之后归零,就像std::stringstd::vector等一样。

这将使它成为原始指针有意义的几个上下文中的一个很好的候选者,以及自动生成对具有原始指针数据成员的类的移动操作,如this case


修改

@JonathanWakely在评论中指出(这与aforementioned question有关):

  

如果移动后observer_ptr为空,则可以使用它来实现   具有指针成员的类型的归零规则。这是非常的   有用的功能。

3 个答案:

答案 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_ptrtrivially copyable。简单的可复制性允许使用std::memcpy复制对象。如果observer_ptr不是简单的可复制的,那么任何具有observer_ptr数据成员的类都不会导致性能损失,这会导致组合类层次结构下降。

我不知道使用std::memcpy优化可以获得哪些性能改进,但它们可能比上述次要性能问题更重要。