6

在过去的 6 个月里,我一直在使用 Qt,但我仍在努力理解隐式共享类的概念。我有以下问题:

  1. 什么是隐式共享类以及它们如何工作?
  2. 奇趣科技的 Qt 网站称它最大限度地利用了资源并最大限度地减少了复制。请解释一下这是怎么发生的。
  3. 谁能举个例子来更好地理解?也欢迎任何网站的链接,用或没有例子来解释这个概念。

感谢所有答案的家伙..关于这个主题,我提出了另一个观点是堆栈对象指向堆分配的共享数据..这是图表......这个图片..

对此有什么看法???...引用计数到底是什么??当对象引用公共共享数据时,它是一种计数器吗?反之亦然?

4

3 回答 3

14

想象一下。您正在使用 C++03 并编写:

string a("hello");
string b = a; 

此时您有两个字符串对象ab每个对象都有自己的缓冲区来存储字符数组。即使缓冲区的内容完全相同,a仍然b有自己的“hello”副本。这是对内存的浪费。如果他们共享缓冲区,您将不得不使用单个字符数组来存储两个字符串的“hello world”。

现在使用 QString 有点不同:

QString a("Hello");
QString b = a;

在这种情况下,只a创建了一个 char 数组来存储“hello”。b而不是创建自己的 char 数组,而是简单地指向a的 char 数组。这样可以节省内存。

现在,如果你这样做b[0]='M',id est,你修改b,然后b创建它自己的 char 数组,复制a's 数组的内容,然后修改它自己的数组。

在 Java 中,字符串是不可变的对象。换句话说,Java 没有在String类上提供任何方法来修改其内容。这样做是为了始终可以共享此类数据。

补充别人提到的东西:

我怎么知道我可以释放 char 数组?
这就是“参考计数”的用途。当一个对象被创建并设置为指向 char 数组时,它的引用计数将增加 1,因此它知道有多少对象仍在使用它。当指向它的对象被销毁时,引用计数会减少。当计数器达到零时,char 数组知道没有人在使用它,因此可以释放它。

这是引用计数的一个非常粗略的实现。无论如何,我并不打算准确或正确。我忽略了在 C++ 中实现复制构造函数和赋值运算符的正确方法。我无法检查实施是否有效。认为它是有点像 C++ 的算法描述。我只是想教这个概念。但是想象一下你有这些类:

class SharedData{
  private:
    int refcount;
    int data;

  public:
    SharedData(int _data){data=_data;refcount=1;}
    void incRef(){refcount++;}
    void decRef(){--refcount; if(refCount==0) delete this;}
};

class Data{
       SharedData* shared;
    public:
        Data(int i){shared = new Data(i);}
        Data(const Data& data){shared = data.shared; shared->incRef();}
        const Data& operator=(const Data& data){if(shared!=data.shared){
                                       shared->decRef();
                                       shared = data.shared;
                                       shared->incRef();}
        }
        ~Data(){shared->decRef();}
};

类的两个对象Data可以共享同一个SharedData对象,所以:

void someFunction() {
    Data a(3) //Creates a SharedData instance and set refcount to 1
    if (expression) {
       Data b = a; //b points to the same SharedData than a. refcount is 2
       b = Data(4);// b points to  diferent SharedData. refcount of SharedData of a is decremented to 1 and b's SharedData has refcount 1
       //destructor of b is called. Because shared data of b has now refcount == 0, the sharedData is freed;
    }
    //destructor of a is called, refcount is decremented again
    // because it is zero SharedData is freed
}

因此资源使用最大化,副本最小化。两者都a使用b相同的SharedData(又名 int 3)。. 4没有从 复制到ab它们只是共享相同的数据。一个 int 没什么大不了的,但想象一下,如果SharedData有一些大字符串或任何其他更复杂的数据结构。只复制一个指针比复制几十个字节要快得多。当你真的不需要副本时,它还可以节省大量内存。

什么是写时复制?
回想一下我在我们做的时候说过的话b[0]='M'。那是写时复制b并且a共享相同的字符数组。但b需要修改字符串。它不能直接这样做,因为那也会修改字符串a。因此b必须创建自己的 char 数组副本才能更改它。因为它只需要在修改数组时创建副本,所以它被称为copy-on-write

于 2012-09-02T06:15:58.980 回答
2

根据我对http://doc.qt.io/archives/qt-4.7/implicit-sharing.html的阅读...

它基本上只是任何使用引用计数和写时复制以避免不必要地复制由类管理的数据的任何类的通用名称。

引用计数

引用计数是一种技术,可确保只要有人对它感兴趣,对象就会一直存在。任何想要保留对象一段时间的代码都会增加引用计数。当它对对象失去兴趣时,它会减少引用,如果引用计数为零,这意味着它是最后一个感兴趣的一方,它也会破坏该对象。

在 Qt 的共享类的情况下,引用计数似乎是完全自动的。引用计数通过相关类的构造函数和析构函数进行管理。

写时复制

除了通过引用计数进行共享之外,类还可以通过在对其进行任何修改之前复制底层数据来确保不同的各方不会破坏彼此的对象版本。这称为写时复制或 COW 习语。

于 2012-09-02T05:57:16.480 回答
0

(免责声明:我从未使用过 Qt,所以我可能在某些细节上错了 - 欢迎评论和改进......)

官方文档写得很好。由此我推断隐式共享类是其实例实际上不复制其底层数据的类(这将是一个 CPU 和内存密集型操作),而是它们只持有对它的引用(即有一个公共支持引用计数数据对象)。这种优化使对象使用更少的内存和 CPU 时间,而对于他们的环境,似乎每个对象都有自己独立的数据。当然,如果对象是可变的,则必须进行实际的逐字节数据复制,并且这些类会自动实现此机制(setter 方法使用该detach()方法使对象独立于公共数据并创建自己的实际副本)。

于 2012-09-02T06:06:28.417 回答