2010-08-10 238 views
1

我正在編寫一個應用程序,我在其中使用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

回答

10

您的比較函數是錯誤的 - 它返回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

1

在你的構造:

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

m_fileName似乎未初始化。

+0

好趕上!謝謝! – LukeFu 2010-08-11 13:36:53

2

它在我看來像你的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