在这个insightful article中,其中一个Qt程序员试图解释Qt实现的各种智能指针。最初,他在共享数据和共享指针之间做了区分:
坦率地说,我不会理解这个解释。文章评论中有一个澄清请求,但我没有发现作者的解释充分。首先,让我们直截了当: 分享之间有区别 指针和共享数据。当你 分享指针,价值 指针及其生命周期受到保护 由智能指针类。其他 单词,指针是不变的。 但是,指针的对象 指的是完全在外面 它的控制。我们不知道是否 对象是否可以复制,如果是的话 可转让与否。
现在,数据共享涉及到 智能指针类知道什么 关于共享的数据。事实上, 重点是数据是 被分享,我们不关心如何。 指针正在被使用的事实 分享数据是无关紧要的 这点。例如,你没有 真的关心Qt工具类是怎样的 隐含地分享,是吗?什么 对你而言,重要的是他们是共享的 (从而减少内存消耗)和 他们的工作好像不是。
如果你做了解这一点,请解释。这种区别是什么,以及其他共享指针类(即来自boost或新的C ++标准)如何适合这种分类?
提前致谢
答案 0 :(得分:7)
在稍后的评论中,他稍微澄清了这个问题
这是我在第一部分尝试解决的重点。当您使用QSharedPointer时,您将共享指针的所有权。该类仅控制和处理指针 - 其他任何内容(如访问数据)都在其范围之外。使用QSharedDataPointer时,您正在共享数据。该类用于隐式共享:因此它可能会分裂。
试图解释:
重要的是,“指针”并不意味着在这种情况下存储地址的对象,但它意味着对象所在的存储位置(地址本身)。所以严格来说,我想,你必须说你正在分享这个地址。因此,boost::shared_ptr
是一个共享“指针”的智能指针。 boost::intrusive_ptr
或其他侵入式智能指针似乎也共享指针,虽然知道指向的对象(它有一个引用计数成员或函数递增/递减它)。
示例:如果有人与你共用一个黑盒子并且他不知道黑盒子里有什么,它类似于共享指针(代表盒子),而不是数据(盒子里面有什么) )。事实上,你甚至不知道盒子里面的东西是可共享的(如果盒子里什么都没包含怎么办?)。智能指针由您和另一个人代表(当然,您不会共享),但地址是框, it 是共享的。
共享数据意味着智能指针足够了解指向的数据,它可能会更改指向的地址(这需要复制数据等)。因此,现在指针可能指向不同的地址。由于地址不同,地址不再共享。这也是std::string
在某些实现中所做的事情:
std::string a("foo"), b(a);
// a and b may point to the same storage by now.
std::cout << (void*)a.c_str(), (void*)b.c_str();
// but now, since you could modify data, they will
// be different
std::cout << (void*)&a[0], (void*)&b[0];
分享数据并不一定意味着您有一个指针呈现给您。您可以通过std::string
和a[0]
的纯粹方式使用cout << a;
,并且永远不会触及任何c_str()
函数。仍然可以在幕后继续分享。许多Qt类和其他小部件工具包的类也会发生同样的事情,称为隐式共享(或写入时复制)。所以我认为可以这样总结:
所以试图分类
boost::shared_ptr
,boost::intrusive_ptr
:分享指针,而不是数据。 QString
,QPen
,QSharedDataPointer
:分享其中包含的数据。 std::unique_ptr
,std::auto_ptr
(以及QScopedPointer
):既不共享指针也不共享数据。 答案 1 :(得分:3)
说我们有这个班级
struct BigArray{
int operator[](size_t i)const{return m_data[i];}
int& operator[](size_t i){return m_data[i];}
private:
int m_data[10000000];
};
现在说我们有两个实例:
BigArray a;
a[0]=1;//initializaation etc
BigArray b=a;
此时我们想要这个不变的
assert(a[0]==b[0]);
默认复制ctor可确保此不变量,但代价是深度复制整个对象。我们可能会尝试这样的加速
struct BigArray{
BigArray():m_data(new int[10000000]){}
int operator[](size_t i)const{return (*m_data)[i];}
int& operator[](size_t i){return (*m_data)[i];}
private:
shared_ptr<int> m_data;
};
这也将满足不变量,而不需要制作深层副本,所以到目前为止一切都很好。 现在使用这个新的实现我们做了
b[0]=2;
现在我们希望这与深拷贝情况一样 断言(A [0] = B [0]!); 但它失败了。要解决这个问题,我们需要稍作改动:
struct BigArray{
BigArray():m_data(new int[10000000]){}
int operator[](size_t i)const{return (*m_data)[i];}
int& operator[](size_t i){
if(!m_data.unique()){//"detach"
shared_ptr<int> _tmp(new int[10000000]);
memcpy(_tmp.get(),m_data.get(),10000000);
m_data=_tmp;
}
return (*m_data)[i];
}
private:
shared_ptr<int> m_data;
};
现在我们有一个只在需要const访问时被浅层复制的类,并且在需要非const访问时被深度复制。这是“shared_data”指针概念背后的想法。 const 调用不会深度复制(它们称之为“分离”),而非const将在深度复制(如果它是共享的)。它还在operator ==之上添加了一些语义,这样它不仅可以比较指针而且还可以比较数据,这样就可以了:
BigArray b=a;//shallow copy
assert(a==b);//true
b[0]=a[0]+1;//deep copy
b[0]=a[0];//put it back
assert(a==b);//true
这种技术称为COW(Copy on Write),自C ++诞生以来就已存在。它也非常脆弱 - 上面的例子看起来很有用,因为它很小并且用例很少。在实践中,它很少值得麻烦,实际上C ++ 0x已经弃用了COW字符串。所以要谨慎使用。
答案 2 :(得分:1)
在第一种情况下,您向指针添加间接级别,以便智能指针表示的对象包装原始指针。只有一个指向对象的指针,它是包装器的工作,用于跟踪对原始指针的引用。一段非常简单的代码可能如下所示:
template<typename T>
struct smart_ptr {
T *ptr_to_object;
int *ptr_to_ref_count;
};
当您复制结构时,您的复制/分配代码必须确保引用计数递增(或者如果对象被销毁则递减)但是指向实际包装对象的指针将永远不会改变并且可能只是浅复制。由于结构非常小,复制起来既简单又便宜,而你所要做的就是操纵引用计数。
在第二种情况下,它更像是一个对象存储库。 “隐式共享”部分表明,您可以通过执行类似FooWidget
的操作来询问框架BarFoo.getFooWidget()
,即使它看起来像指针 - 智能与否 - 您获得的是指向一个新对象,实际上是一个指向现有对象的指针,该对象保存在某种对象缓存中。从这个意义上说,它可能更类似于通过调用工厂方法获得的类似Singleton的对象。
至少那个区别对我来说是这样的,但我可能还远远不够,我需要谷歌地图找回来。