2017-03-06 46 views
5

Tiêu chuẩn nói như sau về chuyên các mẫu từ thư viện chuẩn (thông qua What can and can't I specialize in the std namespace?)Có hợp pháp để chuyên chức năng thư viện std cho shared_ptr thuộc loại người dùng được xác định không?

Một chương trình có thể thêm một mẫu chuyên môn hóa cho bất kỳ thư viện mẫu tiêu chuẩn để namespace std chỉ nếu việc kê khai phụ thuộc vào một user- loại được xác định và chuyên môn đáp ứng các yêu cầu của thư viện chuẩn đối với mẫu gốc và không bị cấm rõ ràng.

Có hợp pháp để chuyên các mẫu thư viện chuẩn với lớp thư viện chuẩn chuyên biệt với lớp do người dùng xác định không? Ví dụ, chuyên std::hash cho std::shared_ptr<MyType>?

Từ đọc đoạn trên và câu hỏi được liên kết, có vẻ như nó phải như vậy, vì tuyên bố chuyên môn phụ thuộc vào MyType, tuy nhiên "Trừ khi bị cấm một cách rõ ràng" làm tôi hơi lo lắng.

Ví dụ dưới đây biên dịch và hoạt động như mong đợi (AppleClang 7.3), nhưng nó có hợp pháp không?

#include <unordered_set> 
#include <memory> 
#include <cassert> 
#include <string> 

struct MyType { 
    MyType(std::string id) : id(id) {} 
    std::string id; 
}; 

namespace std { 
    template<> 
    struct hash<shared_ptr<MyType>> { 
     size_t operator()(shared_ptr<MyType> const& mine) const { 
      return hash<string>()(mine->id); 
     } 
    }; 

    template<> 
    struct equal_to<shared_ptr<MyType>> { 
     bool operator()(shared_ptr<MyType> const& lhs, shared_ptr<MyType> const& rhs) const { 
      return lhs->id == rhs->id; 
     } 
    }; 
} 

int main() { 
    std::unordered_set<std::shared_ptr<MyType>> mySet; 
    auto resultA = mySet.emplace(std::make_shared<MyType>("A")); 
    auto resultB = mySet.emplace(std::make_shared<MyType>("B")); 
    auto resultA2 = mySet.emplace(std::make_shared<MyType>("A")); 
    assert(resultA.second); 
    assert(resultB.second); 
    assert(!resultA2.second); 
} 
+2

Có, điều này là hợp pháp, không có giới hạn về chuyên môn của 'std :: hash' ngoại trừ * DefaultConstructible, CopyAssignable, Swappable và Destructible * (và tất cả các yêu cầu khác không liên quan gì đến * . Một ví dụ về * "rõ ràng bị cấm" * chuyên môn chuyên 'std :: numeric_limits' cho các loại tiêu chuẩn không phải số học. – Holt

+0

Bạn cần có một cái nhìn về tra cứu Depument Dependend, bạn không cần thêm các hàm vào không gian tên std. –

Trả lời

3

Có, đó là hợp pháp.

Thậm chí còn có lý do pháp lý để chuyên cho std::shared_ptr<int> tại một thời điểm; Tôi không biết liệu họ có vá sự mập mờ đó trong tiêu chuẩn như một khiếm khuyết hay không.

Lưu ý rằng đó là một sự triển khai kém của một băm để sử dụng toàn cầu. Đầu tiên, bởi vì nó không hỗ trợ null chia sẻ con trỏ. Thứ hai, bởi vì băm một con trỏ được chia sẻ như luôn luôn giá trị int là vấn đề. Nó thậm chí còn nguy hiểm, bởi vì nếu một con trỏ chia sẻ với một int trong một container có thay đổi int đó, bạn vừa phá vỡ chương trình.

Cân nhắc tự tạo cho mình các loại trường hợp này.

namespace notstd { 
    template<class T, class=void> 
    struct hasher_impl:std::hash<T>{}; 

    namespace adl_helper { 
    template<class T> 
    std::size_t hash(T const& t, ...) { 
     return ::notstd::hasher_impl<T>{}(t); 
    } 
    }; 
    namespace adl_helper2 { 
    template<class T> 
    std::size_t hash_helper(T const& t) { 
     using ::notstd::adl_helper::hash; 
     return hash(t); 
    } 
    } 
    template<class T> 
    std::size_t hash(T const& t) { 
    return ::notstd::adl_helper2::hash_helper(t); 
    } 

    struct hasher { 
    template<class T> 
    std::size_t operator()(T const& t)const { 
     return hash(t); 
    } 
    }; 

} 

Bây giờ điều này cho phép 3 điểm tùy chỉnh.

Đầu tiên, nếu bạn ghi đè std::size_t hash(T const&) trong không gian tên có chứa T, hãy chọn nó.

Nếu không, nếu bạn chuyên notstd::hasher_impl<T, void> cho loại của bạn T, hãy chọn nó.

Thứ ba, nếu cả hai trường hợp không thành công, nó sẽ gọi std::hash<T>, chọn bất kỳ chuyên môn nào.

Sau đó, bạn có thể làm:

std::unordered_set<std::shared_ptr<MyType>, ::notstd::hasher> mySet; 

và thêm:

struct MyType { 
    MyType(std::string id) : id(id) {} 
    std::string id; 
    friend std::size_t hash(MyType const& self) { 
    return ::notstd::hash(self.id); 
    } 
    friend std::size_t hash(std::shared_ptr<MyType> const& self) { 
    if (!self) return 0; 
    return ::notstd::hash(*self); 
    } 
}; 

mà nên cung cấp cho bạn một hash thông minh trên trên shared_ptr<MyType>.

Điều này giúp giữ mối nguy hiểm mà ai đó thay đổi id trên shared_ptr<MyType> làm vỡ mọi thùng chứa chứa shared_ptr<MyType> theo cách không phải là địa phương.

Trạng thái được chia sẻ là ma quỷ; xem xét viết một bản sao trên con trỏ viết nếu bạn đang thực sự lo lắng về việc sao chép những thứ này là đắt tiền.

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