C++ map node handle

本文探讨了在C++中如何高效地在两个std::map容器间移动元素,特别是使用C++17引入的extract方法来减少内存分配与释放操作,并讨论了node handle的概念及其在splice操作中的应用。

考虑如下代码:

#include <iostream>
#include <map>
#include <string>
#include <boost/chrono.hpp>
#include <boost/timer/timer.hpp>

int main()
{
    std::map<int, std::string> a{ { 1, "one" }, { 2, "two" }, { 3, "buckle my shoe" } };
    std::map<int, std::string> b{ { 3, "three" } };

    //计算函数调用占用的时间
    boost::timer::cpu_timer timer;

    for (int i=0;i<10000;++i){

        {
        auto ite = a.find(2);
        auto [k,v] = *ite;
        b.emplace(std::make_pair(k, v));
        a.erase(ite);    
        }
        
        {
        auto ite = b.find(2);
        auto [k,v] = *ite;
        a.emplace(std::make_pair(k, v));
        b.erase(ite);
        }
    }

    boost::timer::cpu_times time = timer.elapsed();
    int wall_time_ = time.wall / 1000000; //纳秒转毫秒
    
    std::cout << "elasped time=" << wall_time_;

}

把元素2(key==2)在a,b两个容器之间移动。涉及到heap的内存分配和释放。当insert时,发生malloc,当erase时,发生free。
C++17开始,支持无heap动作的元素搬移:

#include <iostream>
#include <map>
#include <string>
#include <boost/chrono.hpp>
#include <boost/timer/timer.hpp>

int main()
{
    std::map<int, std::string> a{ { 1, "one" }, { 2, "two" }, { 3, "buckle my shoe" } };
    std::map<int, std::string> b{ { 3, "three" } };

    //计算函数调用占用的时间
    boost::timer::cpu_timer timer;

    for (int i=0;i<10000;++i){
        b.insert(a.extract(2)); 
        auto ite = b.find(2);
        a.insert(b.extract(ite)); 
    }

    boost::timer::cpu_times time = timer.elapsed();
    int wall_time_ = time.wall / 1000000; //纳秒转毫秒
    
    std::cout << "elasped time=" << wall_time_;

}

关键在于extract函数,它返回一个node handle。这个东西不是迭代器。参考:http://en.cppreference.com/w/cpp/container/node_handle

#include <iostream>
#include <map>
#include <string>
#include <boost/type_index.hpp>

int main()
{
    std::map<int, std::string> a{ { 1, "one" }, { 2, "two" }, { 3, "buckle my shoe" } };

    auto nh = a.extract(2); 
    
    auto type = boost::typeindex::type_id_with_cvr<decltype(nh)>();
    std::cout << type.pretty_name() << std::endl;

    if (!nh.empty()){
        nh.key()=4;
        nh.mapped()="four";   
        a.insert( std::move(nh) );
    }
    
    for(auto [k,v]:a){
        std::cout << "key=" << k << " value=" << v << std::endl;
    }
}

node handler持有map元素的所有权(指的是malloc出来的那个资源)。
可以通过key函数,mapped函数获取数据的引用,而是是非const的,允许修改。
必须通过右值插入map容器。因为a.insert(a.extract(2));中的a.extract(2)返回一个右值,所以不必用std::move强制转换。
nh是个左值,所以必须用std::move(nh)转成右值。

node handler的生存期与迭代器截然不同的地方是:node handler不需要同容器活的一样长,因为node handler是资源掠夺者,抢走容器的资源后,就同容器没关系了。
例如:

#include <iostream>
#include <set>

int main()
{
    auto generator = [ s = std::set<int>{2,4,6,8,10} ] () mutable {
        return s.extract(s.begin());
    };
    
    for(int i=0;i<5;++i){
        std::cout << generator().value() << " ";
    }
}

Splicing: 

Node handles can be used to transfer ownership of an element between two associative containers with the same key, value, and allocator type (ignoring comparison or hash/equality), without invoking any copy/move operations on the container element (this kind of operation is known as "splicing"). 

这里需要注意的是,node handler存储的是容器的key(map类的),value,分配器。注意还有分配器,在free资源时,需要调用分配器的deallocate过程。思考一个问题:
如果两个容器的分配器类型不同,这两个容器的元素能spliceing吗?std::set<int, 分配器类型1> a, std::set<int, 分配器类型2> b ?

转载于:https://www.cnblogs.com/thomas76/p/8723729.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值