为什么不复制由单个 shared_ptr 组成的 class

Why not to copy a class consisting of a single shared_ptr

最近,我阅读了 an article 关于 C++ 中惰性数据结构的内容(这个问题与惰性或特定数据结构无关,不过 - 这只是动机)。

一个惰性流(list)实现如下:

template<class T>
class Stream
{
private:
    std::shared_ptr <Susp<Cell<T>>> _lazyCell;
public:
    Stream() {}
    Stream(std::function<Cell<T>()> f)
        : _lazyCell(std::make_shared<Susp<Cell<T>>>(f))
    {}
    Stream(Stream && stm)
        : _lazyCell(std::move(stm._lazyCell))
    {}
    Stream & operator=(Stream && stm)
    {
        _lazyCell = std::move(stm._lazyCell);
        return *this;
    }
    bool isEmpty() const
    {
        return !_lazyCell;
    }
    T get() const
    {
        return _lazyCell->get().val();
    }
    Stream<T> pop_front() const
    {
        return _lazyCell->get().pop_front();
    }
};

作者提到移动构造函数:

I also added a move constructor and a move assignment operator for efficiency.

然而,由于显式存在,不能简单地分配一个 Stream。这背后的动机是什么?

据我所知,class 仅由一个 shared_ptr 组成,可以轻松复制。在这样的 class 中禁止复制构造有什么好处吗?

shared_ptr 在内部用于共享惰性值单元格作为私有实现的一部分。

但是,从用户的角度来看,它是一个不可变的对象。提供复制构造函数和赋值运算符将取消这种不变性。

他正在为 Haskell 的不可变对象的行为建模。

如果这样做是线程安全的,那么使该对象可复制是合理的,因为实际上它是一个(尽管比平常更复杂)共享实现的句柄。

但是,复制者需要了解他们复制的是共享状态的句柄,而不是状态本身。

我认为这是一种过早的优化。

首先,由于 three/five (http://en.cppreference.com/w/cpp/language/rule_of_three) 的规则,移动 copy/assignment 构造函数将以相同的方式创建而无需输入。

因此,正如您已经指出的那样,唯一的区别是缺少 copy/assigment 构造函数。最好将它们标记为已删除,例如:

 Stream(Stream & stm) = deleted;

然而,这里真正的问题是使用只有一个所有者的共享指针。只使用 std::unique_ptr 会好得多。使用它后,复制和赋值自动被禁用,作者的意图更加清晰。