2017-03-06 120 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>的容器。

共享状态是魔鬼;如果你真的担心复制这些昂贵的东西,考虑在写指针上写一个副本。

相关问题