免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5595 | 回复: 4
打印 上一主题 下一主题

关于写时复制(COW) [复制链接]

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-09-26 15:39 |只看该作者 |倒序浏览
大部份的STL在实现string时,都采用COW来保证其高效性。即多个类会共用一个数据缓冲区(buffer),在拷贝构造、赋值等操作时,并不会对buffer进行复制。仅在需要对buffer进行修改,而且此buffer已与别的类共享了,才会开辟空间,将buffer复制一份进行修改。同样在析构时,如果buffer与与别的类共享,也不会释放空间。
举个例子:
  1. #include <stdio.h>
  2. #include <string>
  3. using namespace std;

  4. int main()
  5. {
  6.     string test1 = "hello";
  7.     string test2(test1);

  8.     printf("test1:%p test2:%p\n", test1.c_str(), test2.c_str());
  9. }
复制代码

运行结果:
test1:0x90a9014 test2:0x90a9014

可见两个地址是相等的,它们共用了同一个缓冲区。
什么时候会引起数据区的复制?当然是要修改string的值的时候
  1. #include <stdio.h>
  2. #include <string>
  3. using namespace std;

  4. int main()
  5. {
  6.     string test1 = "hello";
  7.     string test2(test1);

  8.     printf("test1:%p test2:%p\n", test1.c_str(), test2.c_str());
  9.     test2[0] = 'w';
  10.     printf("test1:%p test2:%p\n", test1.c_str(), test2.c_str());
  11. }
复制代码

运行结果:
test1:0x9e85014 test2:0x9e85014
test1:0x9e85014 test2:0x9e8502c

可以看到test2发生了变化。
再进一步,编译如何确定程序要对buffer进行修改,从而去开辟新的空间呢?
程序一般是通过[]运算符、iterator去访问并修改数据。很自然地认为,对于左值会引起数据复制,而右值不会。但实际上,编译没这么做。可能是左值或右值的判定并没有那么简单吧?
  1. #include <stdio.h>
  2. #include <string>
  3. using namespace std;

  4. int main()
  5. {
  6.     string test1 = "hello";
  7.     string test2(test1);

  8.     printf("test1:%p   test2:%p\n", test1.c_str(), test2.c_str());
  9.      printf("test1:%p   test2:%p\n", &test1[0], &test2[0]);
  10. }
复制代码

运行结果:
test1:0x8a4a014   test2:0x8a4a014
test1:0x8a4a014   test2:0x8a4a02c

test2发生了变化。
看一下源码:
  1.       const_reference
  2.       operator[] (size_type __pos) const
  3.       {
  4.         _GLIBCXX_DEBUG_ASSERT(__pos <= size());
  5.         return _M_data()[__pos];
  6.       }

  7.       reference
  8.       operator[](size_type __pos)
  9.       {
  10.         _GLIBCXX_DEBUG_ASSERT(__pos < size());
  11.         _M_leak();
  12.         return _M_data()[__pos];
  13.       }
复制代码

也就是说判定是否可能有写操作是与类的类型相关的,如果是const string,则不复制,如果是string,则一定复制
再看看这个:
  1. #include <stdio.h>
  2. #include <string>
  3. using namespace std;

  4. int main()
  5. {
  6.     string test1 = "hello";
  7.     string test2(test1);

  8.     printf("test1:%p test2:%p\n", test1.c_str(), test2.c_str());
  9.     const string &test3 = test1;
  10.     const string &test4 = test2;
  11.     printf("test1:%p test2:%p\n", &test3[0], &test4[0]);
  12. }
复制代码

结果就是:
test1:0x8c62014  test2:0x8c62014
test1:0x8c62014  test2:0x8c62014

当然这样写很难受,凭什么要搞两个const的引用出来啊?
这样就比较自然:
  1. #include <stdio.h>
  2. #include <string>
  3. using namespace std;

  4. void proc(const string& test1, const string& test2)
  5. {
  6.     printf("test1:%p test2:%p\n", &test1[0], &test2[0]);
  7. }

  8. int main()
  9. {
  10.     string test1 = "hello";
  11.     string test2(test1);

  12.     printf("test1:%p test2:%p\n", test1.c_str(), test2.c_str());
  13.     proc(test1, test2);
  14. }
复制代码

也是说一定要严格地确定数据类型是否是const的,如果函数里不修改修,则传const,良好的习惯有利于代码质量的提高。
string和char *是无法共享数据区的,所以用c++就尽量少用指针,两种风格合在一起,效率是最低的。

[ 本帖最后由 yuxh 于 2006-9-26 15:45 编辑 ]

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
2 [报告]
发表于 2006-09-26 15:42 |只看该作者
年纪越来越老,是不是越来越啰嗦了呢?
但一般对于vector或者list是没有COW的,要拷贝就全拷。
但可以自己封装,随便写了一个,还不是很完善:
  1. #ifndef _COW_CONTAINER_
  2. #define _COW_CONTAINER_ 1

  3. #include <vector>
  4. #include <list>
  5. using namespace std;

  6. template<typename _Tp>
  7. class cow_container
  8. {
  9. public:
  10.   typedef typename _Tp::value_type value_type;
  11.   typedef typename _Tp::reference reference;
  12.   typedef typename _Tp::const_reference const_reference;
  13.   typedef typename _Tp::iterator iterator;
  14.   typedef typename _Tp::const_iterator const_iterator;
  15.   cow_container()
  16.   {
  17.     m_pCowNode = new cow_node;
  18.     m_pCowNode->m_refCount = 0;
  19.   }
  20.   cow_container(const cow_container& __cc)
  21.   {
  22.     m_pCowNode = __cc.m_pCowNode;
  23.     m_pCowNode->m_refCount++;
  24.   }
  25.   cow_container(const_iterator _begin, const_iterator _end)
  26.   {
  27.     m_pCowNode = new cow_node;
  28.     m_pCowNode->m_refCount = 0;
  29.     const_iterator itr;
  30.     for(itr = _begin(); itr != _end; ++itr) {
  31.       m_pCowNode->m_data.push_back(*itr);
  32.     }
  33.   }
  34.   cow_container(const_iterator _begin, size_t _n)
  35.   {
  36.     m_pCowNode = new cow_node;
  37.     m_pCowNode->m_refCount = 0;
  38.     const_iterator itr;
  39.     for(itr = _begin(); itr < _begin + _n; ++itr) {
  40.       m_pCowNode->m_data.push_back(*itr);
  41.     }
  42.   }
  43.   cow_container(const _Tp& __cc)
  44.   {
  45.     m_pCowNode = new cow_node;
  46.     m_pCowNode->m_refCount = 0;
  47.     m_pCowNode->m_data = __cc;
  48.   }
  49.   ~cow_container()
  50.   {
  51.     if(m_pCowNode->m_refCount == 0)
  52.       delete m_pCowNode;
  53.     else
  54.       m_pCowNode->m_refCount--;
  55.   }
  56.   cow_container &operator=(const cow_container& __cc)
  57.   {
  58.     if(m_pCowNode != __cc.m_pCowNode) {
  59.       if(m_pCowNode->m_refCount == 0)
  60.         delete m_pCowNode;
  61.       else
  62.         m_pCowNode->m_refCount--;
  63.       m_pCowNode = __cc.m_pCowNode;
  64.       m_pCowNode->m_refCount++;
  65.     }
  66.     return *this;
  67.   }
  68.   cow_container &operator=(const _Tp& __cc)
  69.   {
  70.     if(m_pCowNode != __cc.m_pCowNode) {
  71.       if(m_pCowNode->m_refCount == 0)
  72.         delete m_pCowNode;
  73.       else
  74.         m_pCowNode->m_refCount--;
  75.       m_pCowNode = new cow_node;
  76.       m_pCowNode->m_refCount = 0;
  77.       m_pCowNode->m_data = __cc;
  78.     }
  79.     return *this;
  80.   }
  81.   const_iterator begin() const
  82.   {
  83.     return m_pCowNode->m_data.begin();
  84.   }
  85.   const_iterator end() const
  86.   {
  87.     return m_pCowNode->m_data.end();
  88.   }
  89.   iterator begin()
  90.   {
  91.     do_copy();
  92.     return m_pCowNode->m_data.begin();
  93.   }
  94.   iterator end()
  95.   {
  96.     do_copy();
  97.     return m_pCowNode->m_data.end();
  98.   }
  99.   const_reference operator[](int _n) const
  100.   {
  101.     return m_pCowNode->m_data[_n];
  102.   }
  103.   reference operator[](int _n)
  104.   {
  105.     do_copy();
  106.     return m_pCowNode->m_data[_n];
  107.   }
  108.   void push_back(const value_type& _val)
  109.   {
  110.     do_copy();
  111.     m_pCowNode->m_data.push_back(_val);
  112.   }
  113.   iterator insert(iterator __position, const value_type& __x)
  114.   {
  115.     do_copy();
  116.     m_pCowNode->m_data.insert(__position, __x);
  117.   }

  118.   const _Tp& container() const
  119.   {
  120.     return m_pCowNode->m_data;
  121.   }
  122. private:
  123.   struct cow_node
  124.   {
  125.     int          m_refCount;
  126.     _Tp          m_data;
  127.   };
  128.   cow_node     *m_pCowNode;

  129.   void do_copy()
  130.   {
  131.     if(m_pCowNode->m_refCount > 0) {
  132.       const _Tp &bak = m_pCowNode->m_data;
  133.       m_pCowNode->m_refCount--;
  134.       m_pCowNode = new cow_node;
  135.       m_pCowNode->m_refCount = 0;
  136.       m_pCowNode->m_data = bak;
  137.     }
  138.   }
  139. };

  140. template<class _Tp>
  141. class VECTOR:public cow_container< vector<_Tp> > { };

  142. template<class _Tp>
  143. class LIST:public cow_container< list<_Tp> > { };

  144. #endif
复制代码

[ 本帖最后由 yuxh 于 2006-9-26 15:48 编辑 ]

论坛徽章:
0
3 [报告]
发表于 2006-09-26 20:30 |只看该作者
我的直觉string是靠RefCount来判断是否COW, 看来得在查查.
reve 该用户已被删除
4 [报告]
发表于 2006-09-26 23:12 |只看该作者
提示: 作者被禁止或删除 内容自动屏蔽

论坛徽章:
38
2017金鸡报晓
日期:2017-02-08 10:39:4215-16赛季CBA联赛之深圳
日期:2023-02-16 14:39:0220周年集字徽章-年
日期:2022-08-31 14:25:28黑曼巴
日期:2022-08-17 18:57:0919周年集字徽章-年
日期:2022-04-25 13:02:5920周年集字徽章-20	
日期:2022-03-29 11:10:4620周年集字徽章-年
日期:2022-03-14 22:35:1820周年集字徽章-周	
日期:2022-03-09 12:51:3220周年集字徽章-年
日期:2022-02-10 13:13:4420周年集字徽章-周	
日期:2022-02-03 12:09:4420周年集字徽章-20	
日期:2022-01-25 20:14:2720周年集字徽章-周	
日期:2022-01-13 15:12:33
5 [报告]
发表于 2006-09-26 23:21 |只看该作者
楼主总能给我们带来惊喜,感谢。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP