2011-11-16 18 views
82

Để hỗ trợ người dùng xác định loại quan trọng trong std::unordered_set<Key>std::unordered_map<Key, Value> người ta phải cung cấp operator==(Key, Key) và một functor băm:Làm cách nào để chuyên std :: hash <Key> :: toán tử() cho loại do người dùng xác định trong vùng chứa không có thứ tự?

struct X { int id; /* ... */ }; 
bool operator==(X a, X b) { return a.id == b.id; } 

struct MyHash { 
    size_t operator()(const X& x) const { return std::hash<int>()(x.id); } 
}; 

std::unordered_set<X, MyHash> s; 

Nó sẽ là thuận tiện hơn để viết chỉ std::unordered_set<X> với một hash mặc định cho loại X , như đối với các loại đi kèm với trình biên dịch và thư viện. Sau khi tham khảo ý kiến ​​

  • C++ chuẩn Draft N3242 §20.8.12 [unord.hash] và §17.6.3.4 [hash.requirements],
  • Boost.Unordered
  • g ++ include\c++\4.7.0\bits\functional_hash.h
  • VC10 include\xfunctional
  • khác nhau related question s trong Stack Overflow

it se ems thể chuyên std::hash<X>::operator():

namespace std { // argh! 
    template <> 
    inline size_t 
    hash<X>::operator()(const X& x) const { return hash<int>()(x.id); } // works for MS VC10, but not for g++ 
    // or 
    // hash<X>::operator()(X x) const { return hash<int>()(x.id); }  // works for g++ 4.7, but not for VC10 
}                    

Với trình biên dịch hỗ trợ cho C++ 11 vẫn chưa thực nghiệm --- Tôi không cố gắng Clang ---, đây là những câu hỏi của tôi:

  1. Có hợp pháp để thêm chuyên môn như vậy vào không gian tên std? Tôi có cảm xúc lẫn lộn về điều đó.

  2. Phiên bản std::hash<X>::operator() nào, nếu có, tuân thủ tiêu chuẩn C++ 11?

  3. Có cách nào để di chuyển không?

+0

Với gcc 4.7.2, tôi đã phải cung cấp một 'toán tử == toàn cầu (Key const, const Key) ' –

Trả lời

99

Bạn được phép rõ ràng và khuyến khích thêm chuyên ngành đến không gian tên std *. (Chỉ và về cơ bản) cách chính xác để thêm một hàm băm là thế này:

namespace std { 
    template <> struct hash<Foo> 
    { 
    size_t operator()(const Foo & x) const 
    { 
     /* your code here, e.g. "return hash<int>()(x.value);" */ 
    } 
    }; 
} 

*) chừng nào (chuyên ngành phổ biến khác mà bạn có thể xem xét hỗ trợ được std::less, std::equal_tostd::swap.) một trong những loại có liên quan là do người dùng xác định, tôi giả sử.

+1

trong khi điều này là có thể, bạn có thường khuyên bạn nên làm theo cách này? Tôi muốn dùng instantiation 'unorder_map ' thay vào đó, để tránh làm hỏng ngày của ai đó với việc kinh doanh ADL vui nhộn. (** Chỉnh sửa ** [Lời khuyên của Pete Becker về chủ đề này] (http://bytes.com/topic/c/answers/820457-how-have-user-defined-hash-unordered_map)) – sehe

+2

@sehe: Nếu bạn có một hàm făm băm nằm xung quanh đó là cấu hình mặc định, có lẽ, nhưng tại sao? (Bình đẳng là dễ dàng hơn, vì bạn chỉ cần thực hiện thành viên -'operator == '.) Triết lý chung của tôi là nếu chức năng là tự nhiên và về cơ bản chỉ" đúng "(như so sánh cặp từ vựng), sau đó tôi thêm nó vào 'std'. Nếu nó là một cái gì đó đặc biệt (như so sánh cặp không theo thứ tự), thì tôi làm cho nó đặc trưng cho một loại container. –

+1

Nên là 'toán tử size_t() (const Foo & x) const'. – aschepler

4

@Kerrek SB đã bao gồm 1) và 3).

2) Mặc dù g ++ và VC10 khai báo std::hash<T>::operator() với các chữ ký khác nhau, cả triển khai thư viện đều tuân thủ Tiêu chuẩn.

Tiêu chuẩn không chỉ định các thành viên của std::hash<T>. Nó chỉ nói rằng mỗi chuyên môn như vậy phải đáp ứng cùng một yêu cầu "băm" cần thiết cho đối số mẫu thứ hai của std::unordered_set và cứ tiếp tục như vậy.Cụ thể:

  • Hash loại H là một đối tượng chức năng, với ít nhất một đối số kiểu Key.
  • H là bản sao có thể cấu hình được.
  • H có thể bị hủy.
  • Nếu h là một biểu hiện của loại H hoặc const H, và k là một biểu hiện của một loại chuyển đổi thành (có thể const) Key, sau đó h(k) là một biểu thức có giá trị với loại size_t.
  • Nếu h là một biểu hiện của loại H hoặc const H, và u là một vế trái của loại Key, sau đó h(u) là một biểu thức có giá trị với loại size_t mà không sửa đổi u.
+0

Không, việc triển khai không tuân thủ tiêu chuẩn, vì chúng cố gắng chuyên 'std :: hash :: operator()' thay vì 'std :: hash ' nói chung và chữ ký 'std :: hash : : operator() 'được định nghĩa thực hiện. – ildjarn

+0

@ildjarn: Làm rõ - Tôi đã nói về việc triển khai thư viện, không phải là chuyên môn đã cố gắng. Không chắc chắn chính xác OP muốn hỏi. – aschepler

6

đặt cược của tôi sẽ được trên Hash mẫu luận cho unordered_map/unorder_set/... lớp:

#include <unordered_set> 
#include <functional> 

struct X 
{ 
    int x, y; 
    std::size_t gethash() const { return (x*39)^y; } 
}; 

typedef std::unordered_set<X, std::size_t(*)(const X&)> Xunset; 
typedef std::unordered_set<X, std::function<std::size_t(const X&)> > Xunset2; 

int main() 
{ 
    auto hashX = [](const X&x) { return x.gethash(); }; 

    Xunset my_set (0, hashX); 
    Xunset2 my_set2(0, hashX); // if you prefer a more flexible set typedef 
} 

Tất nhiên

  • hashX có thể cũng giống như cũng là một tĩnh toàn cầu chức năng
  • trong trường hợp thứ hai, bạn có thể vượt qua điều đó
    • functor cũ fjtor vv (struct Xhasher { size_t operator(const X&) const; };)
    • std::hash<X>()
    • bất kỳ biểu hiện ràng buộc thỏa mãn chữ ký -
+0

Tôi đánh giá cao rằng bạn có thể viết thứ gì đó không có hàm tạo mặc định, nhưng tôi luôn thấy rằng yêu cầu mỗi bản đồ xây dựng phải nhớ đối số bổ sung là một chút gánh nặng - hơi quá nhiều gánh nặng cho khẩu vị của tôi . Tôi OK với một đối số mẫu rõ ràng, mặc dù chuyên 'std :: hash' vẫn là cách tốt nhất :-) –

+0

* loại người dùng xác định * để giải cứu :-) Nghiêm túc hơn, tôi hy vọng chúng tôi sẽ tát chúng trên cổ tay đã ở giai đoạn mà lớp của họ chứa một 'char *'! –

+0

Hmm ... bạn có ví dụ về cách chuyên môn 'băm' can thiệp qua ADL không? Ý tôi là, nó hoàn toàn chính đáng, nhưng tôi có một thời gian khó khăn để đưa ra một vấn đề. –

Các vấn đề liên quan