C ++ unordered_map emplace()函数抛出段错误,我不知道为什么

问题描述 投票:0回答:1

使用std::unordered_map::emplace()时出现段错误。这是最小的可重现示例:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;

class WordTable {
public:
  WordTable() {
    total = 0;
  }
  ~WordTable() {}

  void addWord(const string word, const int incr = 1) {
    cout << "begin emplace" << endl;
    table.emplace(word, Node()); //this is where the seg fault occurs
    cout << "emplace succeeded" << endl;
    if (incr) {
      table[word].incrementCount();
      incrementTotal();
    }
  }
private:
  struct Node {
  public:
    Node() {
      count = 0;
      kids = new WordTable();
    }
    ~Node() {
      delete kids;
    }
    int incrementCount() {
      return ++count;
    }
  private:
    int count;
    WordTable* kids;
  };

  int incrementTotal() {
    return ++total;
  }
  int total;
  unordered_map<string, Node> table;
};

int main() {
  WordTable tmp;
  cout << "add word 1" << endl;
  tmp.addWord("Hi");
  cout << "end add word 1" << endl;
  tmp.addWord("Hi");
  cout << "end add word 2" << endl;

  WordTable* test = new WordTable();
  cout << "add word 3" << endl;
  test->addWord("Hi");
  cout << "end add word 3" << endl;
}

以及相应的输出:

add word 1
begin emplace
emplace succeeded
end add word 1
begin emplace
emplace succeeded
end add word 2
add word 3
begin emplace
Segmentation fault (core dumped)

段错误发生在对.emplace()的第三次调用中对addWord()的调用。

应该发生的是,addWord("Hi")std::unordered_map<std::string, Node>表添加了映射。该映射应将"Hi"作为键值,并将Node()对象作为映射值。

这是第一个奇怪的部分:如果在第三个呼叫之前我只有一个呼叫addWord(),则没有段错误。这是输出:

add word 1
begin emplace
emplace succeeded
end add word 1
end add word 2
add word 3
begin emplace
emplace succeeded
end add word 3

第二个怪异部分是,如果我静态分配test,那么也就不会出现段错误。这是输出的内容:

add word 1
begin emplace
emplace succeeded
end add word 1
begin emplace
emplace succeeded
end add word 2
add word 3
begin emplace
emplace succeeded
end add word 3

我不知道发生了什么,也不知道为什么发生。我只是不了解STL unordered_map::emplace()内部如何发生段错误。我能想到的唯一问题是我在Node()中创建addWord()的方式,但是我没有看到如何使addWord()在前两个调用中成功,但是在第三个调用中出现段错误。

非常感谢您的协助!

c++ segmentation-fault unordered-map
1个回答
0
投票

Node中,您可以在构造函数和析构函数中分配并释放WordTable *kids,但是它将具有default

复制构造函数和运算符。这些将仅复制指针本身,而不创建新对象,例如:
Node(const Node &cp) // default
    : count(cp.count), kids(cp.kids) // no "new"!
{}

当这些副本中的第一个副本被破坏时,指针将被删除,而其他副本则具有无效的指针,这有望在访问时崩溃(替代方法通常是某种形式的堆损坏)。在这种情况下,第二次访问发生在销毁[在我的系统上]副本之一时,在main的末尾,但是出于某种原因,您的编译器/设置可能会在emplace本身中看到这种访问。您可以通过跟踪新内容/删除内容来看到此内容:

Node() { count = 0; kids = new WordTable(); cout << "new kids " << kids << endl; } ~Node() { cout << "delete kids " << kids << endl; delete kids; }
// GCC加字1开始就位新孩子0xa38c30删除孩子0xa38c30 //在替换中被删除安置成功结束添加单词1开始就位新的孩子0xa38c30 //现在可以“免费”获得与0xa38c30相同的指针删除孩子0xa38c30 //并再次删除孩子0xa38c30 //这次是两次,由于某些GCC特定的实现细节安置成功结束添加单词2加字3开始就位新孩子0xa38cf0 //再次使用相同的指针SIGSEGV //这次没有那么幸运,可能是因为该重复删除仅破坏了某些内容
您可以通过“删除”构造函数,运算符来防止默认副本:

Node(const Node &) = delete; Node &operator = (const Node &) = delete;

这将使table.emplace(word, Node());变成编译错误,因为这是复制发生的地方。尽管您调用了emplace,但仍向它传递了一个完整的临时对象,因此它将尝试并放置到副本构造函数Node(const Node &)中。您要对emplace进行的操作是向其传递构造函数参数,这对于默认构造函数来说有点棘手,简单的table.emplace(word)不会编译:

table.emplace(std::piecewise_construct, std::make_tuple(word), std::make_tuple());

或者,如果您希望对象可以安全地复制,请显式实现这些功能。

Node(const Node &cp) : count(cp.count), kids(new WordTable()) // create a full new object this time { *kids = *cp.kids; cout << "copy constructor " << kids << endl; } Node &operator = (const Node &cp) { *kids = *cp.kids; cout << "copy operator " << kids << endl; return *this; }

加字1开始就位新孩子0xee8c30复制构造函数0xee8cd0 //这次创建了一个新对象删除孩子0xee8c30 //删除原始的,但0xee8cd0仍然有效安置成功结束添加单词1开始就位新孩子0xee8c30复制构造函数0xee8d90删除孩子0xee8d90删除孩子0xee8c30安置成功结束添加单词2加字3开始就位新孩子0xee8d40复制构造函数0xee8de0删除孩子0xee8d40安置成功结束添加单词3delete kids 0xee8cd0 //主返回时删除第一个副本
WordTable的副本很好,因为unordered_map<string, Node>将使用刚刚提供的键/值分别复制每个键/值。

对于std::unique_ptr,这种情况也是不错的选择,因为在单个唯一的位置创建和销毁了某些对象,从而节省了手动delete的需要。它还会自动阻止默认复制,因为unique_ptr本身不允许复制。

struct Node { public: Node() : count(0) , kids(std::make_unique<WordTable>()) // std::unique_ptr(new WordTable()) {} int incrementCount() { return ++count; } private: int count; std::unique_ptr<WordTable> kids; };

© www.soinside.com 2019 - 2024. All rights reserved.