【问题标题】:what happens when you modify an element of an std::set?当您修改 std::set 的元素时会发生什么?
【发布时间】:2010-10-28 21:07:28
【问题描述】:

如果我更改 std::set 的元素,例如,通过迭代器,我知道它不是“重新插入”或“重新排序”,但有没有提到它是否会触发未定义的行为?例如,我想插入会搞砸。有没有具体提到会发生什么?

【问题讨论】:

  • 鉴于迭代器 API 被设置为不允许它,您打算如何实现此修改? (编辑:looks 就像早在 2009 年一样,标准中存在一个缺陷,可能无法像现在那样防止这种情况发生)

标签: c++ stl collections set


【解决方案1】:

您不应直接编辑存储在集合中的值。我从具有一定权威性的 MSDN 文档中复制了这个:

使用 STL 容器类集 用于存储和检索数据 来自一个集合,其中的值 包含的元素是独一无二的 并作为关键值根据 数据自动传送到的位置 订购。 a中元素的值 set 不能直接更改。 相反,您必须删除旧值 并插入具有新值的元素。

为什么这很容易理解。 set 实现将无法知道您已在其背后修改了值。正常的实现是红黑树。更改了值后,该实例在树中的位置将是错误的。您可能会看到各种错误行为,例如 exists 查询返回错误结果,因为搜索沿着树的错误分支进行。

【讨论】:

  • "set 实现将无法知道你修改了它背后的值" 实际上,正如下面 coppro 所提到的,你不能通过正常的方式修改它背后的值,因为它迭代器是常量。这就是他们所说的“不能直接改变”的意思。
  • 我想象一个包含 shared_ptr 到我的底层对象的集合 - 然后你通过迭代器中对底层对象的引用调用一个 const 函数,该函数修改用于排序的可变成员。我认为当他们说“可能无法直接更改”时,他们的意思是“不要做这种疯狂的事情”,而不是“无法修改成员”
  • 在这种情况下,集合可能按存储在 shared_ptr 中的地址排序,而不是它正在使用的对象的任何属性(除非它使用自定义的比较函子)
【解决方案2】:

确切的答案取决于平台,但作为一般规则,“键”(您放入集合或地图的第一种类型的东西)应该是“不可变的”。简单来说就是不应该修改,也没有自动重新插入这种东西。

更准确地说,用于比较键的成员变量不得修改。

Windows vc 编译器非常灵活(用 VC8 测试过),这段代码可以编译:

// creation
std::set<int> toto;
toto.insert(4);
toto.insert(40);
toto.insert(25);

// bad modif
(*toto.begin())=100;

// output
for(std::set<int>::iterator it = toto.begin(); it != toto.end(); ++it)
{
    std::cout<<*it<<" ";
}
std::cout<<std::endl;

输出是100 25 40,明显没有排序……不好…… 不过,当您想要修改不参与 运算符 的数据时,这种行为还是很有用的。但您最好知道自己在做什么:这就是过于灵活的代价。

有些人可能更喜欢 gcc 行为(使用 3.4.4 测试),它会给出错误“分配只读位置”。您可以使用 const_cast 解决它:

const_cast<int&>(*toto.begin())=100;

现在也在 gcc 上编译,输出相同:100 25 40。 但至少,这样做可能会让你想知道发生了什么,然后去堆栈溢出看看这个线程:-)

【讨论】:

    【解决方案3】:

    你不能这样做;他们是const。没有任何方法可以让set 检测到您对内部元素进行了更改,因此您无法这样做。相反,您必须删除并重新插入该元素。如果您使用复制成本高昂的元素,您可能必须改用指针和自定义比较器(或改用支持右值引用的 C++1x 编译器,这会使事情变得更好)。

    【讨论】:

    • 如果你有一个引用或指向添加的项目的指针,那么在你添加它之后当然可以修改它的值。你也可以很容易地滥用 C++ 强制转换来实现令人讨厌的未定义行为。
    • @Mark:不,因为当您通过值传递项目以将它们放入容器中时,它们会被复制,因此您无法获得指向它们的指针,除非通过迭代器。
    • @newacct:迭代器提供参考,因此您可以强制转换。但你最好知道你在做什么。您可能想要更改不用于排序的字段,但这可能会发生变化。
    • 你也可以用修改它的 const 成员函数编写一个值类型(也许通过比较器使用的可变成员变量)。不是一个好主意,但编译器无法阻止你。
    猜你喜欢
    • 1970-01-01
    • 2018-07-07
    • 1970-01-01
    • 2017-09-06
    • 1970-01-01
    • 2018-02-07
    • 2014-02-02
    • 2013-11-26
    • 2019-05-27
    相关资源
    最近更新 更多