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

时间:2010-04-17 08:25:13

标签: c++ qt smart-pointers

在这个insightful article中,其中一个Qt程序员试图解释Qt实现的各种智能指针。最初,他在共享数据和共享指针之间做了区分:

  

首先,让我们直截了当:   分享之间有区别   指针和共享数据。当你   分享指针,价值   指针及其生命周期受到保护   由智能指针类。其他   单词,指针是不变的。   但是,指针的对象   指的是完全在外面   它的控制。我们不知道是否   对象是否可以复制,如果是的话   可转让与否。

     

现在,数据共享涉及到   智能指针类知道什么   关于共享的数据。事实上,   重点是数据是   被分享,我们不关心如何。   指针正在被使用的事实   分享数据是无关紧要的   这点。例如,你没有   真的关心Qt工具类是怎样的   隐含地分享,是吗?什么   对你而言,重要的是他们是共享的   (从而减少内存消耗)和   他们的工作好像不是。

坦率地说,我不会理解这个解释。文章评论中有一个澄清请求,但我没有发现作者的解释充分。

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

提前致谢

3 个答案:

答案 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::stringa[0]的纯粹方式使用cout << a;,并且永远不会触及任何c_str()函数。仍然可以在幕后继续分享。许多Qt类和其他小部件工具包的类也会发生同样的事情,称为隐式共享(或写入时复制)。所以我认为可以这样总结:

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

所以试图分类

  • boost::shared_ptrboost::intrusive_ptr:分享指针,而不是数据。
  • QStringQPenQSharedDataPointer:分享其中包含的数据。
  • std::unique_ptrstd::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的对象。

至少那个区别对我来说是这样的,但我可能还远远不够,我需要谷歌地图找回来。