在C ++中,假设我有一个如下定义的无序映射:
unordered_map<int, MyClass> my_map;
auto my_class = my_map[1];
在上面的代码中,如果my_map中没有1作为键,它将使用默认构造函数初始化MyClass并返回。但是有没有办法使用MyClass的非默认构造函数进行初始化?
答案 0 :(得分:3)
您是正确的,operator[]
要求值类型是默认可构造的。
insert
不:
std::unordered_map<int, MyClass> my_map;
// Populate the map here
// Get element with key "1", creating a new one
// from the given value if it doesn't already exist
auto result = my_map.insert({1, <your value here>});
这给了您一对包含元素的迭代器(无论是创建的新对象还是已存在的对象)和一个布尔值(告诉您是哪种情况)。
所以:
auto& my_class = *result.first;
const bool was_inserted = result.second;
现在,您可以使用此信息执行任何操作。通常,您甚至都不关心result.second
,而可以忽略它。
对于更复杂的值类型,您可以使用emplace
,就像insert
一样,但是更好。假设您真的不希望在不使用该值的情况下构造该值,并且您拥有C ++ 17:
auto result = my_map.try_emplace(1, <your value's ctor args here here>);
如果您不在乎(或没有C ++ 17):
auto result = my_map.emplace(1, <your value>);
这仍然优于insert
,因为它可以将值移动到地图中,而不是复制它。
最终,如果您甚至不想不必要地生成ctor args,则始终可以先执行find
,但最好避免这样做,因为插入操作本身会做一个发现。
答案 1 :(得分:2)
想象一个struct T
:
struct T {
int i1, i2;
// no default constructor
explicit T(int i1, int i2): i1(i1), i2(i2) { }
};
使用默认构造函数非常简单:
aMap[123] = T(1, 23);
operator[]
允许按需创建一个不存在的条目(但为此,它需要映射类型的默认构造函数)。
如果mapped_type
类没有提供默认的构造函数,则OP的意图可以通过std::unordered_map::find()
和std::unordered_map::insert()
的简单组合来匹配(或仅检查后者是否成功) )。
(这部分后来被插入,因为《轨道飞行》中的“种族竞赛”指出我跳过了这个简单的解决方案,而直接转到了更复杂的解决方案。)他为此写了alternative answer。由于缺少示范性的MCVE,因此我接受了我的内容并对其进行了修改:
#include <iostream>
#include <unordered_map>
struct T {
int i1, i2;
// no default constructor
explicit T(int i1, int i2): i1(i1), i2(i2)
{
std::cout << "T::T(" << i1 << ", " << i2 << ")\n";
}
};
int main()
{
typedef std::unordered_map<int, T> Map;
Map aMap;
//aMap[123] = T(1, 23); doesn't work without default constructor.
for (int i = 0; i < 2; ++i) {
Map::key_type key = 123;
Map::iterator iter = aMap.find(key);
if (iter == aMap.end()) {
std::pair<Map::iterator, bool> ret
= aMap.insert(Map::value_type(key, T(1 + i, 23)));
if (ret.second) std::cout << "Insertion done.\n";
else std::cout << "Insertion failed! Key " << key << " already there.\n";
} else {
std::cout << "Key " << key << " found.\n";
}
}
for (const auto &entry : aMap) {
std::cout << entry.first << " -> (" << entry.second.i1 << ", " << entry.second.i2 << ")\n";
}
return 0;
}
输出:
T::T(1, 23)
Insertion done.
Key 123 found.
123 -> (1, 23)
如果映射的类型确实也没有副本构造函数,则仍可以使用std::unordered_map::emplace()
(同样可以使用std::unordered_map::find()
进行预检查)来解决:
aMap.emplace(std::piecewise_construct,
std::forward_as_tuple(123),
std::forward_as_tuple(1, 23));
改编后的示例:
#include <iostream>
#include <unordered_map>
struct T {
int i1, i2;
// no default constructor
explicit T(int i1, int i2): i1(i1), i2(i2)
{
std::cout << "T::T(" << i1 << ", " << i2 << ")\n";
}
// copy constructor and copy assignment disabled
T(const T&) = delete;
T& operator=(const T&);
};
int main()
{
typedef std::unordered_map<int, T> Map;
Map aMap;
for (int i = 0; i < 2; ++i) {
Map::key_type key = 123;
Map::iterator iter = aMap.find(key);
if (iter == aMap.end()) {
std::pair<Map::iterator, bool> ret
= aMap.emplace(std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(1 + i, 23));
if (ret.second) std::cout << "Insertion done.\n";
else std::cout << "Insertion failed! Key " << key << " already there.\n";
} else {
std::cout << "Key " << key << " found.\n";
}
}
for (const auto &entry : aMap) {
std::cout << entry.first << " -> (" << entry.second.i1 << ", " << entry.second.i2 << ")\n";
}
return 0;
}
输出:
T::T(1, 23)
Insertion done.
Key 123 found.
123 -> (1, 23)
正如阿空加瓜在评论中提到的那样,即使没有插入find()
,emplace()
也会构造映射值,即使插入失败。
文档。关于`std::unordered_map::emplace()的cppreference提及:
即使容器中已经存在带有键的元素,也可以构造该元素,在这种情况下,新构造的元素将立即被销毁。
正如Jarod42所述,std::unordered_map::try_emplace()
是C ++ 17中值得一提的替代方案
与插入或Emplace不同,如果不发生插入,这些函数就不会从右值参数移开,这使得操作值仅为移动类型(例如
std::unordered_map<std::string, std::unique_ptr<foo>>
)的映射变得容易。另外,与Emplace不同,try_emplace
分别处理mapped_type
的键和参数,而Emplace则需要参数来构造value_type
(即std::pair
)< / p>
答案 2 :(得分:1)
[]
实现get_add_if_missing
。从语义上讲,无开销的实现将类似于:
value_type& get_add_if_missing(key_type const& k, auto&& factory) {
auto b = bucket_for(k);
auto pos = pos_for(k, b);
if (pos == b.end()) {
return b.append(k, factory());
} else {
return *pos;
}
}
API上还没有完全等效的功能(自C++17
起),因此,现在,您需要根据创建临时value_type
的成本来决定要具有哪种次优性:
insert/emplace
,在其他答案中都很好)一个额外的查找版本是:
final itr = m.find(key);
if (itr == m.end()) {
// insert or emplace a new element with the constructor of your choice
}
关于cppreference的std::unordered_map文章应该有足够的用法示例,用于insert
/ emplace
。
使用基于树的实现(std::map
),get_add_if_missing
很有可能实现零开销lower_bound
仿真,随后是启用提示的insert
/ {{1} }。
最后一个好消息是-如果您可以接受Boost.Intrusive(仅标头的库)作为依赖项,则可以构建真正的零开销emplace
(无需临时或重复进行哈希计算) )。哈希映射的API对此已足够详细。