您的位置:首页 > 运维架构

写时复制(Copy-On-Write)

2017-07-03 20:15 274 查看
Scott Meyers推荐我们:


在真正需要一个存储空间时才去分配内存,这样会极大地降低程序运行时的内存开销。



分配内存是一件比较耗时的工作,虽然在需要时才进行分配内存会比较耗时,但是这会给我们的程序在运行时带来比较好的性能。

写时复制(Copy-On-Write)技术,是编程界"懒惰行为"-拖延战术的产物。

案例-std::string

std::string
类的实现就采用了写时才复制技术

C++曾在性能问题上被广泛地质疑和指责过,为了提高性能,STL中许多类的实现都采用了Copy-On-Write技术。使得使用STL的程序有着比较高的性能。

std::string
中有一个
char *
类型的私有成员,用来记录从堆上分配内存的地址,该私有成员在std::string对象构造时分配内存,在std::string对象析构时释放内存。

std::string str1 = "kakawater";
std::string str2 = "kakawater";
std::string str3 = "xiao yu";
std::string strEmpty;
std::cout << "sizeof(str1) = "<<sizeof(str1)<<std::endl;
std::cout << "sizeof(str2) = "<<sizeof(str2)<<std::endl;
std::cout << "sizeof(str3) = "<<sizeof(str3)<<std::endl;
std::cout << "sizeof(strEmpty) = "<<sizeof(strEmpty)<<std::endl;

执行结果:

sizeof(str1) = 24
sizeof(str2) = 24
sizeof(str3) = 24
sizeof(strEmpty) = 24
~AutoRelease()
Program ended with exit code: 0

因为是从堆上分配内存,所以string类在维护这块内存时是格外小心的。

std::string类在返回这块内存地址时,只返回
char const *
类型,也就是其
char const * c_str()const
方法

如果要修改std::string中的内容,就只能通过std::string提供的方法对数据进行修改。

验证写时复制:

std::string str1 = "kakawater";
std::string str3 = str1;
std::string str2 = "kakawater";
std::cout << "sizeof(str1) = "<<sizeof(str1)<<std::endl;
std::cout << "sizeof(str2) = "<<sizeof(str2)<<std::endl;
std::cout << "sizeof(str3) = "<<sizeof(str3)<<std::endl;

printf("str1 address = %p\n",str1.c_str());
printf("str2 address = %p\n",str2.c_str());
printf("str3 address = %p\n\n",str3.c_str());

str3[0] = 'K';

printf("str1 address = %p\n",str1.c_str());
printf("str2 address = %p\n",str2.c_str());
printf("str3 address = %p\n",str3.c_str());


非写时复制技术的输出

sizeof(str1) = 24
sizeof(str2) = 24
sizeof(str3) = 24
str1 address = 0x7fff5fbff399
str2 address = 0x7fff5fbff359
str3 address = 0x7fff5fbff381//str3和str1的c_str成员的内存地址不相同

str1 address = 0x7fff5fbff399
str2 address = 0x7fff5fbff359
str3 address = 0x7fff5fbff381


写时复制技术

sizeof(str1) = 8
sizeof(str2) = 8
sizeof(str3) = 8
str1 address = 0x82e028
str2 address = 0x82e058
str3 address = 0x82e028//str3和str1的c_str成员的内存地址相同

str1 address = 0x82e028
str2 address = 0x82e058
str3 address = 0x82e088//str3和str1的c_str成员的内存地址不相同

Copy-On-Write的原理

Copy-On-Write使用了"引用计数(retainCount)"的机制(在Objective-C和Java中有应用)。

当第一个string对象str1构造时,string的构造函数会根据传入的参数在堆空间上分配内存。

当有其它对象通过str1进行拷贝构造时,str1的引用计数会增加1.

当有对象析构时,这个引用计数会减1。直到最后一个对象析构时,引用计数为0,此时程序才会真正释放这块内存。

即引用计数用来解决用来存放字符串的内存何时释放的问题。

引用计数的实现

使用一个内部类,用来实现引用计数和保存字符串。

实现一个采用Copy-On-Write技术的String

//
//  String.hpp
//  Demo01
//
//  Created by Kakawater on 17/2/7.
//  Copyright © 2017年 Kakawater. All rights reserved.
//

#ifndef String_hpp
#define String_hpp

#include <stdio.h>
#include <iostream>

class String
{
public:
String();
String(const char *cString);
String(const String & rawString);
String & operator = (const String rightStringt);
char & operator[] (int charIndex);
char const & operator[] (int charIndex) const
~String();
void show()const
{
this->_backedStringImpl->show();
}
char const * c_str() const;
private:
class StringImpl
{
public:
StringImpl();
StringImpl(const char *cString);
StringImpl(const String::StringImpl & rawString);
void retain();
void release();
char & operator[] (int charIndex) const;
void show()const
{
std::cout<< backedStr<<std::endl;
}
char const * c_str() const;
private:
long _retainCount;
char *backedStr;
String::StringImpl & operator = (const String::StringImpl rightStringt);
~StringImpl();//将析构函数设置为私有,使得StringImpl对象不能从栈上创建。
};
StringImpl *_backedStringImpl;

};

#endif /* String_hpp */

//
//  String.cpp
//  Demo01
//
//  Created by Kakawater on 17/2/7.
//  Copyright © 2017年 Kakawater. All rights reserved.
//

#include "String.hpp"
#include <string.h>
#include <stdlib.h>
String::String()
{
this->_backedStringImpl = new String::StringImpl();
}

String::String(const char *cString)
{
this->_backedStringImpl = new String::StringImpl(cString);
}

String::String(const String & rawString)
{
this->_backedStringImpl = rawString._backedStringImpl;
this->_backedStringImpl->retain();
}
String & String::operator = (const String rightStringt)
{
this->_backedStringImpl->release();
this->_backedStringImpl = rightStringt._backedStringImpl;
this->_backedStringImpl->retain();

return *this;
}

String::~String()
{
this->_backedStringImpl->release();
}
char & String::operator[] (int charIndex)
{
String::StringImpl* newStringImpl = new String::StringImpl(*(this->_backedStringImpl));
this->_backedStringImpl->release();
this->_backedStringImpl = newStringImpl;
return this->_backedStringImpl->operator[](charIndex);
}

char const &  String::operator[] (int charIndex) const
{
return this->_backedStringImpl->operator[](charIndex);
}

char const * String::c_str()const
{
return this->_backedStringImpl->c_str();
}

String::StringImpl::StringImpl():_retainCount(1)
{
std::cout<<"String::StringImpl::StringImpl()"<<std::endl;
this->backedStr = (char*)malloc(1);
this->backedStr[0] = '\n';
}

String::StringImpl::StringImpl(const char *cString):_retainCount(1)
{
std::cout<< "String::StringImpl::StringImpl(const char *cString)"<<std::endl;
int needLength = strlen(cString);

this->backedStr = (char*)malloc(needLength);

strcpy(this->backedStr, cString);
}

String::StringImpl::StringImpl(const String::StringImpl & rawString):_retainCount(1)
{
std::cout << "String::StringImpl::StringImpl(const String::StringImpl & rawString)" <<std::endl;
int needLength = strlen(rawString.backedStr);

this->backedStr = (char*)malloc(needLength);

strcpy(this->backedStr, rawString.backedStr);
}
String::StringImpl::~StringImpl()
{
std::cout<<"String::StringImpl::~StringImpl() backedStr:"<<this->backedStr<<std::endl;
free(this->backedStr);
}

String::StringImpl & String::StringImpl::operator = (const String::StringImpl rightStringt)
{
free(this->backedStr);

int needLength = strlen(rightStringt.backedStr);

this->backedStr = (char*)malloc(needLength);

strcpy(this->backedStr, rightStringt.backedStr);
this->_retainCount = 1;
return *this;
}

char & String::StringImpl::operator[] (int charIndex)const
{
return this->backedStr[charIndex];
}

void String::StringImpl::retain()
{
++this->_retainCount;
}
void String::StringImpl::release()
{
if(1 == this->_retainCount)
{
delete this;
}else{
--this->_retainCount;
}
}

char const * String::StringImpl::c_str()const
{
return this->backedStr;
}

测试:

String str1 = "kakawater";
String str3 = str1;
String str2 = "kakawater Str2";

str1.show();
str2.show();
str3.show();

std::cout << "sizeof(str1) = "<<sizeof(str1)<<std::endl;
std::cout << "sizeof(str2) = "<<sizeof(str2)<<std::endl;
std::cout << "sizeof(str3) = "<<sizeof(str3)<<std::endl;

printf("str1 address = %p\n",str1.c_str());
printf("str2 address = %p\n",str2.c_str());
printf("str3 address = %p\n\n",str3.c_str());

//
str3[0] = 'A';
std::cout<<"修改后"<<std::endl;
//

str1.show();
str2.show();
str3.show();

printf("str1 address = %p\n",str1.c_str());
printf("str2 address = %p\n",str2.c_str());
printf("str3 address = %p\n",str3.c_str());

执行结果:

String::StringImpl::StringImpl(const char *cString)
String::StringImpl::StringImpl(const char *cString)
kakawater
kakawater Str2
kakawater
sizeof(str1) = 8
sizeof(str2) = 8
sizeof(str3) = 8
str1 address = 0x1002002d0
str2 address = 0x1002002f0
str3 address = 0x1002002d0//str3和str1相同

String::StringImpl::StringImpl(const String::StringImpl & rawString)
修改后
kakawater
kakawater Str2
Aakawater
str1 address = 0x1002002d0
str2 address = 0x1002002f0
str3 address = 0x100200310//str1和str3不相同
String::StringImpl::~StringImpl() backedStr:kakawater Str2
String::StringImpl::~StringImpl() backedStr:Aakawater
String::StringImpl::~StringImpl() backedStr:kakawater
~AutoRelease()
Program ended with exit code: 0

缺陷:

对于非const 的String对象,无法通过operator[]区分是修改还是读取。

使用一个char的代理对象解决该缺陷

思路:通过一个包装类的operator char()转换函数进行读取,operator = 进行修改

//
//  String.hpp
//  Demo01
//
//  Created by Kakawater on 17/2/7.
//  Copyright © 2017年 Kakawater. All rights reserved.
//

#ifndef String_hpp
#define String_hpp

#include <stdio.h>
#include <iostream>

class String
{
public:
class CharProxy
{
public:
CharProxy(int charIndex,String & stringObject);
operator char();
CharProxy & operator = (char charValue);
private:
int  _charIndex;
String & _string;
};
String();
String(const char *cString);
String(const String & rawString);
String & operator = (const String rightStringt);
CharProxy  operator[] (int charIndex);
char const &  operator[] (int charIndex) const;
~String();
void show()const
{
this->_backedStringImpl->show();
}
char const * c_str() const;
private:
class StringImpl
{
public:
StringImpl();
StringImpl(const char *cString);
StringImpl(const String::StringImpl & rawString);
void retain();
void release();
long retainCount() const;
char & operator[] (int charIndex) const;
void show()const
{
std::cout<< backedStr<<std::endl;
}
char const * c_str() const;
private:
long _retainCount;
char *backedStr;
String::StringImpl & operator = (const String::StringImpl rightStringt);
~StringImpl();//将析构函数设置为私有,使得StringImpl对象不能从栈上创建。
};
StringImpl *_backedStringImpl;

};

#endif /* String_hpp */

//
//  String.cpp
//  Demo01
//
//  Created by Kakawater on 17/2/7.
//  Copyright © 2017年 Kakawater. All rights reserved.
//

#include "String.hpp"
#include <string.h>
#include <stdlib.h>
String::String()
{
this->_backedStringImpl = new String::StringImpl();
}

String::String(const char *cString)
{
this->_backedStringImpl = new String::StringImpl(cString);
}

String::String(const String & rawString)
{
this->_backedStringImpl = rawString._backedStringImpl;
this->_backedStringImpl->retain();
}
String & String::operator = (const String rightStringt)
{
this->_backedStringImpl->release();
this->_backedStringImpl = rightStringt._backedStringImpl;
this->_backedStringImpl->retain();

return *this;
}

String::~String()
{
this->_backedStringImpl->release();
}

String::CharProxy  String::operator[] (int charIndex)
{
String::CharProxy charProxy(charIndex,*this);
return charProxy;
}

char const &  String::operator[] (int charIndex) const
{
return this->_backedStringImpl->operator[](charIndex);
}

char const * String::c_str()const
{
return this->_backedStringImpl->c_str();
}

String::StringImpl::StringImpl():_retainCount(1)
{
std::cout<<"String::StringImpl::StringImpl()"<<std::endl;
this->backedStr = (char*)malloc(1);
this->backedStr[0] = '\n';
}

String::StringImpl::StringImpl(const char *cString):_retainCount(1)
{
std::cout<< "String::StringImpl::StringImpl(const char *cString)"<<std::endl;
int needLength = strlen(cString);

this->backedStr = (char*)malloc(needLength);

strcpy(this->backedStr, cString);
}

String::StringImpl::StringImpl(const String::StringImpl & rawString):_retainCount(1)
{
std::cout << "String::StringImpl::StringImpl(const String::StringImpl & rawString)" <<std::endl;
int needLength = strlen(rawString.backedStr);

this->backedStr = (char*)malloc(needLength);

strcpy(this->backedStr, rawString.backedStr);
}
String::StringImpl::~StringImpl()
{
std::cout<<"String::StringImpl::~StringImpl() backedStr:"<<this->backedStr<<std::endl;
free(this->backedStr);
}

String::StringImpl & String::StringImpl::operator = (const String::StringImpl rightStringt)
{
free(this->backedStr);

int needLength = strlen(rightStringt.backedStr);

this->backedStr = (char*)malloc(needLength);

strcpy(this->backedStr, rightStringt.backedStr);
this->_retainCount = 1;
return *this;
}

char & String::StringImpl::operator[] (int charIndex)const
{
return this->backedStr[charIndex];
}

void String::StringImpl::retain()
{
++this->_retainCount;
}
void String::StringImpl::release()
{
if(1 == this->_retainCount)
{
delete this;
}else{
--this->_retainCount;
}
}

long String::StringImpl::retainCount() const
{
return this->_retainCount;
}

char const * String::StringImpl::c_str()const
{
return this->backedStr;
}

String::CharProxy::CharProxy(int charIndex,String & stringObject):_charIndex(charIndex),_string(stringObject)
{

}

String::CharProxy::operator char()
{
return this->_string._backedStringImpl->operator[](this->_charIndex);
}

String::CharProxy & String::CharProxy::operator=(char charValue)
{
if (charValue != this->_string._backedStringImpl->operator[](this->_charIndex)) {
if(this->_string._backedStringImpl->retainCount() > 1)
{
String::StringImpl* newStringImpl = new String::StringImpl(*(this->_string._backedStringImpl));
this->_string._backedStringImpl->release();
this->_string._backedStringImpl = newStringImpl;
}
this->_string._backedStringImpl->operator[](this->_charIndex) = charValue;
}
return *this;
}

测试:

String str1 = "kakawater";
String str3 = str1;
String str2 = "kakawater Str2";

str1.show();
str2.show();
str3.show();

std::cout << "sizeof(str1) = "<<sizeof(str1)<<std::endl;
std::cout << "sizeof(str2) = "<<sizeof(str2)<<std::endl;
std::cout << "sizeof(str3) = "<<sizeof(str3)<<std::endl;

printf("str1 address = %p\n",str1.c_str());
printf("str2 address = %p\n",str2.c_str());
printf("str3 address = %p\n\n",str3.c_str());

std::cout<<"读取  str3[0] = "<<str3[0]<<std::endl;
printf("str1 address = %p\n",str1.c_str());
printf("str2 address = %p\n",str2.c_str());
printf("str3 address = %p\n",str3.c_str());
//
////
str3[0] = 'A';
std::cout<<"将str3[0]修改为'A'"<<std::endl;
////
//
str1.show();
str2.show();
str3.show();

printf("str1 address = %p\n",str1.c_str());
printf("str2 address = %p\n",str2.c_str());
printf("str3 address = %p\n",str3.c_str());

执行结果:

String::StringImpl::StringImpl(const char *cString)
String::StringImpl::StringImpl(const char *cString)
kakawater
kakawater Str2
kakawater
sizeof(str1) = 8
sizeof(str2) = 8
sizeof(str3) = 8
str1 address = 0x1002000b0
str2 address = 0x1002002a0
str3 address = 0x1002000b0

读取  str3[0] = k
str1 address = 0x1002000b0
str2 address = 0x1002002a0
str3 address = 0x1002000b0
String::StringImpl::StringImpl(const String::StringImpl & rawString)
将str3[0]修改为'A'
kakawater
kakawater Str2
Aakawater
str1 address = 0x1002000b0
str2 address = 0x1002002a0
str3 address = 0x100500000
String::StringImpl::~StringImpl() backedStr:kakawater Str2
String::StringImpl::~StringImpl() backedStr:Aakawater
String::StringImpl::~StringImpl() backedStr:kakawater
~AutoRelease()
Program ended with exit code: 0
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: