2010-07-09 63 views
10

我們已經通過以下迷上多次:如何將指針的關係比較轉換爲錯誤?

#include <iostream> 
#include <vector> 
#include <algorithm> 

using namespace std; 

void print(int* pn) { cout << *pn << " "; } 

int main() { 
    int* n1 = new int(1); 
    int* n2 = new int(2); 
    int* n3 = new int(3); 

    vector<int*> v; 
    v.push_back(n1); 
    v.push_back(n2); 
    v.push_back(n3); 

    sort(v.begin(), v.end()); // Here be dragons! 

    for_each(v.begin(), v.end(), print); 
    cout << endl; 
    delete n1; delete n2; delete n3; 
} 

的問題是,性病::排序是比較整數指針不是整數,這不是編程人員意圖。更糟糕的是,輸出可能看起來是正確的和確定性的(考慮由堆棧返回的新地址或分配地址的順序)。根本問題在於那種最終調用T的操作符<,當T是指針類型時,這很少是個好主意。

有沒有什麼辦法來防止這種情況,或者至少得到編譯器警告?例如,有沒有辦法創建一個自定義版本的std :: sort,需要比較函數,當T是一個指針?

+1

我們都被這個bug多次咬傷。這就是爲什麼我對這個問題感興趣。不過,我認爲沒有人真的破解過它。我們需要的是確保這種事情不會被編譯,所以當你在另一個完整的程序之後執行它時,它不會留在代碼中。 – 2010-07-09 18:42:54

+0

每次我試圖想出一些有用的東西在這裏說,這一切都歸結爲「讓更聰明的開發人員」。然後我意識到像這樣簡單的事情也發生在聰明的開發人員身上。絕對保留筆記(wiki?)上的代碼中顯示的錯誤「最想要的」,並在代碼審查期間特別注意它們。對初級開發人員來說也是如此。 – corsiKa 2010-07-09 19:28:41

回答

2

爲指針,一般你可以這樣做:

#include <ctime> 
    #include <vector> 
    #include <cstdlib> 
    #include <algorithm> 
    #include <functional> 
    #include <type_traits> 

    namespace util { 
     struct sort_pointers { 
      bool operator() (int *a, int *b) { 
       return *a < *b; 
      } 
     }; 

     template <typename T, bool is_pointer = !std::tr1::is_pointer<T>::value> 
     struct sort_helper { 
      typedef std::less<T> wont_compare_pointers; 
     }; 

     template <typename T> 
     struct sort_helper<T,false> { 
     }; 

     template <typename Iterator> 
     void sort(Iterator start, Iterator end) 
     { 
      std::sort(start, 
         end, 
         sort_helper 
         < 
          typename Iterator::value_type 
         >::wont_compare_pointers()); 
     } 

     template <typename Iterator, class Func> 
     void sort(Iterator start, Iterator end, Func f) { 
      std::sort(start, end, f); 
     } 
    } 

    int main() { 
     std::vector<int> v1; 
     std::vector<int*> v2; 
     srand(time(0)); 

     for(int i = 0; i < 10; ++i) { 
      v1.push_back(rand()); 
     } 

     util::sort(v1.begin(), v1.end()); 

     for(int i = 0; i < 10; ++i) { 
      v2.push_back(&v1[i]); 
     } 

     /* util::sort(v2.begin(), v2.end()); */ //fails. 
     util::sort(v2.begin(), v2.end(), util::sort_pointers()); 

     return 0; 
    } 

std::tr1::is_pointer只是這是什麼所謂在Visual Studio 2008,但我認爲加速也有一個,而較新的編譯可能會提供它作爲std::is_pointer。我相信有人能夠寫出更漂亮的解決方案,但這似乎工作。

但是我必須說,我同意齒輪,這是沒有理由的,程序員應該能夠看到這是否會成爲一個問題,並採取相應的行動。

增加:

你可以概括它多一點,我認爲,自動選擇一個仿函數,將取消引用指針和比較值:

namespace util { 
    template <typename T> 
    struct sort_pointers { 
     bool operator() (T a, T b) { 
      return *a < *b; 
     } 
    }; 

    template <typename T, bool is_pointer = !std::tr1::is_pointer<T>::value> 
    struct sort_helper { 
     typedef std::less<T> compare; 
    }; 

    template <typename T> 
    struct sort_helper<T,false> { 
     typedef sort_pointers<T> compare; 
    }; 

    template <typename Iterator> 
    void sort(Iterator start, Iterator end) 
    { 
     std::sort(start, 
        end, 
        sort_helper 
        < 
         typename Iterator::value_type 
        >::compare()); 
    } 
} 

這樣,你不必想想如果你提供指針來比較與否,它會自動排序。

+0

非常好!我修改了sort_pointers :: op <以返回類似於Nicholas的「std :: dont_compare_pointers」,所以開發人員得到一個編譯錯誤,並被迫提供一個比較器。現在我需要創建一個修改後的STL版本,用於所有比較(sort,map :: insert等)。 – 2010-07-10 01:09:29

12

IMO,程序員應該知道std::sort假設容器存儲值。如果你需要一個不同的行爲來比較,那麼你提供了一個比較函數。例如。 (未經測試):

template<typename T> 
inline bool deref_compare(T* t1, T* t2) { return *t1 < *t2; } 

//... 

std::sort(v.begin(), v.end(), deref_compare<int>); 

編輯

FWIW,Jacob's answer最接近直接完成你想要的。可能有一些方法可以進一步推廣它。

+0

對所有的編輯抱歉。我一直認爲它的工作,然後它不是。我現在應該堅持原來的答案。 – Cogwheel 2010-07-09 18:06:12

+0

問題是有時開發者忘記提供比較功能。例如,他們修改容器以存儲指針而不是按值,但忘記更新所有的排序調用。 – 2010-07-10 01:04:55

+0

是的,這就是爲什麼我給雅各布的職位給予我的支持。 :) FWIW,我不得不懷疑是否必須經常提醒人們不要使用std :: sort,因爲您必須提醒他們在進行時要小心; – Cogwheel 2010-07-10 01:32:05

2

對於指針一般我沒有很好的答案,但是如果您使用任何類型的智能指針(例如boost :: shared_ptr),則可以限制比較。

#include <boost/shared_ptr.hpp> 
using namespace std; 

template<class T> 
bool operator<(boost::shared_ptr<T> a, boost::shared_ptr<T> b) 
{ 
    return boost::shared_ptr<T>::dont_compare_pointers; 
} 

int main() { 
    boost::shared_ptr<int> A; 
    boost::shared_ptr<int> B; 
    bool i = A < B; 
} 

輸出:

In function 'bool operator<(boost::shared_ptr<T>, boost::shared_ptr<T>) [with T = int]': 
t.cpp:15: instantiated from here 
Line 8: error: 'dont_compare_pointers' is not a member of 'boost::shared_ptr<int>' 
compilation terminated due to -Wfatal-errors. 

所以,你可以使用智能指針,或者創建自己的智能指針包裝。這對於你想要的非常重要,所以如果你創建一個包裝來檢測這種情況,我建議你只在調試模式下使用它。因此,創建一個宏(呃,我知道)並用它來聲明指針。

#ifdef DEBUG 
    #define pointer(x) pointer_wrapper<X> 
#else 
    #define pointer(x) x* 
#endif 

當然,這仍然需要你的程序員使用它!