为何string调用swap导致迭代器失效

为何string调用swap导致迭代器失效

前言

swap操作交换两个相同类型的容器的内容,一般的容器(除array外),交换两个容器内容的操作会保证非常快,因为并没有交换元素本身,而只是交换了两个容器的内部数据结构(string不一定)。

注:这里交换两个容器的内部数据结构是指交换容器中各元素的内存地址,并不是交换各个元素变量所存储的值。除string外,swap使用之后指向容器的指针、引用和迭代器均有效(即开始指向那个元素,在swap之后这个指针、引用和迭代器依然指向该元素)。

除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。

现对string进行swap操作导致迭代器失效展开论述。

讨论

和其它容器不同的是,对string调用swap会导致迭代器、引用和指针失效。因为string存储的是字符串,在string变量中真正存储字符串的是一个叫_Ptr的指针,它指向string所存储的字符串首地址,而字符串并没有固定地址,而是存储在一个临时内存区域中,所以当字符串发生改变时,会发生内存的重新分配,所以会导致迭代器、引用和指针失效。

在此插一嘴,讨论string在内存中的存储位置。

string的存储位置

string字符串小于等于15字节时存储在栈上,大于15字节时候存储在堆上(是否是15这个界限,可以在自己的编译环境下测试)。

这其实又涉及到C++对string存储的一个短字符串优化(Short String Optimization,简称SSO)问题,稍后讨论。

windows下测试

void test3()
{
    string s1{ "123" };
    string s2{ "45678" };
    auto it1 = s1.begin();
    auto it2 = s2.begin();
    auto &a=s1[1];
    cout << "\n交换前s1每个元素的地址和值为:";
    for (const auto &it : s1)
    {
        cout << &it << " " << it << " ";
    }
    cout << "\n交换前s2每个元素的地址和值为:";
    for (const auto &it : s2)
    {
        cout << &it << " " << it << " ";
    }
    cout<<endl;
    cout << "交换前迭代器it1指向的元素为:" <<&it1<<"  "<<*it1 << endl;
    cout << "交换前迭代器it2指向的元素为:" <<&it2<<"  "<<*it2 << endl;
    cout << "交换前迭代器a指向的元素为:" << a << endl;
    cout<<s1.size();
    swap(s1, s2);
//    s1.swap(s2);
    cout << endl << endl;
    cout << "\n交换后s1每个元素的地址和值为:";
    for (const auto &it : s1)
    {
        cout << &it << " " << it << " ";
    }
    cout << "\n交换后s2每个元素的地址和值为:";
    for (const auto &it : s2)
    {
        cout << &it << " " << it << " ";
    }
    cout<<endl;
    cout << "交换后迭代器it1指向的元素为:" <<&it1<< "  "<<*it1 << endl;
    cout << "交换后迭代器it2指向的元素为:" <<&it2<< "  "<<*it2 << endl;
    cout << "交换后迭代器a指向的元素为:" << a << endl;
    return 0;
}

swap之前的各变量内存窗口:

在这里插入图片描述

swap之后的发生编译错误,显示迭代器已经失效了,效果图如下:
在这里插入图片描述

Ubuntu下测试

在这里插入图片描述

因为Ubuntu中C++底层是基于g++与微软中使用的MSVC底层不一样。首先这里交换成功,表示这里string 的交换的是类似于Vector交换的是数据结构,而不是元素的值与array交换。

这里涉及到C++对string存储的SSO问题,首先看std::basic_string 对象的最基本布局

struct {
 CharT* ptr;
 size_type size;
 size_type capacity;
};

其中 ptr 是指向大小为 capacity的CharT的动态分配的array的指针,另外它可以含有一个长度为 size的字符串。倘若字符串很短,单独为其开辟内存过于浪费,于是直接存储在string对象中。

在这里插入图片描述

所以Ubuntu中,当两个string的size一个大于SSO容量15,一个小于15,那么大于15的string不是存储在string对象本身里面,将其复制给小于15的string时,就会是size小于15的string对象本身的字符串清空并单独开辟一个空间,所以导致size小于15的string的迭代器、引用、指针就会失效,而size大于15的string的迭代器没有失效。而且这里类似于Vector的交换,交换的是数据结构,而不是元素的值。

如以下程序所示

    string s1{ "123" };
    string s2{ "456788888888888888888" };
    auto it1 = s1.begin();
    auto it2 = s2.begin();
    auto &a=s1[1];
    cout << "交换前迭代器it1指向的元素为:" <<&it1<<"  "<<*it1 << endl;
    cout << "交换前迭代器it2指向的元素为:" <<&it2<<"  "<<*it2 << endl;
    cout << "交换前迭代器a指向的元素为:" << a << endl;
    cout<<s1.size();
    swap(s1, s2);
//    s1.swap(s2);
    cout << endl << endl;
    cout << "交换后迭代器it1指向的元素为:" <<&it1<< "  "<<*it1 << endl;
    cout << "交换后迭代器it2指向的元素为:" <<&it2<< "  "<<*it2 << endl;
    cout << "交换后迭代器a指向的元素为:" << a << endl;
    return 0;

在这里插入图片描述

可见MSVC下,要求更“严格”,直接报错,GCC下相对宽松。

其实最简单的回答就是「标准允许string.swap 使迭代器失效」。但换个角度讲,标准只是「允许失效」,而不是「要求失效」,所以「对string调用swap 会导致迭代器失效」的说法是不成立的(只针对具体某个实现成立)。标准之所以这样规定,是为了放宽对接口的要求,方便各个实现做优化。

References, pointers, and iterators referring to the elements of a basic_string sequencemay be invalidated by the following uses of that basic_string object:

  • as an argument to any standard library function taking a reference to non-constbasic_string as an argument.
  • References, pointers, and iterators referring to the elements of a basic_string sequencemay be invalidated by the following uses of that basic_string object:

引用自 C++11 §21.4.1/6、C++14 §21.4.1/5 版本,C++03 §21.3/6 措辞不同。
如果可能,看一看STL源码剖析,理解会更好。

参考书籍:C++prime(第五版中文)

参考博文:https://blog.csdn.net/ThorKing01/article/details/97273850,https://www.zhihu.com/question/57561910/answer/153425473

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值