编写可以处理隐式共享的容器,但是为不可复制的类型(如unique_ptr)关闭它?

时间:2012-06-19 04:27:38

标签: c++ qt c++11 containers unique-ptr

我挖出了一个旧的Grid类,它只是一个用类型模板化的简单2-D容器。要制作一个你会这样做:

Grid<SomeType> myGrid (QSize (width, height));

我尝试将其设为“Qt-ish”...例如,它根据QSize执行大小操作,并使用myGrid[QPoint (x, y)]对其进行索引。它可以采用布尔掩码并对设置了掩码位的元素执行操作。还有一项专业化,如果您的元素为QColor,则可以为您生成QImage

但我采用的一个主要的Qt成语是它引人注目implicit sharing。对于我基于Thinker-Qt的程序,这在基于QColor的网格中非常有用。

然而: - /我也碰巧遇到过一些我写过以下内容的案例:

Grid< auto_ptr<SomeType> > myAutoPtrGrid (QSize (width, height));

当我从auto_ptr升级到C ++ 11的unique_ptr时,编译器理所当然地抱怨道。如果需要,隐式共享需要能够制作相同的副本......并且auto_ptr通过将复制与所有权转移混合在一起来扫除这个错误。不可复制的类型和隐式共享根本不混合,unique_ptr非常友好地告诉我们。

(注意:事实上我没有注意到这个问题,因为auto_ptr的用例是通过引用传递网格......从来没有按价值传递。但是,这是糟糕的代码...... C ++ 11的主动性在它发生之前指出了潜在的问题。)

好的,那么......我怎样才能设计一个可以打开和关闭隐式共享的通用容器?当我使用auto_ptr时,我确实想要很多网格功能,如果对不可复制的类型禁用复制,那就太棒了......这会抓住错误!但是,当类型碰巧是可复制的时,拥有隐式共享工作是很好的默认值。

一些想法:

  • 我可以根据您的喜好制作单独的类型(NonCopyableGridCopyableGrid)...或(UniqueGridGrid)......
  • 我可以将标志传递给Grid构造函数
  • 我可以使用静态工厂方法(Grid::newNonCopyableGrid::newCopyable),但会调用相关的构造函数......可能更具描述性
  • 如果可能的话,我可能会“检测”所包含类型的可复制性,然后在执行中是否利用QSharedDataPointer,具体取决于?

选择其中一种方法胜过其他方法,或者让人们在这种情况下更好地采用了一些方法,这是否有充分的理由?

2 个答案:

答案 0 :(得分:2)

如果您要在单个容器中执行此操作,我认为最简单的方法是使用std::is_copy_constructable选择您的数据结构是继承自QSharedData还是替换{{1} } QSharedDataPointer std::unique_ptr不支持移动语义)

这只是我正在思考的一个粗略的例子,因为我没有Qt和C ++ 11一起提供:

QScopedPointer

答案 1 :(得分:1)

<强>声明

我真的不了解您的Grid模板或您的用例。但是我一般都了解容器。所以这个答案可能适用于你的Grid<T>,也许不适用。

既然你已经说明了Grid< unique_ptr<T> >表示独特所有权和不可复制T的意图,那么做什么类似于写作时的副本呢?

如何明确说明何时使用写入时使用复制:

Grid< cow_ptr<T> >

cow_ptr<T>会提供引用计数副本,但如果引用计数不是1,则在“非常规取消引用”下会复制T所以Grid不用担心关于内存管理到这种程度。它只需要处理其数据缓冲区,并可能在Grid的副本和/或移动成员中移动或复制其成员。

通过包裹cow_ptr<T>std::shared_ptr<T>很容易拼凑在一起。以下是我在一个月前处理类似问题时的部分实现:

template <class T>
class cow_ptr
{
    std::shared_ptr<T> ptr_;
public:
    template <class ...Args,
              class = typename std::enable_if
                      <
                         std::is_constructible<std::shared_ptr<T>, Args...>::value
                      >::type
             >
        explicit cow_ptr(Args&& ...args)
            : ptr_(std::forward<Args>(args)...)
        {}

    explicit operator bool() const noexcept {return ptr_ != nullptr;}

    T const* read() const noexcept {return ptr_.get();}
    T      * write()
    {
        if (ptr_.use_count() > 1)
            ptr_.reset(ptr_->clone());
        return ptr_.get();
    }

    T const& operator*() const noexcept {return *read();}
    T const* operator->() const noexcept {return read();}

    void reset() {ptr_.reset();}
    template <class Y>
        void
        reset(Y* p)
        {
            ptr_.reset(p);
        }
};

我选择使“写入”语法非常明确,因为当写入很少但COW很多时,COW往往更有效。要获得const访问权限,您可以像使用任何其他指针一样使用它:

p->inspect();  // compile time error if inspect() isn't const

但要做一些修改操作,你必须使用write成员函数调用它:

p.write()->modify();

shared_ptr有一堆非常方便的构造函数,我不想在cow_ptr中复制所有这些构造函数。所以你看到的cow_ptr构造函数是穷人继承构造函数的实现,它也适用于数据成员。

您可能需要使用其他智能指针功能(如关系运算符)来填充此功能。您可能还想更改cow_ptr复制T的方式。我目前正在假设一个虚拟clone()函数,但您可以轻松替换为write使用T的复制构造函数。

如果明确的Grid< cow_ptr<T> >不能满足您的需求,那一切都很好。我想我会分享以防万一。