写时复制(Copy-On-Write)
2017-07-03 20:15
274 查看
Scott Meyers推荐我们:
在真正需要一个存储空间时才去分配内存,这样会极大地降低程序运行时的内存开销。
分配内存是一件比较耗时的工作,虽然在需要时才进行分配内存会比较耗时,但是这会给我们的程序在运行时带来比较好的性能。
写时复制(Copy-On-Write)技术,是编程界"懒惰行为"-拖延战术的产物。
C++曾在性能问题上被广泛地质疑和指责过,为了提高性能,STL中许多类的实现都采用了Copy-On-Write技术。使得使用STL的程序有着比较高的性能。
执行结果:
因为是从堆上分配内存,所以string类在维护这块内存时是格外小心的。
std::string类在返回这块内存地址时,只返回
如果要修改std::string中的内容,就只能通过std::string提供的方法对数据进行修改。
验证写时复制:
非写时复制技术的输出
写时复制技术
当第一个string对象str1构造时,string的构造函数会根据传入的参数在堆空间上分配内存。
当有其它对象通过str1进行拷贝构造时,str1的引用计数会增加1.
当有对象析构时,这个引用计数会减1。直到最后一个对象析构时,引用计数为0,此时程序才会真正释放这块内存。
即引用计数用来解决用来存放字符串的内存何时释放的问题。
测试:
执行结果:
缺陷:
对于非const 的String对象,无法通过operator[]区分是修改还是读取。
测试:
执行结果:
在真正需要一个存储空间时才去分配内存,这样会极大地降低程序运行时的内存开销。
分配内存是一件比较耗时的工作,虽然在需要时才进行分配内存会比较耗时,但是这会给我们的程序在运行时带来比较好的性能。
写时复制(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
相关文章推荐
- copy-on-write 写时复制
- Copy On Write(写时复制)
- 再谈QVector与QByteArray——Qt的写时复制(copy on write)技术
- PHP 之 写时复制介绍(Copy On Write)
- PHP源码分析-变量的引用计数、写时复制(Reference counting & Copy-on-Write)
- Copy-On-Write(写入时复制)技术
- QVector与QByteArray——Qt的写时复制(copy on write)技术
- Copy-On-Write 写时复制原理
- PHP写时复制(Copy-on-Write)
- 写时复制Copy-On-Write
- fork(),vfork(),写时复制(Copy-On-Write, COW)
- 引用计数,写时复制(Copy On Write)
- JAVA中写时复制(Copy-On-Write)Map实现
- 写时复制(copy-on-write)
- 关于 copy-on-write 写时复制
- 写时复制,写时拷贝,写时分裂,Copy on write
- 数组指针特例-写时复制cow(copy on write)
- PHP 之 写时复制介绍(Copy On Write)
- 底层存储变量的写时复制机制(copy on write)
- PHP中的写时复制(Copy On Write)