当字符串保存在集合中时,c_str() 是不是仍然有效

Posted

技术标签:

【中文标题】当字符串保存在集合中时,c_str() 是不是仍然有效【英文标题】:Is c_str() still valid when the string is kept in a set当字符串保存在集合中时,c_str() 是否仍然有效 【发布时间】:2019-03-06 09:46:09 【问题描述】:

我有一个旧项目需要维护,它使用了 const char *。出于某种原因,我想保留大量运行时生成的字符串。所以我创建了一个全局变量 std::set 来保存这些字符串。当生成一个新字符串时,除了要添加到集合中外,我还会返回并发送 newString.c_str() 并将其保存在其他地方。例如。

std::set<std::string> g_stringDB;
void ArchieveString( AStruct *container, const char *temporaryString )

    auto it = g_stringDB.emplace( temporaryString );
    container->validString = it->first->c_str();

我想知道容器何时被外部使用(我的意思是这个功能之外的任何地方)。如果指针:validString 仍然安全。由于复制,指针是否可能已经指向其他东西,构造发生在集合内? 如果不是,实现此要求的理想方法是什么?

【问题讨论】:

Iterator invalidation rules的可能重复 @mch 迭代器失效的规则不同。 有两个注意事项。将元素添加到 std::set 不会使其任何迭代器失效,包括指向其元素的指针。但是,调整 std::string 的大小可能会使迭代器(包括指向其元素的指针)无效。因此,如果将 astd::string 添加到集合中,则使用返回其 c_str() 的指针是安全的,但前提是该字符串随后从未调整大小。但是,除了您的问题之外,使用静态变量和指向由该静态管理的数据的单独指针存储库在几个方面都是一个脆弱的设计。我会考虑重新架构。 【参考方案1】:

c_str() 返回无效的规则是:

将对字符串的非常量引用传递给任何标准库函数,或者

在字符串上调用非常量成员函数,不包括operator[]、at()、front()、back()、begin()、rbegin()、end()和rend()。

对于集合元素,由于迭代器没有失效,你很好,字符串对象没有改变。

所以如果字符串是固定的,那么你就可以了。

【讨论】:

要添加到@Peter 评论,请注意:std::set 的迭代器都是 const 迭代器。这意味着字符串不能在集合中调整一次。 (见 23.2.4 - 6 - For associative containers where the value type is the same as the key type, both iterator and const_iterator are constant iterators)。然而,它可以被删除,这是一个问题。该设计仍然很脆弱,无法证明未来...... 同意。由于他们可能没有更改遗留代码的绿灯,这​​可能是推动修复它的第一步。关于如何改进设计有很多未知数。也许在随后的问题中? 这不是关于 iterator 失效,而是指针/引用失效或对您提到的对象的可变访问(例如 std::vector&lt;int&gt; v = ...; v[i] = 5; 它既不会使迭代器也不会使引用失效,而是会更改元素)。有些容器在某些操作上提供指针/引用稳定性而没有迭代器稳定性(例如push_front()push_back() 上的deque),理论上,可以编写具有相反保证的容器。【参考方案2】:

如果满足几个条件,它可能是安全的。

首先根据cppreference std::basic_string::c_str()

从 c_str() 中获得的指针可能会通过以下方式失效:

    将对字符串的非常量引用传递给任何标准库 函数, 或在字符串上调用非常量成员函数, 不包括 operator[]、at()、front()、back()、begin()、rbegin()、end() 和rend()。

所以,如果这些都没有发生,使用是安全的。上述事情也可能通过赋值运算符、析构函数或任何其他使对 std::set&lt;std::string&gt; 元素的引用无效的事情发生。

不会使这些引用无效的事情是(或在非常特定的情况下无效):

    std::set::insert() 如cppreference 中所述

    没有迭代器或引用无效。

    但是对于通过节点句柄 (C++17) 获得的元素,有一个更细粒度的语句,这是有道理的:

    如果插入成功,则在节点句柄中保存的元素的指针和引用无效,并且在提取之前获得的对该元素的指针和引用有效。 (C++17 起)

    std::set::erase 来自cppreference

    对已擦除元素的引用和迭代器无效。 其他引用和迭代器不受影响

    std::set::emplacestd::set::emplace_hint 都说

    没有迭代器或引用无效。

    std::set::extract:

    对提取元素的指针和引用仍然有效,但在元素由节点句柄拥有时不能使用:如果将元素插入容器,它们就会变得可用。

    这意味着重新插入后 c_str 字符串再次安全。但是,本文档没有提及其他参考资料。这可能是 cppreference 和/或标准中的缺陷。我希望看到有关该标准的评论。

    std::set::merge:

    所有指向传输元素的指针和引用仍然有效

所以,只要没有修改 set 中的对象,您就应该是安全的。确保通过阅读上面的列表。

【讨论】:

以上是关于当字符串保存在集合中时,c_str() 是不是仍然有效的主要内容,如果未能解决你的问题,请参考以下文章

为啥我仍然可以在字符串范围之外访问 std::string::c_str() 返回的 char 指针? [复制]

页面位于 iFrame 中时未保存会话变量

当正则表达式的某些部分要保存在后续的分割字符串中时,如何使用正则表达式在R中拆分字符串?

连接字符串文字上的 c_str() 是不是安全?

当图像在模态div中时,IOS长按没有“保存图像”选项

当我们保存在 NSUserDefaults 中时,从核心数据和应用程序中获取“<NULL>”数据会崩溃?