我挖出了一个旧的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
时,我确实想要很多网格功能,如果对不可复制的类型禁用复制,那就太棒了......这会抓住错误!但是,当类型碰巧是可复制的时,拥有隐式共享工作是很好的默认值。
一些想法:
NonCopyableGrid
,CopyableGrid
)...或(UniqueGrid
,Grid
)...... Grid
构造函数Grid::newNonCopyable
,Grid::newCopyable
),但会调用相关的构造函数......可能更具描述性选择其中一种方法胜过其他方法,或者让人们在这种情况下更好地采用了一些方法,这是否有充分的理由?
答案 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> >
不能满足您的需求,那一切都很好。我想我会分享以防万一。