简体   繁体   中英

Weird iterator behaviour + segfault with unordered_set

I have a class that has an unordered_set<int> member as follows:

I have the following class definition followed by its regular & copy constructors, as well as some other function that modifies the set (deleted irrelevant code segments since the class is very long):

#include <iostream>
#include <unordered_set>
#include <random>

class HexBoard {
  public:
    HexBoard(int n);
    HexBoard(const HexBoard &obj);
    std::unordered_set<int> emptyPositions();
  private:
    std::unordered_set<int> empty_positions;
};

HexBoard::HexBoard(int n) {
    for (int i = 0; i < n * n; i++) {
        empty_positions.insert(i);
    }
}

HexBoard::HexBoard(const HexBoard &obj) : empty_positions(obj.empty_positions) {};

void HexBoard::placeStone(int i) {
    checkBounds(i); // raises an error if i >= n 
    empty_positions.erase(i);
}

std::unordered_set<int> HexBoard::emptyPositions() {
    return empty_positions;
}

I have a different class that contains an instance of this HexBoard. It has a function that will copy that board into a different variable using the copy constructor:

class Game {
  public:
    Game(HexBoard::HexBoard *board) : board(board) {};
  private:
    HexBoard *board;
    void monteCarlo(int position);
};

void Game::monteCarlo(int position) {

    HexBoard *another_board = new HexBoard(*board);

    int count = 0;
    while (count < 5) {
        count++;

        std::uniform_int_distribution<unsigned> dis(
            0, another_board->emptyPositions().size() - 1
        );

        std::cout << "Empty positons:\n";
        for (const auto& pos : another_board->emptyPositions()) {
            std::cout << pos << " ";
        }
        std::cout << "\n";

        int n = dis(gen);
        std::cout << "Picked random n: " << n << "\n";

        auto it = another_board->emptyPositions().begin();
        std::cout << "it begin: " << *it << "\n";
        std::advance(it, n);
        std::cout << "it advance: " << *it << "\n";
        int absolute_position = *it;
        std::cout << "picked " << absolute_position << "\n";
    }
}

In the monteCarlo function, let's say the contents of emptyPositions set were initially 8, 7, 6, 5, 4, 3, 2, 1 , the stdout output of this function is usually:

Empty positons:
8 7 6 5 4 3 2 1
Picked random n: 4
it begin: 2
Segmentation fault: 11

Why does this segfault? I understand that there is some iterator subtly with regards to the empty_positions.erase(i); line, but even when I comment this out, I get the same behaviour.

I've also added this right after the Picked random n stdout and this segfaults as well (output below it):

std::cout << "set buckets contain:\n";
for ( unsigned i = 0; i < ai_board->emptyPositions().bucket_count(); ++i) {
    std::cout << "bucket #" << i << " contains:";
    for ( auto j = ai_board->emptyPositions().begin(i);
          j != ai_board->emptyPositions().end(i); ++j)
        std::cout << " " << *j;
    std::cout << std::endl;
}

Output:

set buckets contain:
Segmentation fault: 11

The segfault happens at std::advance(it, n); and at this last manual iteration.

I'd appreciate any help.

Thanks

I suspect the problem is that emptyPositions() is returning a copy of the unordered_set . As a result, another_board->emptyPositions().begin() returns an iterator from a temporary whose lifetime is not guaranteed. It is probably being cleaned up before you iterate through it.

You probably meant to have emptyPositions() return a reference to the state variable empty_positions .

In the HexBoard class you have:

std::unordered_set<int> emptyPositions();

That is, the function returns a set by value .

Then you later do

auto it = another_board->emptyPositions().begin();

That will cause emptyPositions to return a temporary object, one that will be destructed once the expression is finished. That will leave you with an iterator to a key in a now destructed set. Dereferencing this iterator will lead to undefined behavior .

The solution is to make the emptyPositions return a constant reference instead:

std::unordered_set<int> const& emptyPositions() const;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM