std::set 未检测到重复的自定义对象

std::set doesn't detect duplicate custom objects

我有一张用于保存球队名称和球员的地图 std:pair<std::string, std::vector<std::string> > 以及一组指向球员的指针,以保持球员按胜利降序排列。还有一个问题 - 一名球员可以参加多个球队。

    class Player
{
public:
    int wins;
    std::string name;

    Player() {}
    Player(std::string name) : wins(0), name(name) {}

    bool operator<(const Player* rhs) const
    {
        return this->wins > rhs->wins;
    }

    bool operator()(const Player* lhs, const Player* rhs) const
    {
        return lhs->wins > rhs->wins;
    }
};

int main()
{
    // EXAMPLE
    std::set<Player*> players;
    std::map<std::string, std::vector<Player> > teams;

    teams["TeamA"].push_back(Player("a"));
    teams["TeamB"].push_back(Player("a"));;
    players.insert(&teams["TeamA"].front());
    players.insert(&teams["TeamB"].front());

    std::cout << players.size(); // output 2 

    return 0;
}

如您所见,'TeamA' 和 'TeamB' 中的球员是相同的,但仍然在集合中添加了两个指针,我不明白为什么..我有什么东西吗不见了?

您的集合包含指针,而不是 Player 对象。这意味着指针比较用于确定等价性。

当然,TeamA 中第一个对象的地址与 TeamB 中第一个玩家的地址不同,即使它们包含相同的数据。

如果你想通过指针比较,使用自定义比较器类型:

PlayerComp {
  bool operator()(Player* lhs, Player* rhs){
    return *lhs < *rhs;
  }
};

std::set<Player*, PlayerComp> players;

而播放器 class 在它自己的实例上实现 operator<,而不是在指针上:

bool operator<(const Player& rhs) const
{
    return wins < rhs.wins;
}

如果我的假设是正确的,这将不会如您所料!

teams["TeamA"].push_back(Player("a"));
teams["TeamB"].push_back(Player("a"));

我假设您希望一个名为 "a" 的玩家加入两个团队。如果此玩家在 A 队中获胜,则其获胜 属性 会增加,就像在 B 队中获胜一样。

不过,您实际上所做的是创建两个不同的玩家,都称为 "a",并且他们都出现在集合中(好吧,实际上不是,见下文...),每个人都保持自己的状态自己的胜利(第一个玩家 "a" A 队的胜利,第二个玩家 B 队的胜利)。

必须做的 是在球员集合中创建球员并将这些单个实例添加到各个球队的向量中:

std::set<Player> players;
std::map<std::string, std::vector<Player*> > teams;
auto entry = players.insert(Player("a"));
teams["TeamA"].push_back(&entry->first);

但这也行不通:您的比较仅基于胜利!如果您输入两个不同的玩家(顺便说一句,如果您应用 StoryTeller 的修复程序,也会发生同样的情况!),两者都以 0 的胜利开始。因此两个玩家比较相等(a < bb < a两者都不适用,因此相等),所以只有一个玩家会进入集合...

此外,std::set 没有 "auto-update" 功能!它要求其条目保持不变(至少其中的成员用于比较!)。因此,如果您在 运行 比赛中更新您的胜利,您的集合中的排序将完全丢失(除此之外,修改地图或集合的键实际上是未定义的行为)。

结论:std::set不是一个选项,至少不是你想要的使用方式!

让我向您推荐另一种方法:

// yes, a vector...
// we'll need to maintain sorting ourselves...
std::vector<Player*> players;
// need pointers here, too, as pointers to objects
// (those within the teams!) might INVALIDATE on
// vector<Player> resizing!!!

std::map<std::string, std::vector<Player*>> teams;

players.push_back(new Player("a"));
teams["TeamA"].push_back(players.back());
teams["TeamB"].push_back(players.back());

players.push_back(new Player("b"));
teams["TeamA"].push_back(players.back());
teams["TeamC"].push_back(players.back());

现在让我们一队获胜:

for(auto p : teams["TeamA"])
{
    ++p->wins;
}

好吧,我们只是求助:

std::sort(players.begin(), players.end(), PlayerComp());

嘿,这只是 StoryTeller 的回答中的 PlayerComp...或者我们改用 lambda:

std::sort
(
    players.begin(), players.end(),
    [](Player const* x, Player const* y) { return *x < *y; }
);

最后,不要忘记再次删除你的播放器对象:

for(auto p : players)
{
    delete p;
}

你可以跳过删除,如果你使用智能指针,e。 G。像这样:

std::vector<std::unique_ptr<Player>> players;

players.push_back(std::make_unique<Player>("a"));
teams["TeamA"].push_back(players.back().get());

for(auto p : teams["TeamA"])
    ++p->score;
std::sort
(
    players.begin(), players.end(),
    [](std::unique_ptr<Player> const& x, std::unique_ptr<Player> const& y) { return *x < *y }
);

上面假设球员的向量拥有球员对象的唯一所有权,就像我的非智能指针解决方案一样——因此,如果球员的矢量被删除。适用于特定情况,std::shared_ptr 提供了一个值得一提的替代方案,它避免了悬空指针的可能性(代价是在创建、分配和销毁期间的一些对象管理开销,但是,在访问智能指针时不是):

std::vector<std::shared_ptr<Player>> players;
std::map<std::string, std::vector<std::shared_ptr<Player>>> teams;

players.push_back(std::make_shared<Player>("a"));
teams["TeamA"].push_back(players.back());


for(auto p : teams["TeamA"])
    ++p->score;
std::sort
(
        players.begin(), players.end(),
        [](std::shared_ptr<Player> const& x, std::shared_ptr<Player> const& y) { return *x < *y; }
);