std::string的拷贝赋值研究

说明:以下涉及的std::string的源代码摘自4.8.2版本。
结论:std::string的拷贝复制是基于引用计数的浅拷贝,因此它们指向相同的数据地址。

// std::string类定义
typedef basic_string string;
template
class basic_string
{
private:
    // _Alloc_hider是模板类basic_string内嵌struct
    struct _Alloc_hider : _Alloc
    {
        //  唯一构造函数,
        // 在构造时使用第一个参数__dat初始化_M_p
        _Alloc_hider(_CharT* __dat, const _Alloc& __a)
            : _Alloc(__a), _M_p(__dat)
       {}

        // _M_p为实际存储数据的地方
        _CharT* _M_p; // The actual data.
    };

private:
    _CharT* _M_data() const
    { return  _M_dataplus._M_p; }

    // 浅拷贝,
    // 这正是x2=x1后,两者数据地址相同的原因
    _CharT* _M_data(_CharT* __p)
    { return (_M_dataplus._M_p = __p); }

    _Rep* _M_rep() const
    {
        // 这里数组下标是“-1”
        return &((reinterpret_cast<_Rep*>(_M_data()))[-1]);
    }

    // 维护引用计数
    struct _Rep_base
    {
        size_type _M_length;
        size_type _M_capacity;
        _Atomic_word _M_refcount;
    };

    // _Rep是模板类basic_string内嵌struct
    struct _Rep : _Rep_base
    {
        // The following storage is init'd to 0 by the linker, 
        // resulting (carefully) in an empty string with one reference.
        // 空的std::string实际都指向了_S_empty_rep_storage,
        // 因此它们的数据地址是相同的
        static size_type _S_empty_rep_storage[];

        static _Rep& _S_empty_rep()
        {
            void* __p = reinterpret_cast(&_S_empty_rep_storage);
            return *reinterpret_cast<_Rep*>(__p);
        }
        
        _CharT* _M_grab(const _Alloc& __alloc1, const _Alloc& __alloc2)
        {
          return (!_M_is_leaked() && __alloc1 == __alloc2)
                  ? _M_refcopy() : _M_clone(__alloc1);
        }
        
        _CharT* _M_refcopy() throw()
        {
        #if _GLIBCXX_FULLY_DYNAMIC_STRING == 0
            if (__builtin_expect(this != &_S_empty_rep(), false))
        #endif
            __gnu_cxx::__atomic_add_dispatch(&this->_M_refcount, 1);
            return _M_refdata();
        }  // XXX MT
        
        _CharT* _M_refdata() throw()
        { return reinterpret_cast<_CharT*>(this + 1); }
    };

public:
    static _Rep& _S_empty_rep()
    {
        return _Rep::_S_empty_rep();
    }

    // 不带参数的默认构造函数
    // 测试环境_GLIBCXX_FULLY_DYNAMIC_STRING值为0,
    // 因此只需要关注_S_empty_rep
    basic_string()
#if _GLIBCXX_FULLY_DYNAMIC_STRING == 0
        : _M_dataplus(_S_empty_rep()._M_refdata(), _Alloc())
        { }
#else
        : _M_dataplus(_S_construct(size_type(), _CharT(), _Alloc()), _Alloc())
        { }
#endif

    basic_string& assign(const basic_string& __str)
    {
        // 如果已经相同,则什么也不用做
        if (_M_rep() != __str._M_rep())
        {
            const allocator_type __a = this->get_allocator();
            _CharT* __tmp = __str._M_rep()->_M_grab(__a, __str.get_allocator());
            _M_rep()->_M_dispose(__a);
            _M_data(__tmp);
        }
        
        return *this;
    }

#if __cplusplus >= 201103L
    basic_string& assign(basic_string&& __str)
    {
        this->swap(__str);
        return *this;
    }
#endif // C++11

    basic_string& operator=(const basic_string& __str)
    { 
        return this->assign(__str); 
    }

private:
    // mutable表明const成员函数会修改_M_dataplus
    mutable _Alloc_hider _M_dataplus;
};

// 测试代码
// 编译命令:
// g++ -g -o x x.cpp -D_GLIBCXX_DEBUG
#include
#include

// 如果没有为结构X提供赋值函数,
// 则编译器生成默认的赋值函数
struct X {
    std::string str;
};

int main() {
    struct X x1, x2;
    x1.str = "abc";
    // X2指向的_S_empty_rep_storage
    printf("%p, %p\n", x1.str.c_str(), x2.str.c_str());
    
    // (gdb) p x1.str._M_dataplus._M_p
    // (gdb) p x2.str._M_dataplus._M_p
    // 拷贝赋值函数采用的是引用计数,
    // 所以x1和x2的数据地址是相同的
    x2 = x1;
    printf("%p, %p\n", x1.str.c_str(), x2.str.c_str());

    // 下面输出的x1和x2数据地址必然不同
    x2.str = "123";
    printf("%p, %p\n", x1.str.c_str(), x2.str.c_str());
    return 0;
}
 

你可能感兴趣的:(C/C++)