C++ STL Set:无法找到()插入的最后一个元素

问题描述:

我正在编写一个应用程序,我在其中使用C++ STL中的Set类。我发现,当我查询插入的最后一个元素时,对set-> find()的调用总是失败。但是,如果我遍历该集合,则可以看到最初查询的元素。C++ STL Set:无法找到()插入的最后一个元素

为了弄清楚什么是错误的,我创建了一个示例应用程序,展现出与我所看到的相同的行为。我的测试代码发布在下面。

对于实际应用程序本身,我需要存储指向集合中对象的指针。这是什么造成了怪异的行为。或者是否有我需要在类中重载的操作符?我正在存储指针?

任何帮助,将不胜感激。

#include <stdio.h> 
#include <set> 

using namespace std; 

#define MySet set<FileInfo *,bool(*)(const FileInfo *, const FileInfo*)> 

class FileInfo 
{ 
    public: 
     FileInfo() 
     { 
      m_fileName = 0; 
     } 
     FileInfo(const FileInfo & file) 
     { 
      setFile(file.getFile()); 
     } 
     ~FileInfo() 
     { 
      if(m_fileName) 
      { 
       delete m_fileName; 
       m_fileName = 0; 
      } 
     } 
     void setFile(const char * file) 
     { 
      if(m_fileName) 
      { 
       delete m_fileName; 
      } 
      m_fileName = new char[ strlen(file) + 1 ]; 
      strcpy(m_fileName, file); 
     } 
     const char * getFile() const 
     { 
      return m_fileName; 
     } 
    private: 
     char * m_fileName; 
}; 

bool fileinfo_comparator(const FileInfo * f1, const FileInfo* f2) 
{ 
    if(f1 && ! f2) return -1; 
    if(!f1 && f2) return 1; 
    if(!f1 && !f2) return 0; 

    return strcmp(f1->getFile(), f2->getFile()); 
} 

void find(MySet *s, FileInfo * value) 
{ 
    MySet::iterator iter = s->find(value); 
    if(iter != s->end()) 
    { 
     printf("Found File[%s] at Item[%p]\n", (*iter)->getFile(), *iter); 
    } 
    else 
    { 
     printf("No Item found for File[%s]\n", value->getFile()); 
    } 
} 

int main() 
{ 
    MySet *theSet = new MySet(fileinfo_comparator); 

    FileInfo * profile = new FileInfo(); 
    FileInfo * shell = new FileInfo(); 
    FileInfo * mail = new FileInfo(); 

    profile->setFile("/export/home/lm/profile"); 
    shell->setFile("/export/home/lm/shell"); 
    mail->setFile("/export/home/lm/mail"); 

    theSet->insert(profile); 
    theSet->insert(shell); 
    theSet->insert(mail); 

    find(theSet, profile); 

    FileInfo * newProfile = new FileInfo(*profile); 

    find(theSet, newProfile); 

    FileInfo * newMail = new FileInfo(*mail); 

    find(theSet, newMail); 

    printf("\nDisplaying Contents of Set:\n"); 
    for(MySet::iterator iter = theSet->begin(); 
      iter != theSet->end(); ++iter) 
    { 
     printf("Item [%p] - File [%s]\n", *iter, (*iter)->getFile()); 
    } 
} 

我从这个得到的输出是:

Found File[/export/home/lm/profile] at Item[2d458] 
Found File[/export/home/lm/profile] at Item[2d458] 
No Item found for File[/export/home/lm/mail] 

Displaying Contents of Set: 
Item [2d478] - File [/export/home/lm/mail] 
Item [2d468] - File [/export/home/lm/shell] 
Item [2d458] - File [/export/home/lm/profile] 

**编辑 这是一种悲哀,我要补充这一点。但正如我之前提到的,这是一个示例应用程序,它是从一个更大的应用程序的不同部分提取的,以展示我收到的失败。

这意味着作为一个单元测试调用set :: find在填充堆分配指针集。如果你对所有new()有问题,我可以提出如何在不使用堆分配指针的情况下神奇地填充集合的建议。否则,评论“太多new()调用”只会让你看起来很傻。

请关注发生的实际问题(现在已解决)。谢谢。

***编辑

也许我应该在我原来的问题已经把这些。但是我希望find()(或者因为它变成更像strcmp的fileinfo_comparator函数比less更少)的问题会更加关注,然后是复制粘贴PoC单元测试的代码审查。

下面是关于完整应用程序本身的代码的一些观点。

  • FileInfo保存了大量的数据以及文件名。它包含SHA1总和,文件大小,模式时间,最后编辑的系统状态等。我已经删除了这个帖子的代码。它以这种形式违反了规则3(感谢@Martin York。请参阅维基链接的评论)。
  • 由于使用接受char *的3rd_party API,最初选择使用char *而不是std :: string。该应用程序已经从那时起发展。改变这不是一个选项。
  • FileInfo中的数据是从系统上的命名管道轮询的,并存储在Singleton中以便跨多个线程访问。 (如果我没有在堆上分配,我会遇到范围问题)
  • 我选择将指针存储在Set中,因为FileInfo对象很大并且不断地从Set中被添加/删除。我决定将指针比总是将大型结构复制到Set中要好。
  • 在我的析构函数中的if语句是不必要的,并且在调试我正在追踪的问题时遗留了工件。它应该被取消,因为它是不需要的。
+4

请大家看看'typedef'关键字 - 没有必要使用宏来缩短类型的名称。 – 2010-08-10 20:57:49

+0

无关nitpick:在删除它们之前不需要检查null的指针。删除空指针是安全的。 – 2010-08-10 22:00:28

+0

为什么到处都是新的,这不是Java。 – 2010-08-10 23:57:59

您的比较函数是错误的 - 它返回bool,而不是整数strcmp(3)。退货声明应该是这样的:

return strcmp(f1->getFile(), f2->getFile()) < 0; 

看一看here

另外,出于好奇,为什么不使用std::set<std::string>呢? STL实际上有不错的默认设置,可以让你从大量的手动内存管理中解放出来。

+1

+1:'new'的错误是C++应用程序中编码不正确的标志。 – 2010-08-11 08:31:44

+0

Doh,那是一个额头sla子!谢谢! @Metthieu请看看应用程序概念证明是什么。阅读大型应用程序中出现的代码以重现问题时,它可能对您而言并不重要。一位优秀的编码人员知道PoC代码看起来很有趣,但可以忽略它来找到真正的问题。 – LukeFu 2010-08-11 13:25:01

+0

这不足以使fileinfo_comparator()正确。比较需要执行严格的弱排序。 http://www.sgi.com/tech/stl/StrictWeakOrdering.html当任一值为NULL时,都不会发生这种情况。它恰好如此,它不会在上面失败,因为在上面的测试集中没有NULL值。 – 2010-08-11 13:46:49

在你的构造:

FileInfo(const FileInfo & file) 
     { 
      setFile(file.getFile()); 
     } 

m_fileName似乎未初始化。

+0

好赶上!谢谢! – LukeFu 2010-08-11 13:36:53

它在我看来像你的FileInfo不能正常工作(至少用于std::set)。要存储在std::set中,比较功能应该返回bool,表示这两个参数按顺序(true)或失序(false)。

鉴于你的FileInfo(坏设计std::string模仿),你可能会更好,如果没有它完全。据我所知,您可以使用std::string而不会丢失任何功能。你也没有很好的理由使用大量的动态分配(并且泄漏了很多你分配的内容)。

#include <set> 
#include <iostream> 
#include <iterator> 
#include <string> 

int main() { 
    char *inputs[] = { "/export/home/lm/profile", "/export/home/lm/shell", "/export/home/lm/mail" }; 
    char *outputs[] = {"Found: ", "Could **not** find: "}; 

    std::set<std::string> MySet(inputs, inputs+3); 

    for (int i=0; i<3; i++) 
     std::cout 
      << outputs[MySet.find(inputs[i]) == MySet.end()] 
      << inputs[i] << "\n"; 

    std::copy(MySet.begin(), MySet.end(), 
     std::ostream_iterator<std::string>(std::cout, "\n")); 

    return 0; 
} 

编辑:即使(或真的,尤其是当FileInfo比较复杂,它不应该试图对自己重新实现字符串的功能。它应该仍然使用std::string为文件名,并实施operator<以该作品:

class FileInfo { 
    std::string filename; 
public: 
    // ... 
    bool operator<(FileInfo const &other) const { 
     return filename < other.filename; 
    } 
    FileInfo(char const *name) : filename(name) {} 
}; 

std::ostream &operator(std::ostream &os, FileInfo const &fi) { 
    return os << fi.filename; 
} 

int main() { 
    // std::set<std::string> MySet(inputs, inputs+3); 
    std:set<FileInfo> MySet(inputs, inputs+3); 

    // ... 

    std::copy(MySet.begin(), MySet.end(), 
     std::ostream_iterator<FileInfo>(std::cout, "\n")); 
} 
+0

感谢您的评论。它的真正形式FileInfo实际上是一个很大的数据,并保存关于文件SHA1总和,大小,模式时间,修改时系统状态等等的数据。 我调整了它,因为所有额外的东西都与我的问题无关。 – LukeFu 2010-08-11 13:36:35