C++智能指针:共享指针与共享数据

问题描述:

在这个insightful article中,其中一位Qt程序员试图解释不同类型的智能指针Qt实现。在开始的时候,他让共享数据之间的区别,并分享自己的指针:C++智能指针:共享指针与共享数据

首先,让我们弄清楚一两件事: 有共享 指针和共享数据之间的差异。当您共享指针 时,指针及其生命周期的值将由智能指针类保护 。在其他 单词中,指针是不变的。 但是,指针 指向的对象完全在其控制范围之外 。我们不知道 对象是否可复制,如果它是 可赋值或不可以。

现在,共享数据涉及 智能指针类知道 关于被共享的数据。实际上, 整点就是数据是 被共享,我们不在乎如何。 指针正被用于 共享数据的事实在 这一点上是不相关的。例如,你不要 真的在乎Qt工具类是如何隐含地共享的,你呢? 对你很重要的是他们共享 (从而减少内存消耗)和 ,他们的工作,就好像他们没有。

坦率地说,我只是不介绍这个解释。文章评论中有澄清的请求,但我没有发现作者的解释是足够的。

如果你明白这一点,请解释一下。这种区别是什么,以及其他共享指针类(即来自boost或新的C++标准)如何适合此分类标准?

预先感谢

+0

这Qt的东西仍然让我困惑:) – 2010-04-17 12:34:09

在第一种情况下,在添加了一个间接层到指针,使得由智能指针所表示的对象包装的原始指针。只有一个指向对象的指针,并且它是包装器的工作,以跟踪对原始指针的引用。代码非常简单的一点可能是这样的:

template<typename T> 
struct smart_ptr { 
    T *ptr_to_object; 
    int *ptr_to_ref_count; 
}; 

当你复制的结构,你的复制/分配的代码必须确保该引用计数递增(或如果对象被销毁递减),但指向实际包装对象的指针永远不会改变,只能被浅拷贝。由于这个结构体非常小,复制起来既容易又便宜,而且你所要做的全部操作就是操纵引用计数。

在第二种情况下,它更像是一个对象存储库。 '隐式共享'部分暗示你可能会通过做一些类似于BarFoo.getFooWidget()的东西来询问框架的FooWidget,并且即使它看起来像指针 - 不管是否聪明 - 您返回的是指向新对象的指针,实际上被递交一个指向一个存在于某种对象缓存中的现有对象的指针。从这个意义上说,它可能更类似于通过调用工厂方法获得的类似单例的对象。

至少这就是我的区别,但我可能离我很远,我需要谷歌地图找回我的路。

在后面的评论,他澄清了这件事有点

这是很重要的一点我想在第一部分打通。当您使用QSharedPointer时,您将共享指针的所有权。该类仅控制和处理指针 - 其他任何内容(如访问数据)都在其范围之外。当您使用QSharedDataPointer时,您正在共享数据。而这个类是用于隐式共享的:所以它可能会分裂。

试图解释是:

,看看有什么重要的是,“指针”并不意味着存储在这种情况下,地址的对象,但它意味着存储位置,其中对象位于(地址本身)。所以严格来说,我认为,你必须说你是分享地址。因此boost::shared_ptr是共享“指针”的智能指针。 boost::intrusive_ptr或另一个侵入式智能指针似乎也分享指针,虽然知道指向该对象的某些内容(即它有一个引用计数成员或多个函数递增/递减它)。

示例:如果某人与您共享一个黑匣子,并且他不知道黑匣子中的内容,它与共享指示器(代表框)类似,但不是数据(内部是什么框)。实际上,你甚至不知道盒子里面的东西是可共享的(如果盒子里什么都没有包含什么?)。智能指针由您和其他人代表(当然,您并不共享),但地址是框,并且共享

共享数据意味着智能指针足够了解所指向的数据,它可能会更改指向的地址(这需要复制数据等)。所以,现在的指针可能会指向不同的地址。由于地址不同,地址不再共享。这是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]; 

共享数据并不一定意味着你必须向你的指针。您可以纯粹使用和cout << a;来使用std::string,并且绝不会触及任何c_str()函数。仍然可以在幕后继续分享。同样的事情也发生在很多Qt类和其他Widget工具包的类别上,这被称为隐式共享(或拷贝写在)。所以我认为可以这样总结:

  • 分享指针:当我们复制智能指针时,我们总是指向相同的地址,这意味着我们共享指针值。
  • 分享数据:我们可能会在不同的时间指向不同的地址。暗示我们知道如何将数据从一个地址复制到另一个地址。

所以试图分类

  • boost::shared_ptrboost::intrusive_ptr:共享指针,而不是数据。
  • QString,QPen,QSharedDataPointer:分享它包含的数据。
  • std::unique_ptrstd::auto_ptr(以及QScopedPointer):既不共享指针,也不是数据。

说我们有这个类

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确保这个不变,但是以牺牲深度拷贝e为代价修复对象。我们可能会尝试像这样的加速

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访问时,深拷贝是浅复制。这是“shared_data”指针概念背后的想法。 const调用不会深度复制(他们称之为“分离”),而非const则在深度复制(如果共享)。它也增加了运营商的顶部一些语义==使得它不只是比较指针,但数据一样,所以这将工作:

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(写入时复制)和自从C++出现以来一直存在。它也非常脆弱 - 上面的例子似乎起作用,因为它很小并且用例很少。在实践中,它很少会遇到麻烦,事实上C++ 0x已经废弃了COW字符串。所以谨慎使用。

+0

所以你暗示他分享数据的意思是COW,并通过共享指针是简单的shared_ptr /引用计数指针成语? – 2010-04-17 14:37:55

+0

是的。如果你阅读QT文档的话也是如此。 http://doc.trolltech.com/4.5/qshareddatapointer.html grep“copy on write” – 2010-04-18 02:08:33