2017-03-06 48 views
5

標準說,有關標準庫的專業模板(通過What can and can't I specialize in the std namespace?爲用戶定義類型的shared_ptr專門化std庫函數是否合法?

程序可以添加一個模板 專業化爲任何標準庫模板std名字空間僅 如果聲明取決於用戶以下並且 專業化符合 原始模板的標準庫要求,並且未明確禁止。

將標準庫模板與專門用戶定義類的標準庫類專門化是合法的嗎?

例如,專門爲std::shared_ptr<MyType>專門設計std::hash

從閱讀上面的段落和鏈接的問題,聽起來應該是這樣,因爲專業化的聲明依賴於MyType,但是「除非明確禁止」稍微擔心我。

下面的例子編譯和按預期工作(AppleClang 7.3),但它是合法的嗎?

#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

是的,這是合法的,除了* DefaultConstructible,CopyAssignable,Swappable和Destructible *(以及與*無關的所有其他要求*「明確禁止」*),'std :: hash'沒有特殊限制。 。 *「明確禁止」*特化的一個例子是專門爲非算術標準類型的'std :: numeric_limits'。 – Holt

+0

你應該看看參數Dependend查找,你不需要添加函數到std命名空間。 –

回答

3

是的,那是合法的。

在某一點上專門爲std::shared_ptr<int>這是甚至是合法的;我不知道他們是否將標準中的模棱兩可作爲缺陷修補。

請注意,這是一個糟糕的全球使用哈希實現。首先,因爲它不支持空分享指針。其次,因爲散列一個共享指針一如既往的int值是有問題的。這甚至是危險的,因爲如果一個容器中的int的共享指針具有該int變化,那麼你只是破壞了該程序。

考慮爲這類情況製作自己的散列器。

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); 
    } 
    }; 

} 

現在,這允許3個點的定製。

首先,如果您在包含T的命名空間中覆蓋std::size_t hash(T const&),它會將其選中。

如果您專門爲您的T類型專門設計了notstd::hasher_impl<T, void>,那麼它就會撿起來。第三,如果這兩個都失敗了,它會調用std::hash<T>,拿起任何專業。

然後,你可以這樣做:

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

,並添加:

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); 
    } 
}; 

這應該給你一個智能散列上shared_ptr<MyType>

這樣可以避免某人在shared_ptr<MyType>上更改id的危險,該操作會以非本地方式打破每個包含shared_ptr<MyType>的容器。

共享狀態是魔鬼;如果你真的擔心複製這些昂貴的東西,考慮在寫指針上寫一個副本。

相關問題