繁体   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