使用地图提取和重新插入的限制性规则的基本原理

2024-04-17

从 C++17 开始,关联容器 https://en.cppreference.com/w/cpp/container支持节点的提取及其重新插入(可能插入到相同类型的另一个容器中)。返回的对象extract(key) is a 节点句柄 https://en.cppreference.com/w/cpp/container/node_handle,它是仅移动的并且对于地图容器来说具有成员函数

key_type &key() const;
mapped_type &mapped() const;

它不仅允许改变映射类型,还允许改变键。这可用于更改密钥无需重新分配(示例取自的文档map::extract() https://en.cppreference.com/w/cpp/container/map/extract):

std::map<int, std::string> map{{1,"mango"}, {2,"papaya"}, {3,"guava"}};
auto handle = map.extract(2);
handle.key() = 4;
map.insert(move(handle));

据我所理解,map被实现为二叉搜索树,而map::extract()取消节点与树的链接,并通过节点句柄返回指向该节点的指针,该句柄接管所有权。之上map::insert(),该节点重新链接到树中,并且所有权再次由映射接管。

因此,节点(以及存储的key and mapped_type)在此过程中不会重新分配、移动或复制。这标准说 http://eel.is/c++draft/associative.reqmts#10(我的高光):

extract 成员仅使已删除元素的迭代器无效; 指向已删除元素的指针和引用仍然有效。然而, 通过此类指针和引用访问元素,而 元素由 node_type 拥有未定义的行为。参考文献和 指向由 node_type 拥有的元素的指针是无效的如果元素插入成功。

我的问题:(1) 使 UB 通过其地址访问提取的元素以及 (2) 在插入时使在提取状态中获取的地址无效,背后的基本原理是什么?

恕我直言,这种提取和插入惯用法可以通过一种始终保持元素地址有效的方式来实现(直到销毁,如果元素从未重新插入,则可能会在映射销毁之前发生)。下面的代码

#include <map>
#include <string>
#include <iostream>

struct immovable_string : std::string
{
    immovable_string(const char*s) : std::string(s) {}
    immovable_string() = default;
    immovable_string(immovable_string const&) = delete;
    immovable_string(immovable_string &&) = delete;
    immovable_string&operator=(immovable_string const&) = delete;
    immovable_string&operator=(immovable_string &&) = delete;
};

int main()
{
    std::map<int,immovable_string> map;

    map.emplace(1,"mango");
    map.emplace(2,"papaya");
    map.emplace(3,"guava");

    std::cout << "initially:     "
              << " address=" << std::addressof(map[2])
              << " value=" << map[2] <<'\n';

    auto handle = map.extract(2);

    std::cout << "after extract: "
              << " address=" << std::addressof(handle.mapped())
              << " value=" << handle.mapped() <<'\n';

    handle.key() = 4;
    map.insert(move(handle));

    std::cout << "after insert:  "
              << " address=" << std::addressof(map[4])
              << " value=" << map[4] <<'\n';
}

编译(使用 gcc 8.2.0-std=c++17)并给出输出

initially:      address=0x7f9e06c02738 value=papaya
after extract:  address=0x7f9e06c02738 value=papaya
after insert:   address=0x7f9e06c02738 value=papaya

正如预期的那样(获得了相同的结果std::string代替immovable_string and/or unordered_map代替map).


Edit

请注意,我是不问关于修改相关问题key (map stores pair<const Key,T>).

我的问题只是关于通过指针或引用访问映射元素的限制。提取和插入习惯用法的整个思想使得only检测元素是否未被移动/复制,即其地址是否始终保持有效(实际上是由标准指定的)。在提取状态 UB 下渲染对元素的访问似乎很奇怪并且使得提取和插入机制不再那么有用:考虑一下多线程代码,一个线程访问一个元素,而另一个线程提取并重新插入它。这可以在没有任何问题的情况下实现,但可能会调用 UB --WHY?

这是一个 UB 场景(恕我直言,完全没问题,不需要 UB):

void somefunc(object*ptr) { ptr->do_something(); }
void re_key(map<int,object> &M, int oldKey, int newKey)
{
    if(M.find(0)!=M.end() && M.find(newKey)==M.end()) {
        auto handle = M.extract(0);
        handle.key() = newKey;
        M.insert(std::move(handle));
    }
}

map<int,object> M = fillMap();
auto ptr = addressof(M[0]);     // takes initial address
thread t1(somefunc,ptr);        // uses said address to access object
thread t2(re_key,M,7);          // extracts and inserts an object 

当然,如果insert()失败,则handle被破坏,地址失效。这是显而易见的,但用户可以对此做一些事情。


我认为“提取”系统的主要微妙之处在于value_type of a map<K, T> is pair<const K, T>— 注意const!

修改 const 对象会导致未定义的行为,因此您需要非常小心,不要修改已知为 const 的内容。虽然节点是任何映射的一部分,但键是常量。提取机械的“魔力”(以及花了这么长时间来指定的原因)在于当节点被提取时,关键是not const.

这基本上要求你认真地看待问题并说服自己pair<const K, T>有时可以解释为pair<K, T>(并记住pair是一个允许用户专门化的模板!)。因此,为了避免 const 对象被修改的任何可能性,任何节点的插入和提取状态都必须有明确的顺序。

[container.node.overview]p4 中有标准措辞可以帮助解决专业化问题:

如果用户定义的专业化pair存在于pair<const Key, T> or pair<Key, T>, where Key是个 容器的key_type and T是容器的mapped_type,涉及节点句柄的操作的行为是未定义的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用地图提取和重新插入的限制性规则的基本原理 的相关文章

  • 添加 Nullable int 时保持 null?

    我想添加可为空的int 并保留null当所有值都是null 我想要这个结果 1 2 3 1 null 1 null null null O null 0 问题是 如果我将一个值与 null 相加 结果为 null int i1 1 int
  • 检测到堆栈崩溃

    我正在执行我的 a out 文件 执行后 程序运行一段时间 然后退出并显示消息 stack smashing detected a out terminated Backtrace lib tls i686 cmov libc so 6 f
  • 在开关中使用“goto”?

    我看到了一个建议的编码标准 内容如下Never use goto unless in a switch statement fall through 我不跟 这个 例外 案例到底是什么样的 这证明了goto 此构造在 C 中是非法的 swi
  • Gwan C#,如何获取HTTP标头?

    我需要它来重写 url 以了解我正在处理哪个友好的 url 用于用户代理和其他东西 EDIT public class Gwan MethodImplAttribute MethodImplOptions InternalCall exte
  • 将表(行)与 OpenXML SDK 2.5 保持在一起

    我想在 Word 文档中生成多个表 每行 2 行 但我想将这两行保留在一起 如果可能的话 new KeepNext 第一行不起作用 new KeepNext 第一行的最后一段不起作用 new CantSplit 放在桌子上不起作用 在所有情
  • 增强精神、递归和堆栈溢出

    为什么下面的代码在运行时崩溃 它会给出堆栈溢出错误 include
  • UI 函数在快速事件完成之前触发

    我有一个停靠在 Silverlight 应用程序中的 Web 浏览器框架 有时会在其上弹出全窗口 XAML Silverlight UI 元素 我已经或多或少修复了一个老问题 即 Web 框架的内容似乎与 Silverlight 内容不能很
  • 使用 C# 和 wpf 创建类似 Dock 的应用程序

    我需要创建一个与我们购买笔记本电脑时获得的应用程序类似的应用程序 仅当鼠标指针到达窗口顶部时它才可见 那么我怎样才能使用 C 4 0 来做到这一点呢 http www notebookcheck net uploads pics win2
  • 如何对 NServiceBus.Configure.WithWeb() 进行单元测试?

    我正在构建一个 WCF 服务 该服务接收外部 IP 上的请求并将其转换为通过 NServiceBus 发送的消息 我的单元测试之一调用Global Application Start 它执行应用程序的配置 然后尝试将 Web 服务解析为 验
  • 在 asp.net MVC 中使用活动目录进行身份验证

    我想使用活动目录对我的 asp net mvc 项目中的用户进行身份验证 在网上冲浪了几个小时后 我没有找到任何对我有用的东西 我已经看到了所有结果 但什么也没有 我尝试按照许多帖子的建议编辑我的 web config 如果有人可以帮助我提
  • 搜索实体的所有字段

    我正在尝试在客户数据库上实现 多功能框 类型的搜索 其中单个查询应尝试匹配客户的任何属性 这是一些示例数据来说明我想要实现的目标 FirstName LastName PhoneNumber ZipCode Mary Jane 12345
  • Project Euler #8,我不明白我哪里出了问题[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我正在做项目欧拉第八题 https projecteuler net problem 8 其中我得到了这个大得离谱的数字 7316
  • 英文日期差异

    接近重复 如何计算相对时间 https stackoverflow com questions 11 how do i calculate relative time 如何在 C 中计算某人的年龄 https stackoverflow c
  • 从浏览器访问本地文件?

    您好 我想从浏览器访问系统的本地文件 由于涉及大量安全检查 是否可以通过某种方式实现这一目标 或使用 ActiveX 或 Java Applet 的任何其他工作环境 请帮帮我 要通过浏览器访问本地文件 您可以使用签名的 Java Apple
  • 逆向工程 ASP.NET Web 应用程序

    我有一个 ASP NET Web 应用程序 我没有源代码 该 bin 包含 10 个程序集和一个 compiled 文件 我在 App Code dll 上使用 Reflector 它向我显示了类和命名空间之类的东西 但它太混乱了 有没有什
  • CUDA 8 编译错误 -std=gnu++11

    我正在尝试转换一些代码以使用 CUDA 并且我认为我遇到了兼容性问题 我们使用CMake 这些是我使用的 gcc 和 CUDA 版本 gcc version gcc Ubuntu 5 4 0 6ubuntu1 16 04 5 5 4 0 2
  • 使用 using 声明时,非限定名称查找如何工作?

    根据 C 标准 这是格式错误还是格式良好 namespace M struct i namespace N static int i 1 using M i using N i int main sizeof i Clang 拒绝它 GCC
  • 如何将 SQL“LIKE”与 LINQ to Entities 结合使用?

    我有一个文本框 允许用户指定搜索字符串 包括通配符 例如 Joh Johnson mit ack on 在使用 LINQ to Entities 之前 我有一个存储过程 该存储过程将该字符串作为参数并执行以下操作 SELECT FROM T
  • 结构化绑定的用例有哪些?

    C 17 标准引入了新的结构化绑定 http en cppreference com w cpp language structured binding功能 最初是proposed http www open std org jtc1 sc
  • 为什么匹配模板类上的部分类模板特化与没有模板匹配的另一个部分特化不明确?

    这个问题可能很难用标题中的句子来描述 但这里有一个最小的例子 include

随机推荐