【问题标题】:C++ smart pointers: sharing pointers vs. sharing dataC++ 智能指针:共享指针与共享数据
【发布时间】:2010-04-17 08:25:12
【问题描述】:

insightful article 中,一位 Qt 程序员试图解释 Qt 实现的不同类型的智能指针。一开始,他区分了共享数据和共享指针本身:

首先,让我们弄清楚一件事: 分享是有区别的 指针和共享数据。当你 共享指针,的值 指针及其生命周期受到保护 由智能指针类。其他 换句话说,指针是不变量。 但是,指针指向的对象 指的是完全在外面 它的控制。我们不知道是否 对象是否可复制,如果它是 可分配与否。

现在,数据共享涉及 智能指针类知道一些东西 关于共享的数据。实际上, 关键是数据是 被共享,我们不在乎如何。 使用指针的事实 共享数据无关紧要 这点。例如,您不 真的很关心 Qt 工具类是怎样的 隐式共享,你呢?什么 对你来说重要的是它们是共享的 (从而减少内存消耗)和 他们的工作就像他们不是一样。

坦率地说,我只是不理解这种解释。文章cmets中有一个澄清请求,但我发现作者的解释不够。

如果您确实理解这一点,请解释一下。这种区别是什么?其他共享指针类(即来自 boost 或新的 C++ 标准)如何适应这种分类?

提前致谢

【问题讨论】:

  • 这个 Qt 的东西还是让我很困惑 :)

标签: c++ qt smart-pointers


【解决方案1】:

在后来的评论中,他稍微澄清了一点

这是我在第一部分试图理解的重点。当您使用 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];

共享数据并不一定意味着您得到了一个指针。您可以通过a[0]cout &lt;&lt; a; 的纯粹方式使用std::string,并且永远不要接触任何c_str() 函数。仍然共享可能会在幕后进行。许多 Qt 类和其他小部件工具包的类也会发生同样的情况,称为隐式共享(或写入时复制)。所以我认为可以这样总结:

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

所以尝试分类

  • boost::shared_ptrboost::intrusive_ptr:共享指针,而不是数据。
  • QStringQPenQSharedDataPointer:分享其中包含的数据。
  • std::unique_ptrstd::auto_ptr(还有QScopedPointer):既不共享指针,也不共享数据。

【讨论】:

    【解决方案2】:

    假设我们有这门课

    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(写时复制),自 C++ 诞生之日起就已存在。它也非常脆弱——上面的例子似乎很有效,因为它很小并且用例很少。在实践中它很少值得麻烦,事实上 C++0x 已经弃用了 COW 字符串。所以谨慎使用。

    【讨论】:

    • 所以你是在暗示他共享数据的意思是COW,共享指针是简单的shared_ptr/引用计数指针习语?
    • 是的。如果您阅读 QT 文档,也可以说明这一点。 doc.trolltech.com/4.5/qshareddatapointer.htmlgrep "写时复制"
    【解决方案3】:

    在第一种情况下,您向指针添加了一个间接级别,这样智能指针所表示的对象就会包裹原始指针。只有一个指向对象的指针,而包装器的工作就是跟踪对原始指针的引用。一段非常简单的代码可能如下所示:

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

    当你复制结构体时,你的复制/赋值代码必须确保引用计数增加(或者如果对象被破坏则减少)但是指向实际包装对象的指针永远不会改变,并且可能只是浅的复制。由于结构非常小,因此复制起来既简单又便宜,“所有”你要做的就是操纵引用计数。

    在第二种情况下,它在我看来更像是一个对象存储库。 “隐式共享”部分建议您可以通过执行类似BarFoo.getFooWidget() 的操作来向框架询问FooWidget,即使它看起来像指针(无论是否智能),您返回的是一个指向新对象的指针,实际上,您得到了一个指向保存在某种对象缓存中的现有对象的指针。从这个意义上说,它可能更类似于通过调用工厂方法获得的类似 Singleton 的对象。

    至少这对我来说是这样的区别,但我可能离题太远了,我需要谷歌地图才能找到回去的路。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-22
      • 2015-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-11
      • 2015-10-06
      • 1970-01-01
      相关资源
      最近更新 更多