为什么使用unordered_map和tuple需要默认构造函数?

use*_*086 5 c++ stl c++11

在下面的程序中,我将一些信息存储在哈希表(std :: unordered_map)中,键是RectData类的对象,关联的值是元组<uint,RectData,enum>和自定义KeyHash和KeyEqual定义.

在没有默认构造函数的情况下插入<key,value>对会在gcc 4.9.2中出现两页错误.第一个错误的行是:

visited_info[rect0] = info0;
Run Code Online (Sandbox Code Playgroud)

我用MSVC++ 12.0仔细检查过,我也有错误信息.

当我添加默认构造函数时,编译是正常的,并且在运行时调用默认构造函数.我无法理解为什么RectData类需要默认构造函数?

使用[]运算符从哈希表中检索数据也需要在编译时使用默认构造函数,但是在运行时不会调用它,为什么?

auto info = visited_info[rect];
Run Code Online (Sandbox Code Playgroud)

注意:使用visited_info.emplace()和visited_info.find()更改代码可以解决问题,但不会回答问题.

谢谢你的回答.

完整代码如下.

#include <boost/functional/hash.hpp>
#include <tuple>
#include <vector>
#include <unordered_map>
#include <iostream>

using uint = unsigned int;

enum class Direction : int { left = 0, right = 1, up = 2, down = 3, null = 4 };

class RectData {
 public:
  RectData(uint width, uint height)
      : width_(width), height_(height), datas_(width * height, 0) {
    total_ = width_ * height_;
  }


  // A default constructor must be defined!
  RectData() : RectData(0u, 0u) {
    std::cout << "Calling the default constructor !!!" << std::endl;
  }

  size_t hash() const {
    return boost::hash_value(datas_);
  }

  bool operator==(const RectData &rect) const {
    return (width_ == rect.width_) &&
           (height_ == rect.height_) &&
           (datas_ == rect.datas_);
  }

  struct KeyHash {
    std::size_t operator()(const RectData &rect) const {
      return rect.hash();
    }
  };

  struct KeyEqual {
    std::size_t operator()(const RectData &r1, const RectData &r2) const {
      return r1 == r2;
    }
  };

 private:
  uint width_;
  uint height_;
  std::vector<uint> datas_;
  uint total_;
};

using StoredInfo = std::tuple<uint, RectData, Direction>;

int main() {
  std::unordered_map<RectData, StoredInfo, RectData::KeyHash,
                     RectData::KeyEqual> visited_info;

  RectData rect0(5u, 5u);
  RectData rect1(4u, 4u);
  RectData rect2(3u, 3u);
  RectData rect3(2u, 2u);

  StoredInfo info0 = std::make_tuple(10u, rect1, Direction::up);
  StoredInfo info1 = std::make_tuple(11u, rect2, Direction::down);
  StoredInfo info2 = std::make_tuple(12u, rect3, Direction::left);
  StoredInfo info3 = std::make_tuple(13u, rect0, Direction::right);


  // the line below requires a default RectData constructor!!! 
  visited_info[rect0] = info0;

  // default RectData constructor also needed here !!!
  visited_info[rect1] = std::move(info2);

  // but not needed here
  visited_info.insert(std::make_pair(rect2, info2));

  // and not needed here
  visited_info.emplace(rect3, info3);

  // but needed here and not called!!! 
  StoredInfo i1 = visited_info[rect1];
  std::cout << "Verify (must be 11) = " << std::get<0>(i1)
            << std::endl;

  // but needed here and not called!!! 
  StoredInfo &i2 = visited_info[rect2];
  std::cout << "Verify (must be 12) = " << std::get<0>(i2)
            << std::endl;


  // and not needed here
  auto it = visited_info.find(rect3); 
  std::cout << "Verify (must be 13) = " << std::get<0>(it->second)
            << std::endl;

}
Run Code Online (Sandbox Code Playgroud)

Use*_*ess 6

visited_info[rect0] = info0;
Run Code Online (Sandbox Code Playgroud)

那你觉得这怎么样?有充分证据表明,左侧评估对地图中存储的项目的引用.如果之前没有该项目,则首先默认构建它.

然后,您可以使用复制或移动分配来更新表达式右侧的默认构造项.

如果你想避免你现在得到的default-construct-and-assign操作,请改用emplace.


NB.一个可能的混淆源是,例如,Python,MyObj[1]可能转换为__getitem__呼叫,但MyObj[1]=1转移到__setitem__呼叫.

在C++中,左侧和右侧表达式都必须评估某些内容,而不知道它们所处的语句.因此,左侧评估为您可以读取的引用来自或分配 - 但是在您可以获取该引用之前,该对象需要存在.