COW和SSO

GCC 4.x.x string的实现是 COW (写时复制)
GCC 5.x.x string的实现是 SSO (短字符串优先)

验证COW的存在

在g++ 4.

	std::string s1 = "hello,world";
	std::string s2(s1);
	printf("s1'addr = %p, str = %s, s2'addr = %p,str = %s\n", s1.c_str(), s1.c_str(), s2.c_str(), s2.c_str());

	s2[0] = 'H';
	printf("s1'addr = %p, str = %s, s2'addr = %p,str = %s\n", s1.c_str(), s1.c_str(), s2.c_str(), s2.c_str());

该例子可以看到,s2复制s1的时候进行的是浅拷贝,两者指向同一块空间,当s2发生修改时,s2会重新申请空间。

验证SSO的存在

在g++ 5.4.0

	std::string s1 = "hello,world";

	printf("s1'size = %d\n", sizeof(s1)); // 可以看到string不再是一个指针

	std::string s3 = "1234567891111111"; // 可以看到超过15个字符s3就在堆上了
	printf("s1'size = %p\n", s3.c_str());


	std::string s2(s1);
	printf("s1'addr = %p, str = %s, s2'addr = %p,str = %s\n", s1.c_str(), s1.c_str(), s2.c_str(), s2.c_str());

	std::string s4(s3); // 可以看到SSO没用引用计数,每次都是重新申请空间
	printf("s3'addr = %p, str = %s, s4'addr = %p,str = %s\n", s3.c_str(), s3.c_str(), s4.c_str(), s4.c_str());

如果想让SSO提升,在大于15字符后采用引用计数,可以看看FaceBook开源库中string的实现 https://github.com/facebook/folly.git

一个简单的写时复制实现

该图来自《Effective STL》条款15
实现思路:
上图来自《Effective STL》条款15,本例只实现RefCnt和Value,Size Capactity暂时不管。
①class COWString中定义一个char的成员,char的前四个字节存放引用计数,后面存放实际的字符串。
②当发生复制构造和赋值操作时,执行浅拷贝,引用计数加1,有对象析构时引用计数减1,只有当引用计数为0时才释放空间
③当对象发生修改时,重载operator[],进行深拷贝

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

class COWString {
public:
	COWString();
	COWString(const char* pstr);
	COWString(const COWString& rhs);
	COWString& operator=(const COWString& rhs);
	~COWString();

	const char& operator[](size_t idx) const { return _pstr[idx]; }
	char& operator[](size_t idx);


	size_t size() { return strlen(_pstr); }
	const char* c_str() const { return _pstr; }

	size_t refcount() const { return *(int*)(_pstr - 4); }

private:
	void initRefcount() { *(int*)_pstr = 1; }
	void increaseRefcount() { (*(int*)(_pstr - 4))++; }
	void decreaseRefcount()
	{ 
		if (_pstr == NULL) {
			return;
		}
		(*(int*)(_pstr - 4))--; 
	}
	void release()
	{
		decreaseRefcount();
		if (refcount() == 0) {
			std::cout << "delete _pstr" << std::endl;
			delete [](_pstr - 4);   // 注意这里一定要减4,不然内存泄露
		}
	}
private:
	char* _pstr;
};

COWString::COWString()
{
	this->_pstr = new char[5]();  // 前四个字节用来存放引用计数,再加上一个'\0',申请5个字节  注意这里的()要加上,不然申请的数组没有初始化,可能会有脏数据 重载了operator+后几个字符串相加会触发这个bug
	initRefcount();
	this->_pstr += 4;
}

COWString::COWString(const char* pstr)
{
	this->_pstr = new char[strlen(pstr) + 5]();
	initRefcount();
	this->_pstr += 4;
	strcpy_s(this->_pstr, strlen(pstr) + 1, pstr);
}

COWString::COWString(const COWString& rhs)
{
	this->_pstr = rhs._pstr;
	increaseRefcount();
}

COWString& COWString::operator=(const COWString& rhs)
{
	if (this != &rhs) {
		release();
		this->_pstr = rhs._pstr;
		increaseRefcount();
	}
	return *this;
}

// 下标访问运算符无法区分出是读操作还是写操作
char& COWString::operator[](size_t idx)
{
	if (refcount() > 1) {
		char* tmp = new char[strlen(_pstr) + 5]();
		tmp += 4;
		strcpy_s(tmp, size() + 1, this->_pstr);
		decreaseRefcount();
		this->_pstr = tmp;
		increaseRefcount();
	}
	return _pstr[idx];
}

COWString::~COWString()
{
	std::cout << "COWString::~COWString " << _pstr << std::endl;
	release();
}

int main()
{
	COWString s1("hello");
	COWString s2(s1);
	COWString s3 = s1;

	printf("s1' addr = %p, s1' c_str() = %s, refcount = %d\n", s1.c_str(), s1.c_str(), (int)s1.refcount());
	printf("s2' addr = %p, s2' c_str() = %s, refcount = %d\n", s2.c_str(), s2.c_str(), (int)s2.refcount());
	printf("s3' addr = %p, s3' c_str() = %s, refcount = %d\n", s3.c_str(), s3.c_str(), (int)s3.refcount());

	std::cout << "执行修改操作后" << std::endl;
	s3[1] = 'E';
	printf("s1' addr = %p, s1' c_str() = %s, refcount = %d\n", s1.c_str(), s1.c_str(), (int)s1.refcount());
	printf("s2' addr = %p, s2' c_str() = %s, refcount = %d\n", s2.c_str(), s2.c_str(), (int)s2.refcount());
	printf("s3' addr = %p, s3' c_str() = %s, refcount = %d\n", s3.c_str(), s3.c_str(), (int)s3.refcount());
	
	return 0;
}

执行结果如图:
在这里插入图片描述
思考:
引用计数为什么要与char*放一起,不能单独定义一个成员变量存储?
①引用计数作为成员变量如:

class COWString{
	char* _pstr;
	int _refCnt;
}

这样不行,每个对象的引用计数相互独立了,无法同步增减。
②引用计数作为静态成员变量如:

class COWString{
	char* _pstr;
	static int _refCnt;
}

这样不行,静态成员变量为整个类共有,会造成不是指向同一个字符串的对象的引用计数也一致了。
③通过保存引用计数的指针:

class COWString{
	char* _pstr;
	int* _refCnt;
}

这样可行,不过要申请两次内存,因此将两个成员合并就是本例的实现。
④通过对象成员:

class String{
    friend class COWString;
	char* _pstr;
	int _refCnt;
}
class COWString{
	String* _string;;
}

可行。

区分读写操作的写时复制实现

上面的例子有个问题,就是只访问下标的元素,没有发生修改时,也会去重新开辟空间,如:const char c = s1[0];
实现思路:
①写操作:s1[0] = ‘H’; 先调用COWString::operator[idx]返回char,然后调用char的operator=修改变量
读操作:const char c = s1[0]; 仅仅调用COWString::operator[idx]返回char
因此区分读还是写操作就在于是否调用了char类型对象的operator=对operator[idx]重新赋值,COWString::operator[idx]返回的是一个char,由于char是内置类型,不能重载,因此我们考虑将COWString::operator[idx]返回一个自定义对象,然后重载自定义对象的operator=,这样写操作就实现了
②由于COWString::operator[idx]返回一个自定义对象,例const char c = s1[0]返回的自定义对象不能直接赋值给char类型变量,因此需要类型转换函数来解决。

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

class COWString {
	class CharProxy {
	public:
		CharProxy(size_t idx, COWString& self)
		: _idx(idx), _self(self)
		{}

		operator char() { // 类型转换函数
			std::cout << "CharProxy::operator char()" << std::endl;
			return _self._pstr[_idx];
		}
		char& operator=(const char& ch)
		{
			if (_self.refcount() > 1) {
				char* tmp = new char[strlen(_self._pstr) + 5]();
				tmp += 4;
				strcpy_s(tmp, _self.size() + 1, _self._pstr);
				_self.decreaseRefcount();
				_self._pstr = tmp;
				_self.increaseRefcount();
			}
			return _self._pstr[_idx];
		}
	private:
		size_t _idx;
		COWString& _self;
	};
public:
	COWString();
	COWString(const char* pstr);
	COWString(const COWString& rhs);
	COWString& operator=(const COWString& rhs);
	~COWString();

	const char& operator[](size_t idx) const
	{ 
		return _pstr[idx]; 
	}
	CharProxy operator[](size_t idx);


	size_t size() { return strlen(_pstr); }
	const char* c_str() const { return _pstr; }

	size_t refcount() const { return *(int*)(_pstr - 4); }

private:
	void initRefcount() { *(int*)_pstr = 1; }
	void increaseRefcount() { (*(int*)(_pstr - 4))++; }
	void decreaseRefcount()
	{ 
		if (_pstr == NULL) {
			return;
		}
		(*(int*)(_pstr - 4))--; 
	}
	void release()
	{
		decreaseRefcount();
		if (refcount() == 0) {
			std::cout << "delete _pstr" << std::endl;
			delete [](_pstr - 4);   // 注意这里一定要减4,不然内存泄露
		}
	}
private:
	char* _pstr;
};

COWString::COWString()
{
	this->_pstr = new char[5]();  // 前四个字节用来存放引用计数,再加上一个'\0',申请5个字节  注意这里的()要加上,不然申请的数组没有初始化,可能会有脏数据 重载了operator+后几个字符串相加会触发这个bug
	initRefcount();
	this->_pstr += 4;
}

COWString::COWString(const char* pstr)
{
	this->_pstr = new char[strlen(pstr) + 5]();
	initRefcount();
	this->_pstr += 4;
	strcpy_s(this->_pstr, strlen(pstr) + 1, pstr);
}

COWString::COWString(const COWString& rhs)
{
	this->_pstr = rhs._pstr;
	increaseRefcount();
}

COWString& COWString::operator=(const COWString& rhs)
{
	if (this != &rhs) {
		release();
		this->_pstr = rhs._pstr;
		increaseRefcount();
	}
	return *this;
}

// 注意这里是返回的对象,return的是个临时对象,返回引用,临时对象销毁,会异常
COWString::CharProxy COWString::operator[](size_t idx)
{
	return CharProxy(idx, *this);
}

COWString::~COWString()
{
	std::cout << "COWString::~COWString " << _pstr << std::endl;
	release();
}

int main()
{
	COWString s1("hello");
	COWString s2(s1);
	COWString s3 = s1;

	printf("s1' addr = %p, s1' c_str() = %s, refcount = %d\n", s1.c_str(), s1.c_str(), (int)s1.refcount());
	printf("s2' addr = %p, s2' c_str() = %s, refcount = %d\n", s2.c_str(), s2.c_str(), (int)s2.refcount());
	printf("s3' addr = %p, s3' c_str() = %s, refcount = %d\n", s3.c_str(), s3.c_str(), (int)s3.refcount());

	std::cout << "执行写操作后 s3[1] = 'E' " << std::endl;
	s3[1] = 'E';
	printf("s1' addr = %p, s1' c_str() = %s, refcount = %d\n", s1.c_str(), s1.c_str(), (int)s1.refcount());
	printf("s2' addr = %p, s2' c_str() = %s, refcount = %d\n", s2.c_str(), s2.c_str(), (int)s2.refcount());
	printf("s3' addr = %p, s3' c_str() = %s, refcount = %d\n", s3.c_str(), s3.c_str(), (int)s3.refcount());

	std::cout << "执行读操作后 s2[1] " << s2[1] << std::endl;
	s3[1] = 'E';
	printf("s1' addr = %p, s1' c_str() = %s, refcount = %d\n", s1.c_str(), s1.c_str(), (int)s1.refcount());
	printf("s2' addr = %p, s2' c_str() = %s, refcount = %d\n", s2.c_str(), s2.c_str(), (int)s2.refcount());
	printf("s3' addr = %p, s3' c_str() = %s, refcount = %d\n", s3.c_str(), s3.c_str(), (int)s3.refcount());

	const char c = s2[1]; // 隐式调用类型转换成员函数
	std::cout << "执行读操作后 s2[1] " << c << std::endl;
	printf("s1' addr = %p, s1' c_str() = %s, refcount = %d\n", s1.c_str(), s1.c_str(), (int)s1.refcount());
	printf("s2' addr = %p, s2' c_str() = %s, refcount = %d\n", s2.c_str(), s2.c_str(), (int)s2.refcount());
	printf("s3' addr = %p, s3' c_str() = %s, refcount = %d\n", s3.c_str(), s3.c_str(), (int)s3.refcount());
	
	return 0;
}

执行结果如图:
在这里插入图片描述

参考资料:https://www.cnblogs.com/cthon/p/9181979.html
http://www.kohn.com.cn/wordpress/?p=245
《Effective STL》

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值