过去6个月我一直在与Qt合作,我仍然在努力理解隐式共享课程的概念。我有以下问题:
Thanx伙伴们提供了所有答案..我围绕着这个主题的另一个问题是堆栈对象指向堆分配的共享数据..这是图表...
任何对此??? ...以及究竟什么是引用计数??当对象引用公共共享数据时,它是一种计数器,反之亦然?反之亦然?
答案 0 :(得分:14)
想象一下。你正在使用C ++ 03,你写道:
string a("hello");
string b = a;
此时你有两个字符串对象a
和b
,每个对象都有自己的缓冲区来存储一个字符数组。即使缓冲区的内容完全相同,a
和b
仍然拥有自己的“hello”副本。这是浪费记忆。如果他们共享缓冲区,则必须使用单个char数组为两个字符串存储“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
数组的内容然后修改自己的数组。
在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
未从a
复制到b
,他们只是共享相同的数据。 int并不是什么大问题,但想象一下SharedData
是否包含一些大字符串或任何其他更复杂的数据结构。仅复制指针远比几十个字节快。当你真的不需要副本时,它还可以节省大量内存。
什么是写时复制?
回想一下我们做b[0]='M'
时所说的话。那是 copy-on-write 。 b
和a
共享相同的char数组。但是b
需要修改字符串。它无法直接执行,因为这也会修改a
的字符串。所以b
必须创建自己的char数组副本才能更改它。因为它只需要在修改数组时创建副本,所以它被称为 copy-on-write 。
答案 1 :(得分:2)
基于我对http://doc.qt.io/archives/qt-4.7/implicit-sharing.html ...
的阅读它基本上只是使用引用计数和写时复制的任何类的通用名称,以避免不必要地复制由类管理的数据。
引用计数是一种确保对象在任何人对其感兴趣时都会挂起的技术。想要在一段时间内保持对象的任何代码都会增加引用计数。当它失去对对象的兴趣时,它会减少引用,如果引用计数达到零,意味着它是最后一个感兴趣的一方,它也会破坏该对象。
对于Qt的共享类,似乎引用计数是完全自动的。引用计数通过相关类的构造函数和析构函数进行管理。
除了通过引用计数进行共享之外,类还可以通过在对其进行任何修改之前制作基础数据的副本来确保不同方不会破坏彼此的对象版本。这称为写时复制或COW习语。
答案 2 :(得分:0)
(免责声明:我从未使用过Qt,所以我在某些细节上可能会出错 - 欢迎评论和改进......)
official documentation写得很好。从那里我推断出隐式共享类是其实例实际上不复制其底层数据的类(这将是CPU和内存密集型操作),而是它们只持有对它的引用(即,有一个共同的支持引用计数数据对象)。这种优化使对象使用更少的内存和CPU时间,而对于它们的环境,似乎每个对象都有自己独立的数据。当然,如果对象是可变的,则必须进行实际的字节到字节数据复制,并且这些类自动实现此机制(setter方法使用detach()
方法使对象独立于公共数据并创建它们自己的实际副本。)