C++ 通过移动有效地将元组插入到容器中

C++ 通过移动有效地将元组插入到容器中,c++,c++11,move-semantics,emplace,C++,C++11,Move Semantics,Emplace,我是一个move语义学初学者。这个代码是: template <typename... Args> void foo(const Args & ... args){ map<tuple<Args...>, int> cache; auto result = cache.emplace(move(make_tuple(args ...)),1); //... } 模板 void foo(常量参数和…参数){ 地图缓存;

我是一个
move
语义学初学者。这个代码是:

template <typename... Args>
void foo(const Args & ... args){
    map<tuple<Args...>, int> cache;
    auto result = cache.emplace(move(make_tuple(args ...)),1);
    //...
    }
模板
void foo(常量参数和…参数){
地图缓存;
自动结果=cache.emplace(移动(生成元组(args…),1);
//...
}
效率高于:

template <typename... Args>
void foo(const Args & ... args){
    map<tuple<Args...>, int> cache;
    tuple<Args...> t(args...);
    auto result = cache.insert(make_pair(t,1));
    //...
    }
模板
void foo(常量参数和…参数){
地图缓存;
元组t(args…);
自动结果=cache.insert(make_pair(t,1));
//...
}
尤其是当
args
包含一些大对象时

同样的问题,但是使用了
std::vector
(因此不需要
make\u pair
make\u tuple

模板
void foo(常量参数和…参数){
地图缓存;
自动结果=cache.emplace(移动(生成元组(args…),1);
//...
}
这段代码应该更快<代码>安放执行就地构建(完美转发)。这应保证最低数量的构造和副本。但是,如果您对它们进行基准测试,这并没有什么坏处

通常,在可能的情况下使用emplace。它应该总是一个更好的选择。

首先:

auto result = cache.emplace(move(make_tuple(args ...)),1);
vs

没什么区别
make_tuple(args…
是一个临时值,因此作为右值引用传递。这一举动没有增加任何东西

会有不同的事情发生

tuple<Args...> t(args...);
auto result = cache.emplace(t, 1);

如果将右值引用传递给
foo()
,则
转发(args).
将其作为右值引用转发,从而导致移动而不是复制。如果您使用左值引用调用
foo()
,它将作为左值转发。

因为这是用于记忆,所以这两个选项都不是一个好主意

对于唯一的密钥容器,
放置
插入
(除非
插入
传递了一个
值类型
-即,
)可以无条件地分配内存并首先构造密钥-值对,然后如果密钥已经存在,则销毁该对并释放内存;如果您的密钥已经存在,那么这显然是非常昂贵的。(他们需要这样做,因为在一般情况下,您必须先构造密钥,然后才能检查它是否存在,并且必须直接在其最终位置构造密钥。)

但是,您还希望避免不必要地复制密钥,因此插入
值_type
是不好的-其中的
密钥是const限定的,因此无法从中移动

最后,您还希望避免额外的查找。虽然没有内存分配那么昂贵,但保存起来还是不错的

因此,我们需要首先查找密钥,并且仅在密钥不在地图中时调用
emplace
。在C++11中,只允许同构查找,因此必须创建一个
args…
副本

map<tuple<Args...>, int> cache;
auto key = std::make_tuple(args...);
auto it = cache.lower_bound(key); // *it is the first element whose key is
                                  // not less than 'key'
if(it != cache.end() && it->first == key) {
    // key already in the map, do what you have to do
}
else {
    // add new entry, using 'it' as hint and moving 'key' into container.
    cache.emplace_hint(it, std::move(key), /* value */);
}
地图缓存;
自动键=标准::生成元组(args;
自动it=缓存。下限(键);//*它是其键为的第一个元素
//不少于“关键”
if(it!=cache.end()&&it->first==key){
//已在地图中键入,请执行您必须执行的操作
}
否则{
//添加新条目,使用“it”作为提示,并将“key”移动到容器中。
cache.emplace_hint(it,std::move(key),/*value*/);
}
在C++14中,您可以执行异构查找,这意味着您可以在实际需要时保存副本:

map<tuple<Args...>, int, less<>> cache; // "diamond functor" enables hetergeneous lookup
auto key = std::tie(args...); // this is a tuple<const Args&...> - a tuple of references!
auto it = cache.lower_bound(key); // *it is the first element whose key is
                                  // not less than 'key'
if(it != cache.end() && it->first == key) {
    // key already in the map, do what you have to do
}
else {
    // add new entry, copying args...
    cache.emplace_hint(it, key, /* value */);
}
映射缓存;//“菱形函子”启用非同步查找
自动关键点=标准::连接(参数…)//这是一个元组-一个引用元组!
自动it=缓存。下限(键);//*它是其键为的第一个元素
//不少于“关键”
if(it!=cache.end()&&it->first==key){
//已在地图中键入,请执行您必须执行的操作
}
否则{
//添加新条目,复制参数。。。
cache.emplace_hint(it,key,/*value*/);
}

顺便说一句,将大对象作为映射键的元组不是最佳选项,那么您可以提出一个吗?:)我应该怎么建议,因为我根本不知道你的问题是什么。你显然是对的:这是记忆编辑器的一部分,其中
args
是输入(为了简单起见,我没有使用值类型,它是记忆函数的返回类型)好的,第一个可能的改进是使用
unordered\u map
,因为键顺序是不相关的谢谢,我很高兴我终于开始理解
move
技巧:)顺便说一句,原地构建是因为位置而不是因为移动,但是
move(…)
是没有用的?@justHelloWorld老实说。。我不知道。我不确定完美的转发是否会占用maketuple并执行到位。如果是这样的话,那么采取行动是没有用的,从另一方面来说,这是很重要的。你可能不会接受这个答案,因为有一个更了解情况的人会给你一个更好的答案。我很感激你的诚实:)至少,不要再说了,
const
@T.C.是个复制/粘贴的疏忽,谢谢。但是你所说的“至少”是什么意思呢?谢谢你的回答,我真的很感激。第一个问题:我对args使用const,因为它是管理左值引用的好习惯。那么,如果我们删除它,会有什么变化呢(因为您的注释“there中的键是const-qualified的,因此无法从中移动”)。第二个问题:没有必要保留map保证的键顺序,因此无序的_映射可能是首选。但是google::dense_hash_map的性能会更好,所以问题是:使用此结构或任何第三方哈希结构都可以进行异构查找?@justHelloWorld 1)问题是
map
值类型
),而不是
参数
;你对此无能为力。2) 无序的_映射不支持异构查找;我不知道第三方散列结构-你必须检查他们的文档。我认为你的代码不起作用,因为你正在比较
元组
(so
key
)和
tuple`(so
it->first
),但请回答我在这个问题上提出的问题。@justHelloWorld
std
template <typename... Args>
void foo(Args && ... args){
    map<tuple<Args...>, int> cache;
    auto result = cache.emplace(make_tuple(forward<Args>(args)...)),1);
    //...
}
map<tuple<Args...>, int> cache;
auto key = std::make_tuple(args...);
auto it = cache.lower_bound(key); // *it is the first element whose key is
                                  // not less than 'key'
if(it != cache.end() && it->first == key) {
    // key already in the map, do what you have to do
}
else {
    // add new entry, using 'it' as hint and moving 'key' into container.
    cache.emplace_hint(it, std::move(key), /* value */);
}
map<tuple<Args...>, int, less<>> cache; // "diamond functor" enables hetergeneous lookup
auto key = std::tie(args...); // this is a tuple<const Args&...> - a tuple of references!
auto it = cache.lower_bound(key); // *it is the first element whose key is
                                  // not less than 'key'
if(it != cache.end() && it->first == key) {
    // key already in the map, do what you have to do
}
else {
    // add new entry, copying args...
    cache.emplace_hint(it, key, /* value */);
}