首页 > 学技术 > 技术网文 > C/C++ > 正文

[精华] 关于写时复制(COW)


来源 chinaunix.net kuqin整理

大部份的STL在实现string时,都采用COW来保证其高效性。即多个类会共用一个数据缓冲区(buffer),在拷贝构造、赋值等操作时,并不会对buffer进行复制。仅在需要对buffer进行修改,而且此buffer已与别的类共享了,才会开辟空间,将buffer复制一份进行修改。同样在析构时,如果buffer与与别的类共享,也不会释放空间。
举个例子:
#include <stdio.h>

#include <string>
using namespace std;

int main()
{
    string test1 = "hello";
    string test2(test1);

    printf("test1:%p test2:%p\n", test1.c_str(), test2.c_str());
}

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

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

#include <string>
using namespace std;

int main()
{
    string test1 = "hello";
    string test2(test1);

    printf("test1:%p test2:%p\n", test1.c_str(), test2.c_str());
    test2[0] = 'w';
    printf("test1:%p test2:%p\n", test1.c_str(), test2.c_str());
}

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


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

#include <string>
using namespace std;

int main()
{
    string test1 = "hello";
    string test2(test1);

    printf("test1:%p   test2:%p\n", test1.c_str(), test2.c_str());
     printf("test1:%p   test2:%p\n", &test1[0], &test2[0]);
}

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


test2发生了变化。
看一下源码:
      const_reference

      operator[] (size_type __pos) const
      {
        _GLIBCXX_DEBUG_ASSERT(__pos <= size());
        return _M_data()[__pos];
      }

      reference
      operator[](size_type __pos)
      {
        _GLIBCXX_DEBUG_ASSERT(__pos < size());
        _M_leak();
        return _M_data()[__pos];
      }

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

#include <string>
using namespace std;

int main()
{
    string test1 = "hello";
    string test2(test1);

    printf("test1:%p test2:%p\n", test1.c_str(), test2.c_str());
    const string &test3 = test1;
    const string &test4 = test2;
    printf("test1:%p test2:%p\n", &test3[0], &test4[0]);
}

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


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

#include <string>
using namespace std;

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

int main()
{
    string test1 = "hello";
    string test2(test1);

    printf("test1:%p test2:%p\n", test1.c_str(), test2.c_str());
    proc(test1, test2);
}

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

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



 yuxh 回复于:2006-09-26 15:42:29

年纪越来越老,是不是越来越啰嗦了呢?
但一般对于vector或者list是没有COW的,要拷贝就全拷。
但可以自己封装,随便写了一个,还不是很完善:
#ifndef _COW_CONTAINER_

#define _COW_CONTAINER_ 1

#include <vector>
#include <list>
using namespace std;

template<typename _Tp>
class cow_container
{
public:
  typedef typename _Tp::value_type value_type;
  typedef typename _Tp::reference reference;
  typedef typename _Tp::const_reference const_reference;
  typedef typename _Tp::iterator iterator;
  typedef typename _Tp::const_iterator const_iterator;
  cow_container()
  {
    m_pCowNode = new cow_node;
    m_pCowNode->m_refCount = 0;
  }
  cow_container(const cow_container& __cc)
  {
    m_pCowNode = __cc.m_pCowNode;
    m_pCowNode->m_refCount++;
  }
  cow_container(const_iterator _begin, const_iterator _end)
  {
    m_pCowNode = new cow_node;
    m_pCowNode->m_refCount = 0;
    const_iterator itr;
    for(itr = _begin(); itr != _end; ++itr) {
      m_pCowNode->m_data.push_back(*itr);
    }
  }
  cow_container(const_iterator _begin, size_t _n)
  {
    m_pCowNode = new cow_node;
    m_pCowNode->m_refCount = 0;
    const_iterator itr;
    for(itr = _begin(); itr < _begin + _n; ++itr) {
      m_pCowNode->m_data.push_back(*itr);
    }
  }
  cow_container(const _Tp& __cc)
  {
    m_pCowNode = new cow_node;
    m_pCowNode->m_refCount = 0;
    m_pCowNode->m_data = __cc;
  }
  ~cow_container()
  {
    if(m_pCowNode->m_refCount == 0)
      delete m_pCowNode;
    else
      m_pCowNode->m_refCount--;
  }
  cow_container &operator=(const cow_container& __cc)
  {
    if(m_pCowNode != __cc.m_pCowNode) {
      if(m_pCowNode->m_refCount == 0)
        delete m_pCowNode;
      else
        m_pCowNode->m_refCount--;
      m_pCowNode = __cc.m_pCowNode;
      m_pCowNode->m_refCount++;
    }
    return *this;
  }
  cow_container &operator=(const _Tp& __cc)
  {
    if(m_pCowNode != __cc.m_pCowNode) {
      if(m_pCowNode->m_refCount == 0)
        delete m_pCowNode;
      else
        m_pCowNode->m_refCount--;
      m_pCowNode = new cow_node;
      m_pCowNode->m_refCount = 0;
      m_pCowNode->m_data = __cc;
    }
    return *this;
  }
  const_iterator begin() const
  {
    return m_pCowNode->m_data.begin();
  }
  const_iterator end() const
  {
    return m_pCowNode->m_data.end();
  }
  iterator begin()
  {
    do_copy();
    return m_pCowNode->m_data.begin();
  }
  iterator end()
  {
    do_copy();
    return m_pCowNode->m_data.end();
  }
  const_reference operator[](int _n) const
  {
    return m_pCowNode->m_data[_n];
  }
  reference operator[](int _n)
  {
    do_copy();
    return m_pCowNode->m_data[_n];
  }
  void push_back(const value_type& _val)
  {
    do_copy();
    m_pCowNode->m_data.push_back(_val);
  }
  iterator insert(iterator __position, const value_type& __x)
  {
    do_copy();
    m_pCowNode->m_data.insert(__position, __x);
  }

  const _Tp& container() const
  {
    return m_pCowNode->m_data;
  }
private:
  struct cow_node
  {
    int          m_refCount;
    _Tp          m_data;
  };
  cow_node     *m_pCowNode;

  void do_copy()
  {
    if(m_pCowNode->m_refCount > 0) {
      const _Tp &bak = m_pCowNode->m_data;
      m_pCowNode->m_refCount--;
      m_pCowNode = new cow_node;
      m_pCowNode->m_refCount = 0;
      m_pCowNode->m_data = bak;
    }
  }
};

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

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

#endif


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


 Alligator27 回复于:2006-09-26 20:30:02

我的直觉string是靠RefCount来判断是否COW, 看来得在查查. :)


 reve 回复于:2006-09-26 23:12:47

明白~~


 醉卧水云间 回复于:2006-09-26 23:21:33

楼主总能给我们带来惊喜,感谢。




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=834292
转载请注明作者名及原文出处



收藏本页到: