[英]C++ Map Initialize object using non-default constructor
在C ++中,假設我有一個無序映射,定義如下:
unordered_map<int, MyClass> my_map;
auto my_class = my_map[1];
在上面的代碼中,如果my_map中沒有1作為鍵,它將使用默認構造函數初始化MyClass並返回。 但是,有一種方法可以使用MyClass的非默認構造函數進行初始化嗎?
沒錯, 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
先進行find
,但是最好避免這樣做,因為插入操作本身也會進行查找。
想象一個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
類未提供默認的構造函數,則可以通過std::unordered_map::find()
和std::unordered_map::insert()
的簡單組合來匹配OP的意圖(或僅對后者進行檢查)成功)。
(這部分是后來插入的,因為“軌道上的種族競賽”指出我跳過了這個簡單的解決方案,而直接轉到更復雜的解決方案。)他為此寫了一個替代答案 。 由於缺少示范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)
正如Aconcagua在評論中提到的那樣,如果沒有預先檢查find()
,即使插入失敗, emplace()
可能也會構造映射值。
該文檔。 cppreference上的`std :: unordered_map :: emplace()的引用提到了這一點:
即使容器中已經存在帶有鑰匙的元素,也可以構造該元素,在這種情況下,新構造的元素將立即被銷毀。
正如Jarod42所述, std::unordered_map::try_emplace()
是C ++ 17中的替代方法,值得一提的是
與insert或emplace不同,如果不執行插入操作,這些函數不會從rvalue參數中移出,這使得操作值僅為移動類型的映射(如
std::unordered_map<std::string, std::unique_ptr<foo>>
變得容易。std::unordered_map<std::string, std::unique_ptr<foo>>
。 此外,與mapped_type
不同,mapped_type
分別處理try_emplace
的鍵和參數,而mapped_type
則需要參數來構造value_type
(即std::pair
)。
[]
實現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
,在其他答案中也包括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
/ emplace
。
最后是個好消息-如果您可以接受Boost.Intrusive (僅標頭的庫)作為依賴項,則可以構建真正零開銷的get_add_if_missing
(無臨時性或重復的哈希計算)。 哈希映射的API對此已經足夠詳細。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.