问题描述
我正在尝试为纸牌游戏 Avalon 实现贝叶斯估计器。游戏有五轮,每一轮最多包含五个不同玩家提出的五个提案。如果提议被接受,玩家将继续进行任务,游戏进入下一轮。在上一轮结束之前,下一轮将有哪5名选手被提名,尚不得而知。我想跟踪使用迭代器提出团队建议的当前玩家,但不知何故它最终指向无处。具体来说,在调用 round1
的构造函数中,迭代器 Round::proposer
正确指向 &PlayerA
,即 Round::proposers
的开头。但是,当我将此实例(或它的副本?)添加到 Game::rounds
时,proposer
的 Round 成员 Game::rounds.back()
无处指向,即使 Round 成员 proposers
仍然是正确的。为什么会这样?在执行过程中,当然会在调用(*Round::proposer)->make_proposal();
时抛出读访问冲突异常。对于这个冗长的问题,我深表歉意,但产生错误似乎需要两个间接级别。
// Player.h
#include <string>
class Player
{
private:
std::string name;
public:
Player(std::string name) : name(name) {};
void make_proposal() const {};
};
// Round.h
#include "Player.h"
#include <vector>
class Round
{
private:
std::vector<const Player*> proposers;
std::vector<const Player*>::const_iterator proposer;
public:
Round(std::vector<const Player*> proposers) : proposers(proposers),proposer(Round::proposers.begin()) {};
void next_proposal() { (*Round::proposer)->make_proposal(); };
};
// Game.h
#include "Round.h"
#include <vector>
class Game
{
private:
std::vector<Player*> players;
std::vector<Player*>::iterator active_player;
std::vector<Round> rounds;
public:
Game(std::vector<Player*> players);
void advance_player();
void next_round();
};
// Game.cpp
#include "Game.h"
Game::Game(std::vector<Player*> players)
: players(players),active_player(Game::players.begin())
{
std::vector<Player*>::const_iterator player = Game::players.begin();
std::vector<const Player*> proposers = { *player };
for (unsigned int i = 0; i < 4; ++i) {
++player;
if (player == Game::players.end()) player = Game::players.begin();
proposers.push_back(*player);
}
Round round1(proposers);
Game::rounds = { round1 };
}
void Game::next_round()
{
Game::rounds.back().next_proposal();
}
#include <iostream>
#include "Game.h"
int main()
{
Player playerA("A");
Player playerB("B");
Player playerC("C");
Player playerD("D");
Player playerE("E");
Player playerF("F");
std::vector<Player*> players = { &playerA,&playerB,&playerC,&playerD,&playerE,&playerF };
Game game(players);
for(unsigned int i = 0; i < 5; ++i) {
game.next_round();
}
}
出人意料的是,替换了两行代码
Round round1(proposers);
Game::rounds = { round1 };
在Game.cpp
与
Round* round1 = new Round(proposers);
Game::rounds = { *round1 };
解决了这个问题,虽然我真的不明白为什么。毕竟,rounds
是 Game
的成员变量,并且存在直到实例 game
被销毁。此 hack 的后续问题:最后一个代码片段中 round1
指向的实例是否被类 Game
的默认构造函数销毁,因为它在添加到成员变量之前被取消引用?
解决方法
您的 Round
无法正常复制:
class Round
{
private:
std::vector<const Player*> proposers;
std::vector<const Player*>::const_iterator proposer;
public:
Round(std::vector<const Player*> proposers) : proposers(proposers),proposer(Round::proposers.begin()) {};
void next_proposal() { (*Round::proposer)->make_proposal(); };
};
如果您确实复制了它,proposer
仍将是原始 Round
中元素的迭代器,而不是副本中的向量。当你这样做时:
Round* round1 = new Round(proposers);
Game::rounds = { *round1 };
然后本地对象 round1
在作用域结束时不会被销毁,因此在复制 {{1} 之后,现在位于 rounds
内部的迭代器}},指的是一个仍然活着的元素。虽然它指的是 round1
中的元素,而不是您放置在 round1
中的 Round
。
要么注意 rounds
的 3/5
规则,要么使用索引而不是迭代器。复制整个向量时,索引不会失效。 (当您将更多元素推回向量时,它们也不会失效,但迭代器会)
类似问题的一个更简单的例子:
Round
将 #include <iostream>
struct broken {
int x;
int* ptr;
broken(int a = 0) : x(a),ptr(&x) {}
};
int main() {
broken a{42};
broken b{123};
a = b;
a.x = 0;
std::cout << *(a.ptr);
}
复制到 b
后,a
中的指针仍将指向 a
,因此输出为 b.x
(不是 123
作为人们可能会期待)。