C++ map node handle

考慮以下代碼:node

#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動做的元素搬移:ios

#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_handleapp

#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是資源掠奪者,搶走容器的資源後,就同容器不要緊了。
例如:this

#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: spa

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"). code

這裏須要注意的是,node handler存儲的是容器的key(map類的),value,分配器。注意還有分配器,在free資源時,須要調用分配器的deallocate過程。思考一個問題:
若是兩個容器的分配器類型不一樣,這兩個容器的元素能spliceing嗎?std::set<int, 分配器類型1> a, std::set<int, 分配器類型2> b ?blog

相關文章
相關標籤/搜索