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

时间:2017-03-06 17:01:53

标签: c++ c++11 templates

标准说明了以下关于标准库中的专门化模板(通过What can and can't I specialize in the std namespace?

  

程序可能会添加模板   专门用于任何标准库模板到命名空间std   如果声明取决于用户定义的类型和   专业化符合标准库的要求   原始模板并未明确禁止。

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

例如,为std::hash专门设置std::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);
}

1 个答案:

答案 0 :(得分:3)

是的,这是合法的。

在某一点上专注于std::shared_ptr<int>甚至是合法的。我不知道他们是否将标准中的歧义修补为缺陷。

请注意,这是一个糟糕的哈希实现全局使用。首先,因为它不支持null共享指针。其次,因为哈希共享指针总是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点定制。

首先,如果您在包含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;

并添加:

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>上为您提供智能哈希。

这可以防止有人在id上更改shared_ptr<MyType>,从而以非本地方式打破包含shared_ptr<MyType>的每个容器。

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