Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/sorting/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 使用map提取和重新插入的限制性规则的基本原理_C++_Language Lawyer_C++17_Undefined Behavior - Fatal编程技术网

C++ 使用map提取和重新插入的限制性规则的基本原理

C++ 使用map提取和重新插入的限制性规则的基本原理,c++,language-lawyer,c++17,undefined-behavior,C++,Language Lawyer,C++17,Undefined Behavior,自C++17以来,支持提取节点并将其重新插入(可能插入到相同类型的另一个容器中)。extract(key)返回的对象是a,它是仅移动的,并且对于地图容器具有成员函数 key_type &key() const; mapped_type &mapped() const; 它不仅允许修改映射类型,还允许修改密钥。这可用于在不重新分配的情况下更改键(示例取自): 编译(使用gcc 8.2.0使用-std=c++17)并给出输出 initially: address=0x7f

自C++17以来,支持提取节点并将其重新插入(可能插入到相同类型的另一个容器中)。
extract(key)
返回的对象是a,它是仅移动的,并且对于地图容器具有成员函数

key_type &key() const;
mapped_type &mapped() const;
它不仅允许修改映射类型,还允许修改密钥。这可用于在不重新分配的情况下更改键(示例取自):

编译(使用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
代替
固定字符串和/或
无序映射
而不是
映射
,获得了相同的结果)


编辑

请注意,我没有询问与修改
键相关的问题(
map
存储

我的问题仅仅是关于通过指针或引用访问映射元素的限制。只有当元素未被移动/复制,即其地址始终有效(事实上由标准指定)时,提取和插入习惯用法的整个思想才有意义。在提取状态下呈现对元素的访问看起来很奇怪,使得提取和插入机制变得不那么有用:考虑多线程代码,其中一个线程访问元素,而另一个线程提取并重新插入元素。这可以在没有任何问题的情况下实现,但可能会调用UB--为什么?

下面是一个UB场景(IMHO非常好,不需要UB):

void somefunc(object*ptr){ptr->do_something();}
无效重新设置键(映射与维护、int-oldKey、int-newKey)
{
如果(M.find(0)!=M.end()&&M.find(newKey)==M.end()){
自动手柄=M.extract(0);
handle.key()=newKey;
插入(标准::移动(手柄));
}
}
map M=fillMap();
自动ptr=addressof(M[0]);//取首字母地址
线程t1(somefunc,ptr);//使用所述地址访问对象
螺纹t2(重新编号,M,7);//提取和插入对象

当然,如果
insert()。这是显而易见的,但用户可以对此有所了解。

我认为“提取”系统中最主要的微妙之处在于
映射的
值类型是
-请注意
常数

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

这基本上要求您认真研究问题,并说服自己,
pair
有时可以解释为
pair
(请记住,
pair
是允许用户专门化的模板!)。因此,为了避免修改const对象,必须对任何节点的插入和提取状态进行清晰的排序

[container.node.overview]p4中有标准的措辞来帮助解决专门化问题:

如果
存在用户定义的
专门化,其中
是 容器的
key\u类型
T
是容器的
mapped\u类型
,涉及节点句柄的操作行为未定义


我只是跟进Kerrek SB的回答,希望能更详细地解释这个问题(因此更令人信服)。所采用的方法提到了
std::pair
vs
std::pair
难题,并且“使用与
std::launder
在提取和重新插入时使用的技术类似的技术,可以安全地实现两者之间的转换。”

这样,只要用户代码遵守您提到的限制,提取和重新插入就可以通过调用“实现‘魔术’”(这是本文中明确的措辞)来解决容器代码本身中任何可能的别名问题,从而避免与类型双关相关的优化

这就提出了一个问题,为什么不能将这种“魔力”扩展到包括这样的情况:用户代码通过一个指针访问分离节点的
映射的
元素,而该指针是在节点仍然属于容器时获得的。原因是实现这种“魔术”的范围远远大于实现仅适用于节点提取和插入的有限魔术的范围

例如,考虑以下函数:

int f(std::pair<const int, int> &a, const std::pair<int, int> &b)
{
    a.second = 5;
    return b.second;
}
由于类型双关限制,显然这是UB。等等,我知道你想抗议是因为:

  • std::map
    节点类型
    没有
    value()
    方法
  • 即使它这样做了,
    node\u type
    也应该
    std::launder
    (或大致相当的值)
  • 然而,这些要点并没有提供实际的补救办法。就第一点而言,考虑这个小的变化:

    int f(std::pair<const int, int> &a, const int &b)
    {
        a.second = 5;
        return b;
    }
    
    int g()
    {
        std::map<int, int> m{{1, 1}};
        auto &r = m[1];
        auto node = m.extract(1);
        return f(r, node.mapped());
    }
    
    这是因为使用
    std::launder
    根本不授予用户键入双关语的权限。相反,
    std::launder
    只允许通过在最初位于
    &value
    float
    和位于
    std::launder
    之后的
    int
    之间建立生存期依赖关系来重用
    value
    的内存。事实上,就标准而言,
    value
    *intp
    不可能同时处于活动状态,因为它们具有指针不兼容的类型和相同的内存位置

    (什么<代码>标准::流槽
    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 
    
    int f(std::pair<const int, int> &a, const std::pair<int, int> &b)
    {
        a.second = 5;
        return b.second;
    }
    
    int g()
    {
        std::map<int, int> m{{1, 1}};
        auto &r = m[1];
        auto node = m.extract(1);
        return f(r, node.value());
    }
    
    int f(std::pair<const int, int> &a, const int &b)
    {
        a.second = 5;
        return b;
    }
    
    int g()
    {
        std::map<int, int> m{{1, 1}};
        auto &r = m[1];
        auto node = m.extract(1);
        return f(r, node.mapped());
    }
    
    float g()
    {
        float value = 0.0f; // deliberate separate initialization, see below
        value = 3.14f;
        int *intp = std::launder(reinterpret_cast<int *>(&value));
        *intp = 1;
        return value + *intp;
    }