簡體   English   中英

C ++ Map使用非默認構造函數初始化對象

[英]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)

在coliru上進行現場演示


如果映射的類型確實也缺少復制構造函數,則仍可以使用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)

在coliru上進行現場演示

正如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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM