4

我挖出了一个旧Grid类,它只是一个以类型为模板的简单二维容器。要制作一个,您可以这样做:

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

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

但我采用的一个主要 Qt 习惯用法是它在后台进行了隐式共享。事实证明,这在我拥有的基于Thinker-Qt的程序的基于 QColor 的网格中非常有用。

但是:-/ 我也碰巧遇到过一些我写过以下内容的情况:

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

当我从auto_ptrC++11's升级时unique_ptr,编译器理所当然地抱怨。隐式共享需要能够在需要时制作相同的副本……并且auto_ptr通过将复制与所有权转让混为一谈,从而消除了这个错误。不可复制类型和隐式共享根本不混合,并且unique_ptr很友好地告诉我们。

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

好的,那么...我如何设计一个可以打开和关闭隐式共享的通用容器?当我使用 Grid 时,我确实需要许多 Grid 功能,auto_ptr如果对不可复制的类型禁用复制,那就太好了……这会捕获错误!但是,当类型恰好是可复制的时,默认情况下具有隐式共享工作是很好的。

一些想法:

  • 我可以根据您的口味制作单独的类型 ( NonCopyableGrid, CopyableGrid)...或 ( UniqueGrid, )...Grid
  • 我可以将标志传递给Grid构造函数
  • 我可以使用静态工厂方法(Grid::newNonCopyable, Grid::newCopyable),但它会在后台调用相关的构造函数......也许更具描述性
  • 如果可能,我可能会“检测”包含类型的可复制性,然后在实现中利用QSharedDataPointer或不利用,这取决于?

有什么好的理由选择其中一种方法而不是其他方法,或者人们是否针对这种情况采用了更好的方法?

4

2 回答 2

2

如果您打算在单个容器中执行此操作,我认为最简单的方法是使用std::is_copy_constructable选择您的数据结构是否继承自QSharedData,并替换QSharedDataPointerstd::unique_ptr (QScopedPointer不支持移动语义)

这只是我所想的一个粗略示例,因为我没有同时使用 Qt 和 C++11:

template<class T>
class Grid
{       
    struct EmptyStruct
    {
    };

    typedef typename std::conditional<
        std::is_copy_constructible<T>::value,  
        QSharedData,  
        EmptyStruct
    >::type GridDataBase;

    struct GridData : public GridDataBase
    {
        // data goes here
    };

    typedef typename std::conditional<
        std::is_copy_constructible<T>::value, 
        QSharedDataPointer<GridData>, 
        std::unique_ptr<GridData>
    >::type GridDataPointer;

public:
    Grid() : data_(new GridData) {}

private:
    GridDataPointer data_;
};
于 2012-06-19T06:25:39.817 回答
1

免责声明

我不太了解您的Grid模板或用例。但是,我确实了解容器。所以也许这个答案适用于你Grid<T>,也许它不适用。

既然您已经说明了Grid< unique_ptr<T> >表明唯一所有权和不可复制的意图,那么T对写时复制做类似的事情怎么样?

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

Grid< cow_ptr<T> >

Acow_ptr<T>会提供引用计数副本,但T如果引用计数不是 1,则“非常量取消引用”会执行副本。因此Grid不必担心内存管理到这种程度。它只需要处理它的数据缓冲区,并且可能在Grid's 复制和/或移动成员中移动或复制其成员。

Acow_ptr<T>很容易通过 wrapping 拼凑在一起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 往往更有效。要获得 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> >不能真正满足您的需求,那很好。我想我会分享以防万一。

于 2012-06-19T14:51:10.717 回答