我过去6个月一直在使用Qt,但仍然难以理解隐式共享类的概念。我有以下问题:
- 什么是隐式共享类?它们如何工作?
- Trolltech Qt网站上说它最大化资源使用和最小化复制,请解释这是如何实现的。
- 有没有人能给出任何例子来更好地理解?欢迎提供任何有或无示例解释此概念的网站链接。
感谢大家的回答。关于这个主题,我想到了另一个点,即栈对象指向堆分配的共享数据。这是图表:
对此有何看法?什么是引用计数?当对象引用共享数据时,它是否会增加计数器?反之亦然?
我过去6个月一直在使用Qt,但仍然难以理解隐式共享类的概念。我有以下问题:
感谢大家的回答。关于这个主题,我想到了另一个点,即栈对象指向堆分配的共享数据。这是图表:
对此有何看法?什么是引用计数?当对象引用共享数据时,它是否会增加计数器?反之亦然?
想象一下以下情境。你使用的是C++03,你写了如下代码:
string a("hello");
string b = a;
此时,您有两个字符串对象a和b,每个对象都有自己的缓冲区来存储字符数组。尽管缓冲区的内容完全相同,但是a和b仍然拥有各自的"hello"副本。这样会浪费内存。如果它们共用一个缓冲区,您只需要使用一个字符数组来为两个字符串存储"hello world"。QString a("Hello");
QString b = a;
在这种情况下,只有a
创建了一个char数组来存储“hello”。而b
没有创建自己的char数组,它只是指向a
的char数组。这样可以节省内存。b [0] ='M'
,即修改b
,那么b
将创建自己的char数组,复制a
的数组内容,然后修改自己的数组。String
类提供任何方法来修改其内容。这样做是为了始终可以共享这种数据。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
)。不是将a
中的4
复制到b
中,而是它们共享相同的数据。一个 int 不是什么大不了的事情,但想象一下如果 SharedData
是一个大字符串或任何其他更复杂的数据结构。仅复制一个指针比几十个字节要快得多。当您不需要副本时,这也可以节省大量内存。
什么是写时复制?
回想一下上面我们做 b[0]='M'
时说的话。那就是写时复制。b
和 a
共享同一个字符数组。但是 b
需要修改字符串,它不能直接修改,因为这会同时改变 a
中的字符串。所以 b
必须创建自己的 char 数组的副本才能对其进行更改。由于只有在修改数组时才必须创建副本,因此称为写时复制。
(免责声明:我从未使用过Qt,因此在某些细节上可能会有所错误-欢迎评论和改进...)
官方文档写得相当好。从中我推断出隐式共享类是那些实例不会实际复制其基础数据的类(这将是一项CPU和内存密集型操作),而是仅保留对其的引用(即存在一个共同的支持引用计数的数据对象)。这种优化使对象使用更少的内存和CPU时间,而对于它们的环境来说,似乎每个对象都有自己独立的数据。当然,如果对象是可变的,则必须执行实际的字节到字节数据复制,并且这些类会自动实现此机制(setter方法使用detach()
方法使对象独立于公共数据并创建自己的实际副本)。
std::string
的实现通常比这要复杂得多,包括引用计数、COW,有时甚至还包括短字符串优化(将长度较短的字符串内联)。 - Marcelo Cantosstd::string
中是明确禁止的,理由充分。当涉及到多线程时,它会变成一个巨大的性能下降。 - Puppyb=Data(4)
的说法不准确。Data(4)
创建了一个data==4
的SharedData
。对b
的赋值将引用计数增加到2。在if
结束时,b
和Data(4)
的析构函数都被调用。因此,引用计数被设置为零。 - André Oriani