为用户定义类型的shared_ptr专门化std库函数是否合法?

Ric*_*Tea 5 c++ templates c++11

该标准说明了以下关于标准库中的特殊化模板(通过什么可以而且我不能专注于std命名空间?)

只有当声明取决于用户定义的类型并且特化符合原始模板的标准库要求且未明确禁止时,程序才可以将任何标准库模板的模板特化添加到命名空间std.

使用专用于用户定义类的标准库类来专门化标准库模板是否合法?

例如,专门std::hashstd::shared_ptr<MyType>

通过阅读上段和相关问题,听起来应该如此,因为专业化的宣言依赖于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);
}
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 4

是的,这是合法的。

甚至值得怀疑的是,专门从事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);
    }
  };

}
Run Code Online (Sandbox Code Playgroud)

现在这允许 3 个定制点。

首先,如果您std::size_t hash(T const&)在包含 的命名空间中重写T,它会选择它。

如果做不到这一点,如果你专注notstd::hasher_impl<T, void>于你的类型T,它就会选择它。

第三,如果这两个都失败,它会调用std::hash<T>,获取任何专业化。

然后你可以这样做:

std::unordered_set<std::shared_ptr<MyType>, ::notstd::hasher> mySet;
Run Code Online (Sandbox Code Playgroud)

并添加:

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);
  }
};
Run Code Online (Sandbox Code Playgroud)

这应该会给你一个关于 的智能哈希shared_ptr<MyType>

id这保留了有人对 a进行更改的危险,这会以非本地方式shared_ptr<MyType>破坏包含 a 的每个容器。shared_ptr<MyType>

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