什么是深拷贝、浅拷贝
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存。
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
假设B复制了A,修改A的时候,看B是否发生变化:
- 如果B跟着也变了,说明是浅拷贝(修改堆内存中的同一个值)
- 如果B没有改变,说明是深拷贝(修改堆内存中的不同的值)
隐式共享
隐式共享也称“写时复制(copy-on-write)”。就是:修改前大家都用同一块内存的数据,修改时才会进行全数据拷贝操作。
Qt中的很多C++类都使用了隐式数据共享来最大化资源使用和最小化拷贝代价。隐式共享类在作为参数传递时,不仅安全而且高效,因为只是指向数据的指针被传递了,底层的数据只有在函数向它执行写动作时才会发生拷贝,即写时拷贝。
一个共享类是由一个指向共享数据块的的指针组成的,该数据块包含一个引用计数和实际数据。
当一个隐式共享类的对象被创建时,它会引用计数设为1。无论何时,当一个新的对象引用这个共享数据时,引用计数会增加,当一个对象解引用这个共享数据时,引用计数会减少。当引用计数变为0时,共享数据会被删除。
处理共享数据时,通常有两种方式拷贝一个对象。我们通常称它们为深拷贝和浅拷贝。其中,深拷贝意味着复制一个对象;浅拷贝只拷贝引用,即只有指向共享数据的指针被复制。但执行一次深拷贝在内存和CPU方面的代价都是昂贵的。执行一次浅拷贝是非常快的,因为这只牵涉到设置一个指针和增加引用计数。隐式共享对象的赋值(使用operator=)被实现为浅拷贝。
共享的好处就在于程序不必执行不必要的拷贝,这就会促使更少的内存使用和更少的数据复制。这样,对象就可以简单的被赋值,作为函数的参数传递,作为函数的返回值。
QString str1 = "hello world my name is cainiao";
QString str2 = str1;
qDebug() << "str1 address" << str1.constData();
qDebug() << "str2 address" << str2.constData();
结果如下:
str1 address 0x1f6898
str2 address 0x1f6898
从上面可以看出,str1 和str2 均指向同一块内存,str2的仅仅是复制了str1指向的指针。
我们修改代码,如下,我们修改了str2的第一个字符,即针对str2做了修改。
QString str1 = "hello world my name is cainiao";
QString str2 = str1;
qDebug() << "str1 address" << str1.constData();
qDebug() << "str2 address" << str2.constData();
str2[0] = 'm';
qDebug() << "str2 address" << str2.constData();
qDebug() << "str1="<<str1;
qDebug() << "str2="<<str2;
结果如下:
str1 address 0x7e6898
str2 address 0x7e6898
str2 address 0x6f2618
str1= "hello world my name is cainiao"
str2= "mello world my name is cainiao"
表明,str2在修改后,指向的地址不在是str1的地址。并且修改str2的内容明显对str1没有影响,即此时str2做了str1的全盘拷贝,并且修改了第一个字符。
qt中如何实现隐式共享?
看过qt代码的人应该会知道,qt中很多类都继承与QShareData,也就是说通过QShareData可以实现隐式共享。
QSharedData
{
public:
mutable QAtomicInt ref;
inline QSharedData() noexcept : ref(0) { }
inline QSharedData(const QSharedData &) noexcept : ref(0) { }
// using the assignment operator would lead to corruption in the ref-counting
QSharedData &operator=(const QSharedData &) = delete;
~QSharedData() = default;
};
从QShareData中,我们看到有一个QAtomicInt
来做引用计数。
我们以 Student类为例,要实现该类的隐式共享,需要:
- 定义继承于 QSharedData 的 StudentData 类,用于包含所有的数据成员;
- 定义 Student类,包含 QSharedDataPointer 数据成员
代码:
#include
#include
class StudentData : public QSharedData
{
public:
StudentData () : year(20) {}
StudentData (const StudentData &t) : QSharedData(t)
,name(t.name)
,address(t.address)
,phone(t.phone)
,isBoy(t.isBoy)
,year(t.year)
{
}
~StudentData()
{
}
QString name;
QString address;
QString phone;
bool isBoy = true;
int year;
};
class Student
{
public:
Student()
{
d = new StudentData ;
}
Student(const QString &name,const QString &phone, const QString &address,const int year, const bool b )
{
d = new StudentData;
setName(name);
setPhone(phone);
setAddress(address);
setYear(year);
setBoy(b);
}
Student(const Student &other) : d(other.d){}
QString name() const {return d->name;}
void setName(const QString &name) {d->name = name;}
QString phone() const {return d->phone;}
void setPhone(const QString &phone) {d->phone= phone;}
QString address() const {return d->address;}
void setAddress(const QString &address) {d->address= address;}
int year() const {return d->year;}
void setYear(int year) {d->year= year;}
bool isBoy() const {return d->isBoy ;}
void setBoy (const bool b) {d->isBoy = b;}
private:
QSharedDataPointer<StudentData> d;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Student s1("xiaoHong","1309999999","beijing",20,false);
Student s2 = s1;
return a.exec();
}
调试窗口结果如下:
可以看出s1和s2的d数据指向的是同一个地址,即同一份数据。
下来我们尝试修改下s2的数据,
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Student s1("xiaoHong","1309999999","beijing",20,false);
Student s2 = s1;
s2.setName("dawang");
return a.exec();
}
可以看出此时s1和s2的d指针指向已经不同,即s2深拷贝了s1.