std :: set不会检测重复的自定义对象

时间:2017-07-18 14:18:57

标签: c++ c++11 stdmap stdset

我有一张地图,用于保持球队名称和球队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&#39;中的球员。和&#39; TeamB&#39;是相同但仍然在集合中添加两个指针,我无法弄清楚为什么..有什么我缺少的东西?

2 个答案:

答案 0 :(得分:2)

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

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

如果您想通过指针对象进行比较,请使用自定义比较器类型:

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

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

而玩家类在其自己的实例上实现operator<,而不是在指针上实现:

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

答案 1 :(得分:1)

如果我的假设是正确的,这将不会像你期望的那样成功!

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

我在这里假设您想要一个名为“a”的单人玩家成为两支球队的一部分。如果该玩家在A队中获胜,则其wins属性会增加,就像在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没有“自动更新”功能!它要求其条目保持不变(至少其成员用于比较!)。因此,如果您在比赛的运行​​中更新您的胜利,您的集合中的排序将完全丢失(除此之外,修改地图的键或设置实际上是未定义的行为)。

结论: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;
}

如果使用智能指针,可以跳过删除,例如: 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; }
);
相关问题