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

Is it legal to specialize std library functions for a shared_ptr of a user defined type?

本文关键字:ptr shared 是否 类型 库函数 std 用于 定义 用户      更新时间:2023-10-16

该标准对标准库中的专用模板有以下说明(通过我可以和不能专注于 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);
}

是的,这是合法的。

在某一时刻专门从事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&),它会选取它。

如果做不到这一点,如果你专门为你的Tnotstd::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>的每个容器。

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