自定义比较器以将唯一元素插入到 C++ 中的集合中
Custom comparator to insert unique elements into a set in c++
我有以下代码片段,用于将元素插入集合并检索它们。但是正如您从示例输出中看到的那样,学生 1 的名字(即 "stud1")并没有被打印出来,即使它是按到达时间排序的。任何人都可以帮助找出这种方法有什么问题吗?
Student.h
#ifndef Student_h
#define Student_h
#include "string"
class Student
{
public:
Student();
~Student();
void setName(const std::string& p_name) { _name = p_name; }
void setArrivalTime(const int p_arr_t) { _arrivalTime = p_arr_t; }
const std::string& getName() const { return _name; }
const int getArrivalTime() const { return _arrivalTime; }
private:
std::string _name;
int _arrivalTime;
};
struct CompareStudByArrivaltime
{
const bool operator()(const Student* s1, const Student* s2) const;
};
#endif /* Student_h */
Student.cpp
#include <stdio.h>
#include "Student.h"
Student::Student()
{
}
Student::~Student()
{
}
const bool CompareStudByArrivaltime::operator()(const Student* s1, const Student* s2) const
{
if (s1->getName() == s2->getName())
{
return false;
}
return (s1->getArrivalTime() <= s2->getArrivalTime());
}
main.cpp
#include <iostream>
#include <set>
#include <map>
#include <vector>
#include "Student.h"
typedef std::vector<Student> StudentsPool;
typedef std::set<Student*, CompareStudByArrivaltime> Students;
typedef std::map<std::string,Students> SchoolStudentsMap;
SchoolStudentsMap g_school_studs;
StudentsPool g_stud_pool;
Student* getStud(const std::string& n)
{
for(StudentsPool::iterator itr = g_stud_pool.begin(); itr != g_stud_pool.end(); ++itr)
{
if (itr->getName() == n)
{
return &(*itr);
}
}
return NULL;
}
void initObj()
{
/** School 1 Record */
std::string school_name = "school1";
char c1 [] = {'s','t','u','d','1','[=12=]'};
std::string n1(c1);
//Student* s1 = new Student();
Student s1;
s1.setName(n1);
s1.setArrivalTime(10);
g_stud_pool.push_back(s1);
Student* tmp = NULL;
tmp = getStud("stud1");
g_school_studs[school_name].insert(tmp);
char c2 [] = {'s','t','u','d','2','[=12=]'};
std::string n2(c2);
Student s2;
s2.setName(n2);
s2.setArrivalTime(2);
g_stud_pool.push_back(s2);
tmp = getStud("stud2");
g_school_studs[school_name].insert(tmp);
char c3 [] = {'s','t','u','d','3','[=12=]'};
std::string n3(c3);
Student s3;
s3.setName(n3);
s3.setArrivalTime(5);
g_stud_pool.push_back(s3);
tmp = getStud("stud3");
g_school_studs[school_name].insert(tmp);
}
void processObj()
{
for(SchoolStudentsMap::iterator itr = g_school_studs.begin(); itr != g_school_studs.end(); ++itr)
{
Students& studs = itr->second;
for(Students::iterator sitr = studs.begin(); sitr != studs.end(); ++sitr)
{
Student* s = (*sitr);
std::cerr << "Name: " << s->getName() << ", Arr Time: " << s->getArrivalTime() << std::endl;
}
}
}
int main(int argc, const char * argv[])
{
initObj();
processObj();
return 0;
}
示例输出
Name: stud2, Arr Time: 2
Name: stud3, Arr Time: 5
Name: , Arr Time: 10
你的比较器不正确,因为它中断了 "strict weak ordering relation",它应该是这样的:
bool CompareStudByArrivaltime::operator()(const Student* s1, const Student* s2) const
{
if (s1->getName() != s2->getName())
{
return s1->getName() < s2->getName();
}
return (s1->getArrivalTime() < s2->getArrivalTime());
}
或更简单:
bool CompareStudByArrivaltime::operator()(const Student* s1, const Student* s2) const
{
return std::make_tuple( s1->getName(), s1->getArrivalTime() ) <
std::make_tuple( s2->getName(), s2->getArrivalTime() );
}
详情可见here
看看你的比较函数。如果到达时间相同,但物品的顺序不同,那么您 returning true
。
const bool CompareStudByArrivaltime::operator()(const Student* s1, const Student* s2) const
{
if (s1->getName() == s2->getName())
{
return false;
}
return (s1->getArrivalTime() <= s2->getArrivalTime());
// what if s1 and s2 are equal, but switched? You still return true.
}
假设s1和s2到达时间相同。你的函数 returns true
。那么假设我们调用了您的比较函数,但这次使用的是 s2 和 s1。你还是returntrue
。怎么可能?怎么能说s1放在s2之前的容器中,然后同时s2应该放在s1之前的容器中呢?编译器问你哪个先出现,当项目相等时你给出了不可能的答案。这就是 std::set
排序标准被混淆并最终给你不正确结果的地方。
简而言之,这就是严格弱排序的全部内容,@Slava 给出了解决方案的详细信息。
顺便说一句,切换项目和检查 return 值的测试是由调试 Visual C++ 运行时完成的。您的代码可能会立即断言,因为运行时调用排序例程两次,首先是 s1, s2
,然后是 s2, s1
。如果您针对这两种情况return编辑true
,运行时将中止您的应用程序。
另一个问题是您将指向项目的指针存储在此处的 Student
向量中:
g_stud_pool.push_back(s1);
tmp = getStud("stud1"); // <-- gets pointer to item just placed in g_stud_pool
g_school_studs[school_name].insert(tmp); // <-- pointer to Student from the vector being stored
//...
g_stud_pool.push_back(s2); // <-- invalidates previous pointer
tmp = getStud("stud2");
g_school_studs[school_name].insert(tmp); // <-- map now contains invalid pointer(s)
您正在向 g_stud_pool
向量中添加项目,然后立即使用指向您刚刚放置在向量中的项目的指针,通过将该指针放置在您的 std::set
中来引用该项目。
这样做的问题是,每次向向量添加一个项目时,指向先前项目的任何指针都可能失效。最终发生的事情是,您的 set
使用的比较函数将使用已失效的地址。
解决此问题的最快方法(不是唯一方法)是更改为在调整大小时不会使指针(和迭代器)失效的容器。这样的容器就是std::list
。所以改成这样:
#include <list>
typedef std::list<Student> StudentsPool;
解决了失效问题,因为 std::list
在调整列表大小时不会使指针和迭代器失效。
我有以下代码片段,用于将元素插入集合并检索它们。但是正如您从示例输出中看到的那样,学生 1 的名字(即 "stud1")并没有被打印出来,即使它是按到达时间排序的。任何人都可以帮助找出这种方法有什么问题吗?
Student.h
#ifndef Student_h
#define Student_h
#include "string"
class Student
{
public:
Student();
~Student();
void setName(const std::string& p_name) { _name = p_name; }
void setArrivalTime(const int p_arr_t) { _arrivalTime = p_arr_t; }
const std::string& getName() const { return _name; }
const int getArrivalTime() const { return _arrivalTime; }
private:
std::string _name;
int _arrivalTime;
};
struct CompareStudByArrivaltime
{
const bool operator()(const Student* s1, const Student* s2) const;
};
#endif /* Student_h */
Student.cpp
#include <stdio.h>
#include "Student.h"
Student::Student()
{
}
Student::~Student()
{
}
const bool CompareStudByArrivaltime::operator()(const Student* s1, const Student* s2) const
{
if (s1->getName() == s2->getName())
{
return false;
}
return (s1->getArrivalTime() <= s2->getArrivalTime());
}
main.cpp
#include <iostream>
#include <set>
#include <map>
#include <vector>
#include "Student.h"
typedef std::vector<Student> StudentsPool;
typedef std::set<Student*, CompareStudByArrivaltime> Students;
typedef std::map<std::string,Students> SchoolStudentsMap;
SchoolStudentsMap g_school_studs;
StudentsPool g_stud_pool;
Student* getStud(const std::string& n)
{
for(StudentsPool::iterator itr = g_stud_pool.begin(); itr != g_stud_pool.end(); ++itr)
{
if (itr->getName() == n)
{
return &(*itr);
}
}
return NULL;
}
void initObj()
{
/** School 1 Record */
std::string school_name = "school1";
char c1 [] = {'s','t','u','d','1','[=12=]'};
std::string n1(c1);
//Student* s1 = new Student();
Student s1;
s1.setName(n1);
s1.setArrivalTime(10);
g_stud_pool.push_back(s1);
Student* tmp = NULL;
tmp = getStud("stud1");
g_school_studs[school_name].insert(tmp);
char c2 [] = {'s','t','u','d','2','[=12=]'};
std::string n2(c2);
Student s2;
s2.setName(n2);
s2.setArrivalTime(2);
g_stud_pool.push_back(s2);
tmp = getStud("stud2");
g_school_studs[school_name].insert(tmp);
char c3 [] = {'s','t','u','d','3','[=12=]'};
std::string n3(c3);
Student s3;
s3.setName(n3);
s3.setArrivalTime(5);
g_stud_pool.push_back(s3);
tmp = getStud("stud3");
g_school_studs[school_name].insert(tmp);
}
void processObj()
{
for(SchoolStudentsMap::iterator itr = g_school_studs.begin(); itr != g_school_studs.end(); ++itr)
{
Students& studs = itr->second;
for(Students::iterator sitr = studs.begin(); sitr != studs.end(); ++sitr)
{
Student* s = (*sitr);
std::cerr << "Name: " << s->getName() << ", Arr Time: " << s->getArrivalTime() << std::endl;
}
}
}
int main(int argc, const char * argv[])
{
initObj();
processObj();
return 0;
}
示例输出
Name: stud2, Arr Time: 2
Name: stud3, Arr Time: 5
Name: , Arr Time: 10
你的比较器不正确,因为它中断了 "strict weak ordering relation",它应该是这样的:
bool CompareStudByArrivaltime::operator()(const Student* s1, const Student* s2) const
{
if (s1->getName() != s2->getName())
{
return s1->getName() < s2->getName();
}
return (s1->getArrivalTime() < s2->getArrivalTime());
}
或更简单:
bool CompareStudByArrivaltime::operator()(const Student* s1, const Student* s2) const
{
return std::make_tuple( s1->getName(), s1->getArrivalTime() ) <
std::make_tuple( s2->getName(), s2->getArrivalTime() );
}
详情可见here
看看你的比较函数。如果到达时间相同,但物品的顺序不同,那么您 returning true
。
const bool CompareStudByArrivaltime::operator()(const Student* s1, const Student* s2) const
{
if (s1->getName() == s2->getName())
{
return false;
}
return (s1->getArrivalTime() <= s2->getArrivalTime());
// what if s1 and s2 are equal, but switched? You still return true.
}
假设s1和s2到达时间相同。你的函数 returns true
。那么假设我们调用了您的比较函数,但这次使用的是 s2 和 s1。你还是returntrue
。怎么可能?怎么能说s1放在s2之前的容器中,然后同时s2应该放在s1之前的容器中呢?编译器问你哪个先出现,当项目相等时你给出了不可能的答案。这就是 std::set
排序标准被混淆并最终给你不正确结果的地方。
简而言之,这就是严格弱排序的全部内容,@Slava 给出了解决方案的详细信息。
顺便说一句,切换项目和检查 return 值的测试是由调试 Visual C++ 运行时完成的。您的代码可能会立即断言,因为运行时调用排序例程两次,首先是 s1, s2
,然后是 s2, s1
。如果您针对这两种情况return编辑true
,运行时将中止您的应用程序。
另一个问题是您将指向项目的指针存储在此处的 Student
向量中:
g_stud_pool.push_back(s1);
tmp = getStud("stud1"); // <-- gets pointer to item just placed in g_stud_pool
g_school_studs[school_name].insert(tmp); // <-- pointer to Student from the vector being stored
//...
g_stud_pool.push_back(s2); // <-- invalidates previous pointer
tmp = getStud("stud2");
g_school_studs[school_name].insert(tmp); // <-- map now contains invalid pointer(s)
您正在向 g_stud_pool
向量中添加项目,然后立即使用指向您刚刚放置在向量中的项目的指针,通过将该指针放置在您的 std::set
中来引用该项目。
这样做的问题是,每次向向量添加一个项目时,指向先前项目的任何指针都可能失效。最终发生的事情是,您的 set
使用的比较函数将使用已失效的地址。
解决此问题的最快方法(不是唯一方法)是更改为在调整大小时不会使指针(和迭代器)失效的容器。这样的容器就是std::list
。所以改成这样:
#include <list>
typedef std::list<Student> StudentsPool;
解决了失效问题,因为 std::list
在调整列表大小时不会使指针和迭代器失效。