C ++ Map使用非默认构造函数初始化对象

时间:2018-09-12 07:29:56

标签: c++

在C ++中,假设我有一个如下定义的无序映射:

unordered_map<int, MyClass> my_map;
auto my_class = my_map[1];

在上面的代码中,如果my_map中没有1作为键,它将使用默认构造函数初始化MyClass并返回。但是有没有办法使用MyClass的非默认构造函数进行初始化?

3 个答案:

答案 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)

Live Demo on 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)

Live Demo on coliru

正如阿空加瓜在评论中提到的那样,即使没有插入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对此已足够详细。