6 写时拷贝技术
- 定义
在数据第一次写入到某个存储位置时,首先将原有内容拷贝出来,写到另一位置处,然后再将数据写入到存储设备中,该技术只拷贝在拷贝初始化开始之后修改过的数据。
6.1 拷贝控制
- C++提供两个拷贝控制函数
拷贝构造函数
拷贝赋值运算符重载 - 实例
class String{
public:
String(const char* str = NULL);
String(const String& str);
String& operator=(const String& str);
~String();
size_t size()const;
friend ostream& operator<<(ostream& os,String const& str);
private:
char* data;
};
6.2 深拷贝与浅拷贝
- 概念
浅拷贝:只拷贝指针地址。
通常默认拷贝构造函数与赋值运算符重载都是浅拷贝。
深拷贝:重现分配堆内存,拷贝指针指向内容。
例如:
String::String(const char* str){
if(NULL == str){
data = new char[1];
data[0] = '\0';
}else{
data = new char[strlen(str)+1];
strcpy(data,str);
}
}
String::~String(){
delete [] data;
data = NULL;
}
String::String(const String& str){
data = new char[str.size()+1];
strcpy(data,str.data);
}
String& String::operator=(const String& str){
if(this != &str){
delete [] data;
data = new char[str.size()+1];
strcpy(data,str.data);
}
return *this;
}
inline size_t String::size()const{
return strlen(data);
}
ostream& operator<<(ostream& os,const String& str){
return os << static_cast<const void *>(str.data) << ':' << str.data;
}
- 比较
优点 | 缺点 |
---|---|
浅拷贝 | 只有一份数据,节省空间。 因为多个指针指向同一个空间,容易引发同一内存多次释放的问题。 |
深拷贝 | 每个指针指向不同地址,没有同一内存多次释放的问题。 存在多份相同数据,浪费空间。 |
浅拷贝与深拷贝的优缺点分别互为彼此的优缺点。有什么办法可以兼有二者的优点?
主要解决问题:
数据相同时只有一份内存。
不会出现多次释放问题。
6.3 计数器技术:数据相同共享一份内存
计数器技术就是兼有浅拷贝与深拷贝优点的一种技术。
在类声明中添加了计数器s_count
。
class String{
public:
String(const char* str = NULL);
String(const String& str);
String& operator=(const String& str);
~String();
size_t size()const;
friend ostream& operator<<(ostream& os,String const& str);
private:
char* data;
static int s_count;
};
实现中增加计数处理
int String::s_count = 0;
String::String(const char* str){
if(NULL == str){
data = new char[1];
data[0] = '\0';
}else{
data = new char[strlen(str)+1];
strcpy(data,str);
}
++s_count;
}
String::~String(){
if(--s_count == 0){
delete [] data;
data = NULL;
}
}
String::String(const String& str):data(str.data){
++s_count;
}
String& String::operator=(const String& str){
if(this != &str){
data = str.data;
++s_count;
}
return *this;
}
问题:构造新对象的计数是几?计数器技术会导致构造新对象计数错误。
解决:每个空间应该具有自己的引用计数,而不能所有空间共享一个引用计数。
- 头文件
class String{
struct StringBase{
StringBase(const char* str);
~StringBase();
char* data;
int count;
};
public:
String(const char* str = NULL);
String(const String& str);
String& operator=(const String& str);
~String();
size_t size()const;
friend ostream& operator<<(ostream& os,String const& str);
private:
StringBase* base;
};
实现
String::StringBase::StringBase(const char* str){
if(NULL == str){
data = new char[1];
data[0] = '\0';
}else{
data = new char[strlen(str)+1];
strcpy(data,str);
}
++count;
}
String::StringBase::~StringBase(){
if(--count == 0){
delete [] data;
data = NULL;
}
}
String::String(const char* str){
base = new StringBase(str);
}
String::~String(){
}
String::String(const String& str):base(str.base){
base->count++;
}
String& String::operator=(const String& str){
if(this != &str){
base->count--;
base = str.base;
base->count++;
}
return *this;
}
inline size_t String::size()const{
return strlen(base->data);
}
ostream& operator<<(ostream& os,const String& str){
return os << static_cast<const void *>(str.base) << ':' << str.base->data;
}
6.4 写时拷贝技术
以上都是拷贝复制操作,如果字符串发生改变,那么才是真正的写时拷贝。
例如:实现+=操作
String& String::operator+=(const String& str){
base.count--;
// 一些具体操作...
base = new StringBase();
base.count++;
return *this;
}
- 实例
#include <iostream>
#include <cstring>
#include <memory>
using namespace std;
class String {
shared_ptr<char[]> ptr;
// char* s; // ptr.get()
public:
String(const char* s) {
char* t = new char[strlen(s)+1];
strcpy(t,s);
ptr = shared_ptr<char[]>(t); //t智能指针
}
~String() {
cout << __func__ << endl;
}
size_t size()const {
return strlen(ptr.get());
}
/*
String(const String& s){}
String& operator=(const String& s){}
*/
friend ostream& operator<<(ostream& os,const String& s) {
return os << (void*)s.ptr.get() << ":" << s.ptr.get();
}
char& operator[](int i);
String operator+(const String& s)const {
char* t = new char[size()+s.size()+1];
strcpy(t,ptr.get());
strcat(t,s.ptr.get());
return String(t);
}
};
int main() {
String s("Hello,World");
cout << s << endl;
String t = s;
t = "def"; //String("def")为临时对象,用完即析构
cout << s << endl;
cout << t << endl;
cout << s+t << endl;
}
结果:
[root@localhost 5]# ./a.out
0x1d59e70:Hello,World
~String
0x1d59e70:Hello,World
0x1d5a2c0:def
0x1d5a320:Hello,Worlddef
~String
~String
~String