概述
该库实现了一个可扩展的框架,用于实现支持用户定义类型的哈希算法。其结构主要基于 Howard Hinnant、Vinnie Falco 和 John Bytheway 的论文 “类型不知道 #”。
该设计的关键特性是**哈希算法**(它接收未类型化的字节流(即**消息**)并生成哈希值(即**消息摘要**))与 `hash_append` 函数(它接收一个类型,负责将该类型的值转换为字节序列并将其馈送给**哈希算法**)之间的清晰分离。
这使得用户定义类型的哈希支持可以一次性编写,然后自动与任何哈希算法一起使用,甚至包括在类型定义时尚不可用的算法。
提供了以下流行的哈希算法
但用户也可以编写自己的算法;只要哈希算法符合概念,`hash_append` 就可以与它一起工作,所有支持 `hash_append` 的用户定义类型也可以。
哈希字节序列
该库解决了两个主要用例:哈希未类型化的字节序列和哈希 C++ 对象。
未类型化的字节序列(也称为**消息**)通过传递给**哈希算法**进行哈希,哈希算法然后生成哈希值(或**消息摘要**)。
相同的**哈希算法**,当传递相同的**消息**时,总是会生成相同的**摘要**。(已发布的算法提供**消息**和相应的**摘要**对,称为**测试向量**,以实现独立实现的验证。)
(要哈希 C++ 对象,首先将其转换为(序列化为)字节序列,然后传递给**哈希算法**。)
哈希算法要求
一个**哈希算法**必须具有以下结构,并满足以下最低要求
struct HashAlgorithm
{
using result_type = /*integral or array-like*/;
static constexpr std::size_t block_size = /*...*/; // optional
HashAlgorithm();
explicit HashAlgorithm( std::uint64_t seed );
HashAlgorithm( void const* seed, std::size_t n );
HashAlgorithm( HashAlgorithm const& r );
HashAlgorithm& operator=( HashAlgorithm const& r );
void update( void const* data, std::size_t n );
result_type result();
};
result_type
嵌套类型 `result_type` 是生成的哈希值的类型。它可以是无符号整数类型(不是 `bool`),通常是 `std::uint32_t` 或 `std::uint64_t`,或者是值类型为 `unsigned char` 的 `std::array` 类类型。
通常,非密码哈希函数具有整数 `result_type`,而密码哈希函数具有数组类 `result_type`,但这不是必需的。
提供的实用函数 `get_integral_result` 可用于从任何有效的 `result_type` 获取整数哈希值。
block_size
加密哈希函数提供一个 `block_size` 值,它是它们的块大小(例如 MD5 为 64,SHA2-512 为 128),并且是实现相应 HMAC 所必需的。
`block_size` 是一个可选要求。
默认构造函数
所有**哈希算法**都必须是默认可构造的。默认构造函数将哈希算法的内部状态初始化为其规范中发布的初始值。
例如,`md5_128` 的默认构造函数对应于调用参考实现的函数 `MD5_Init`。
接受整数种子的构造函数
所有**哈希算法**都必须可以从 `std::uint64_t` 类型的值构造,该值用作种子。
使用种子值 0 等同于默认构造。
不同的种子值会导致内部状态以不同方式初始化,因此,由不同种子初始化的哈希算法实例在传递相同的消息时会产生不同的哈希值。
使用随机(从外部不可观察)值进行播种有助于防止 哈希洪水攻击。
接受字节序列种子的构造函数
所有**哈希算法**都可以从 `unsigned char` 值的种子序列构造(这使得所有哈希算法都成为 密钥哈希函数。)
空序列(长度为 0 的序列)会生成一个默认构造的实例。
不同的种子序列会生成不同初始化的实例。
更新
函数 `update` 是将输入消息提供给哈希算法的机制。
多次调用 `update` 等同于使用各个调用中连接的字节序列调用一次。也就是说,输入消息可以分部分提供,并且其分部分的方式无关紧要,也不会影响最终的哈希值。
给定
Hash hash; // some hash algorithm
unsigned char message[6] = { /*...*/ }; // some input message
以下 `update` 调用
hash.update( message, 6 );
等效于
hash.update( message, 4 );
hash.update( message + 4, 2 );
和
for( int i = 0; i < 6; ++i ) hash.update( &message[i], 1 );
结果
在通过调用 `update` 提供整个输入消息后,可以通过调用 `result` 获取最终哈希值。
调用 `result` 通过根据具体算法规范填充消息,选择性地将消息长度合并到状态中,以及对状态执行最终化操作(同样根据具体算法指定)来最终化内部状态。
然后通过转换内部状态获得最终哈希值并返回。
请注意,`result` 是非 const 的,因为它会改变内部状态。允许多次调用 `result`;随后的调用会再次执行状态最终化,并因此产生 `result_type` 值的伪随机序列。这可用于有效扩展哈希函数的输出。例如,通过调用 `result` 四次,可以从 `result_type` 为 64 位的哈希算法获得 256 位结果。
作为一个玩具示例,**不适用于生产环境**,以下是如何在库提供的 FNV-1a 实现之上编写随机数生成器的方法
std::uint64_t random()
{
static boost::hash2::fnv1a_64 hash;
return hash.result();
}
编译时哈希
在 C++14 下,可以在编译时调用一些哈希算法。这些算法提供以下接口
struct HashAlgorithm
{
using result_type = /*integral or array-like*/;
static constexpr std::size_t block_size = /*...*/; // optional
constexpr HashAlgorithm();
explicit constexpr HashAlgorithm( std::uint64_t seed );
HashAlgorithm( void const* seed, std::size_t n );
constexpr HashAlgorithm( unsigned char const* seed, std::size_t n );
constexpr HashAlgorithm( HashAlgorithm const& r );
constexpr HashAlgorithm& operator=( HashAlgorithm const& r );
void update( void const* data, std::size_t n );
constexpr void update( unsigned char const* data, std::size_t n );
constexpr result_type result();
};
除了添加的 `constexpr` 限定符之外,唯一的区别是字节种子构造函数和 `update` 有第二个重载,它接受 `unsigned char const*` 而不是 `void const*`。(在 C++26 之前,指向 `void` 的指针不能在 `constexpr` 函数中使用。)
提供的哈希算法
FNV-1a
Fowler-Noll-Vo 哈希函数 作为一类一次处理一个字节输入的哈希函数的代表。对于每个输入字符 `ch`,32 或 64 位状态通过操作 `state = (state ^ ch) * fnv_prime` 进行更新。
FNV-1a 是非密码的,相对于最先进的哈希函数来说相对较弱(尽管在其类别中表现良好),但在输入字符串较短时速度很快。
xxHash
xxHash 是 Yann Collet 开发的一种快速非密码哈希算法。
其速度(在 Xeon E5-2683 v4 @ 2.10GHz 上,`xxhash_32` 约为 5GB/s,`xxhash_64` 约为 10GB/s)使其非常适合快速生成文件或数据完整性校验和。
SipHash
Jean-Philippe Aumasson 和 Daniel J. Bernstein(论文)的 SipHash 旨在挫败针对接收外部不受信任输入的哈希表(例如 HTTP 消息头或 JSON 对象)的 哈希洪水攻击。
它不是加密哈希函数(尽管其设计类似于加密哈希函数),因为在已知初始种子的情况下,它不提供抗碰撞性。
然而,它是一个密码学上强大的带密钥哈希函数(或伪随机函数,PRF)。如果攻击者不知道初始种子,则很难计算出碰撞或通过观察输出来恢复种子。
SipHash 已被采纳为暴露于外部输入的哈希表的实际标准哈希函数,并在 Python、Perl、Ruby、Rust 和其他语言中使用。
SipHash 是用于暴露于外部输入的哈希表的推荐哈希函数。作为最佳实践,它应该用每个连接变化的随机值而不是每个进程固定的值进行播种。
MD5
MD5 于 1991 年由 Ron Rivest 设计,曾是最著名和最广泛使用的 加密哈希函数,但已被破解,不再被视为适用于任何目的的加密函数。它产生 128 位摘要。
当需要加密强度时,新代码不应再使用 MD5,除非实现现有规范或协议要求使用它。
请改用 SHA2-512/256(或 32 位代码中的 SHA2-256)。
如果您需要精确的 128 位摘要,请改用 RIPEMD-128。请注意,128 位摘要不再被认为是加密的,因为复杂度为 264 的攻击在资金充足的攻击者的能力范围内。
SHA-1
SHA-1 现在被认为对资金充足的攻击者不安全,不应再在新代码中使用。请改用 SHA2-512/256、32 位代码中的 SHA2-256,或者,如果您需要精确的 160 位摘要,请改用 RIPEMD-160。
SHA-2
SHA-2 是一个加密哈希函数家族,也由 NSA 设计,最初由 NIST 于 2002 年 发布,并于 2015 年 更新。它包括 SHA2-224、SHA2-256、SHA2-384、SHA2-512、SHA2-512/224 和 SHA2-512/256,每个都生成相应位长度的摘要。
其中,SHA2-256 和 SHA2-512 是基本算法,其余是截断摘要的变体。
SHA-2 函数尚未被破解并广泛使用,尽管存在较新的标准(SHA-3)。
SHA2-256 及其截断变体 SHA2-224 使用 32 位操作,因此在 32 位平台上不会损失性能。
SHA2-512 及其截断变体 SHA2-384、SHA2-512/224 和 SHA2-512/256 使用 64 位操作,在 64 位平台上比 SHA2-256 快约 1.5 倍,但在 32 位代码中慢两倍。
在 64 位平台上,SHA2-512/256 和 SHA2-512/224 应优先于 SHA2-256 和 SHA2-224,不仅因为速度,还因为它们对长度扩展攻击具有抵抗力,因为它们不会在最终摘要中暴露其内部状态的所有位。
SHA-3
SHA-3 包括 6 种算法:SHA3-224、SHA3-256、SHA3-384、SHA3-512、SHAKE128 和 SHAKE256。
SHA3 函数输出固定大小的摘要,而 SHAKE128 和 SHAKE256 是经批准的可扩展输出函数,也称为 XOFs。XOFs 允许以定义的方式生成任意长的消息摘要。
与 SHA-2 不同,SHA3-224 不是 SHA3-256 算法的截断版本。相反,所有 SHA-3 算法都通过截断 Keccak 算法置换的 1600 位状态的下部来创建摘要。XOFs 以固定大小的块增量返回其摘要,使用 FIPS 202 算法 8 的步骤 8 中指定的截断。
SHA-3 对所有家族中的算法都使用 64 位操作。
BLAKE-2
BLAKE-2 是一种加密哈希函数,以其极快的速度而著称,远远超过 MD5、SHA-1、SHA-2 和 SHA-3 等其他常见算法。实现了 BLAKE2b(针对 64 位平台优化)和 BLAKE2s(针对 8 到 32 位平台优化)。BLAKE2b 的固定大小摘要为 512 位,BLAKE2s 使用 256 位摘要。
BLAKE2 也明确设计了带密钥哈希,使其功能上等同于消息认证码 (MAC)。种子构造函数充当密钥,BLAKE2b 最多使用输入的前 64 字节作为密钥,BLAKE2s 最多使用前 32 字节。多余的输入通过将其传递给 `update()` 函数然后调用 `result()` 来处理。HMAC typedefs 仍然提供以方便和兼容现有协议,例如 Noise。当与 `hmac
RIPEMD-160, RIPEMD-128
RIPEMD-160 于 1996 年设计,是一种加密哈希函数,其知名度低于 MD5 和 SHA-1,但最近因其在比特币和其他加密货币中的使用而流行。
尽管它尚未被破解,但没有理由在新代码中优先使用它而不是 SHA-2。
RIPEMD-128 是 RIPEMD-160 的截断变体。(请注意,128 位摘要不再被认为是加密的,因为复杂度为 264 的攻击在资金充足的攻击者的能力范围内。)
HMAC
消息认证码与摘要的区别在于它依赖于消息内容和秘密**密钥**;相比之下,消息摘要只依赖于消息内容。
尽管库提供的所有哈希算法都可以通过最初使用秘密密钥播种哈希算法(通过调用接受字节序列的构造函数)来生成消息认证码,但哈希算法通常并非设计为以这种方式使用,并且这种使用尚未经过加密分析和验证。(SipHash 是一个例外;它专门设计为 MAC。)
HMAC 算法以类模板 `hmac
提供了常见 HMAC 实例的便捷别名。例如,定义 `md5_128` 的 `md5.hpp` 头文件也定义了 `hmac_md5_128` 作为 `hmac
选择哈希算法
如果您的用例需要加密强度,请使用 SHA2-512/256(或 32 位代码中的 SHA2-256)作为摘要,并使用相应的 HMAC 作为消息认证码。
注意
|
当需要或期望加密安全性时,不再推荐长度小于 256 位的摘要。 |
对于计算文件或内容校验和,当速度是关键且外部引起的冲突不是问题时,请使用 xxHash-64。
注意
|
xxHash-32 在 32 位代码中会更快,但由于它只产生 32 位结果,当项目数量达到数万时,冲突将成为一个问题,这通常是不可接受的。 |
对于大量项目(数百万),64 位可能不够;在这种情况下,请使用 MD5 或 xxHash-64,扩展到 128 位。
注意
|
尽管 MD5 不再是密码学安全的,但在不需要加密强度时仍可使用。 |
对于哈希表,默认情况下使用 SipHash,使用每个连接或每个容器变化的随机(从外部不可预测)种子。避免使用固定的进程范围种子。切勿在没有种子的情况下使用 SipHash。
对于键很短(3-4-5 字节)、未暴露于外部输入的哈希表,您可以使用 FNV-1a,尽管例如 `boost::unordered_flat_map` 的默认哈希函数通常会表现更好。
哈希 C++ 对象
哈希 C++ 对象的传统方法是让它们负责提供哈希值。例如,标准遵循这种方法,通过让每个类型 `T` 负责实现 `std::hash
当然,这意味着特定的哈希算法因类型而异,并且在一般情况下是完全不透明的。
该库采用不同的方法;哈希算法由用户已知并选择。C++ 对象通过首先转换为表示其值的字节序列(**消息**),然后将其传递给哈希算法来进行哈希。
转换必须遵守以下要求
-
相等对象必须生成相同的消息;
-
不同对象应该生成不同的消息;
-
对象应始终生成非空消息。
前两个要求直接源于哈希值要求,而第三个要求则更为微妙,旨在防止诸如不同序列 `[[1], [], []]` 和 `[[], [1], []]` 生成相同消息的情况。(这类似于所有 C++ 对象 `sizeof` 不为零的要求,包括空对象。)
在此库中,转换由函数 `hash_append` 执行。它的声明如下
template<class Hash, class Flavor = default_flavor, class T>
constexpr void hash_append( Hash& h, Flavor const& f, T const& v );
调用 `hash_append(h, f, v)` 的效果是调用 `h.update(p, n)` 一次或多次(但从不为零次)。这些调用的组合结果形成了对应于 `v` 的消息。
`hash_append` 本身处理以下类型 `T`
-
整数类型(有符号和无符号整数、字符类型、`bool`);
-
浮点类型(`float` 和 `double`);
-
枚举类型;
-
指针类型(对象和函数,但不包括指向成员的指针类型);
-
C 数组;
-
容器和范围(提供 `begin()` 和 `end()` 的类型);
-
无序容器和范围;
-
固定大小容器(`std::array`,`boost::array`);
-
类元组类型(`std::pair`,`std::tuple`);
-
描述的类(使用 Boost.Describe)。
不在上述类别中的用户定义类型可以通过声明具有适当参数的 `tag_invoke` 函数重载来提供 `hash_append` 支持。
`hash_append` 的第二个参数,即**风格**,用于在可能存在多种行为且需要选择时控制序列化过程。它目前包含以下成员
-
static constexpr endian byte_order; // 原生、小端或大端
-
using size_type = std::uint32_t; // 或 std::uint64_t
风格的 `byte_order` 成员影响标量 C++ 对象如何序列化为字节。例如,`uint32_t` 整数 `0x01020304` 当 `byte_order` 为 `endian::big` 时可以序列化为 `{ 0x01, 0x02, 0x03, 0x04 }`,当 `byte_order` 为 `endian::little` 时可以序列化为 `{ 0x04, 0x03, 0x02, 0x01 }`。
值 `endian::native` 表示使用当前平台的字节顺序。这通常会带来更高的性能,因为它允许 `hash_append` 将底层对象字节直接传递给哈希算法,而无需任何处理。
风格的 `size_type` 成员类型影响容器和范围大小(通常是 `size_t` 类型)如何序列化。由于 `size_t` 的字节大小可能不同,直接序列化该类型会导致在编译为 64 位或 32 位时哈希值不同。使用固定宽度类型可避免此问题。
在 `boost/hash2/flavor.hpp` 中定义了三种预定义风格
struct default_flavor
{
using size_type = std::uint32_t;
static constexpr auto byte_order = endian::native;
};
struct little_endian_flavor
{
using size_type = std::uint32_t;
static constexpr auto byte_order = endian::little;
};
struct big_endian_flavor
{
using size_type = std::uint32_t;
static constexpr auto byte_order = endian::big;
};
当不传递风格调用 `hash_append` 时,使用默认风格:`hash_append(h, {}, v);`。它会带来更高的性能,但哈希值与字节序有关。
连续可哈希类型
`hash_append(h, f, v)` 做的第一件事是检查类型是否在请求的字节顺序下**连续可哈希**,通过测试 `is_contiguously_hashable
整数类型
当 `T` 是整数类型(`bool`,有符号或无符号整数类型,如 `int` 或 `unsigned long`,或字符类型,如 `char8_t` 或 `char32_t`)时,`v` 会根据请求的字节顺序转换为其字节表示(`unsigned char` 数组,大小为 `sizeof(T)`)。
然后 `hash_append` 调用 `h.update(p, n)`,其中 `p` 是此表示的地址,`n` 是其大小。
例如,类型为 `std::uint32_t` 的值 `0x01020304`,当 `Flavor::byte_order` 为 `endian::little` 时,会转换为数组 `{ 0x04, 0x03, 0x02, 0x01 }`。
int main()
{
boost::hash2::fnv1a_32 h1;
std::uint32_t v1 = 0x01020304;
boost::hash2::hash_append( h1, boost::hash2::little_endian_flavor(), v1 );
boost::hash2::fnv1a_32 h2;
unsigned char v2[] = { 0x04, 0x03, 0x02, 0x01 };
h2.update( v2, sizeof(v2) );
assert( h1.result() == h2.result() );
}
浮点类型
当 `T` 是浮点类型(目前只支持 `float` 和 `double`)时,`v` 使用等效于 `std::bit_cast` 的方式转换为相同大小的无符号整数类型,然后使用该转换后的值调用 `hash_append`。
int main()
{
boost::hash2::fnv1a_32 h1;
float v1 = 3.14f;
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
std::uint32_t v2 = 0x4048f5c3;
boost::hash2::hash_append( h2, {}, v2 );
assert( h1.result() == h2.result() );
}
然而,这里有一个微妙之处。哈希函数 `H` 的要求是,如果 `x == y`,那么 `H(x) == H(y)`。但是 `+0.0 == -0.0`,尽管这两个值的位表示不同。
因此,为了满足这个要求,如果 `v` 是负零,它首先被替换为相同类型的正零,然后才进行 `bit_cast` 转换为整数。
int main()
{
boost::hash2::fnv1a_32 h1;
boost::hash2::hash_append( h1, {}, +0.0 );
boost::hash2::fnv1a_32 h2;
boost::hash2::hash_append( h2, {}, -0.0 );
assert( h1.result() == h2.result() );
}
枚举类型
当 `T` 是枚举类型时,`v` 会转换为 `T` 的基础类型,然后将转换后的值传递给 `hash_append`。
enum E: int
{
v1 = 123
};
int main()
{
boost::hash2::fnv1a_32 h1;
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
int v2 = 123;
boost::hash2::hash_append( h2, {}, v2 );
assert( h1.result() == h2.result() );
}
指针
当 `T` 是指针类型时,它会使用 `reinterpret_cast` 转换为 `std::uintptr_t`,并将转换后的值传递给 `hash_append`。
int x1 = 0;
int main()
{
boost::hash2::fnv1a_32 h1;
boost::hash2::hash_append( h1, {}, &x1 );
boost::hash2::fnv1a_32 h2;
boost::hash2::hash_append( h2, {}, reinterpret_cast<std::uintptr_t>(&x1) );
assert( h1.result() == h2.result() );
}
数组
当 `T` 是数组类型 `U[N]` 时,`v` 的元素会按顺序传递给 `hash_append`。
这是通过调用 `hash_append_range(h, f, v + 0, v + N)` 来实现的。
int main()
{
boost::hash2::fnv1a_32 h1;
int v1[4] = { 1, 2, 3, 4 };
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
boost::hash2::hash_append_range( h2, {}, v1 + 0, v1 + 4 );
assert( h1.result() == h2.result() );
boost::hash2::fnv1a_32 h3;
boost::hash2::hash_append( h3, {}, v1[0] );
boost::hash2::hash_append( h3, {}, v1[1] );
boost::hash2::hash_append( h3, {}, v1[2] );
boost::hash2::hash_append( h3, {}, v1[3] );
assert( h1.result() == h3.result() );
}
范围
当 `T` 是**范围**(`boost::container_hash::is_range
-
当 `T` 是**无序范围**(`boost::container_hash::is_unordered_range
::value` 为 `true`)时,`hash_append` 调用 `hash_append_unordered_range(h, f, v.begin(), v.end())`。`hash_append_unordered_range` 以一种其顺序不影响哈希值的方式从范围元素派生哈希值。 -
当 `T` 是**连续范围**(`boost::container_hash::is_contiguous_range
::value` 为 `true`)时,`hash_append` 首先调用 `hash_append_range(h, f, v.data(), v.data() + v.size())`,然后,如果 `has_constant_size ::value` 为 `false`,它会调用 `hash_append_size(h, f, v.size())`。 -
否则,`hash_append` 首先调用 `hash_append_range(h, f, v.begin(), v.end())`,然后,如果 `has_constant_size
::value` 为 `false`,它会调用 `hash_append_size(h, f, m)`,其中 `m` 是 `std::distance(v.begin(), v.end())`。
作为一种特殊情况,为了满足 `hash_append` 调用必须始终导致至少一次 `Hash::update` 调用的要求,对于大小为 0 的固定大小范围,会调用 `hash_append(h, f, '\x00')`。
int main()
{
boost::hash2::fnv1a_32 h1;
std::vector<int> v1 = { 1, 2, 3, 4 };
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
std::list<int> v2 = { 1, 2, 3, 4 };
boost::hash2::hash_append( h2, {}, v2 );
assert( h1.result() == h2.result() );
boost::hash2::fnv1a_32 h3;
boost::hash2::hash_append_range( h3, {}, v1.data(), v1.data() + v1.size() );
boost::hash2::hash_append_size( h3, {}, v1.size() );
assert( h1.result() == h3.result() );
boost::hash2::fnv1a_32 h4;
boost::hash2::hash_append_range( h4, {}, v2.begin(), v2.end() );
boost::hash2::hash_append_size( h4, {}, std::distance(v2.begin(), v2.end()) );
assert( h2.result() == h4.result() );
}
元组
当 `T` 是一个元组(`boost::container_hash::is_tuple_like
作为一种特殊情况,为了满足 `hash_append` 调用必须始终导致至少一次 `Hash::update` 调用的要求,对于大小为 0 的元组,会调用 `hash_append(h, f, '\x00')`。
int main()
{
boost::hash2::fnv1a_32 h1;
std::tuple<int, int, int> v1 = { 1, 2, 3 };
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
boost::hash2::hash_append( h2, {}, get<0>(v1) );
boost::hash2::hash_append( h2, {}, get<1>(v1) );
boost::hash2::hash_append( h2, {}, get<2>(v1) );
assert( h1.result() == h2.result() );
}
描述的类
当 `T` 是一个**描述的类**(`boost::container_hash::is_described_class
struct X
{
int a;
};
BOOST_DESCRIBE_STRUCT(X, (), (a))
struct Y: public X
{
int b;
};
BOOST_DESCRIBE_STRUCT(Y, (X), (b))
int main()
{
boost::hash2::fnv1a_32 h1;
X v1 = { { 1 }, 2 };
boost::hash2::hash_append( h1, {}, v1 );
boost::hash2::fnv1a_32 h2;
boost::hash2::hash_append( h2, {}, v1.a );
boost::hash2::hash_append( h2, {}, v1.b );
assert( h1.result() == h2.result() );
}
作为一种特殊情况,为了满足 `hash_append` 调用必须始终导致至少一次 `Hash::update` 调用的要求,对于没有任何基类或成员的类,会调用 `hash_append(h, f, '\x00')`。
用户定义类型
当 `T` 是不属于上述类别之一的用户定义类型时,它需要通过定义适当的 `tag_invoke` 重载来提供自己的哈希支持。
此 `tag_invoke` 重载需要具有以下形式
template<class Provider, class Hash, class Flavor>
void tag_invoke( boost::hash2::hash_append_tag const&, Provider const& pr, Hash& h, Flavor const& f, X const* v );
其中 `X` 是用户定义的类型。
它可以作为 `X` 命名空间中的一个单独的自由函数定义,但推荐的方法是在 `X` 的定义中将其定义为内联 `friend`
#include <boost/hash2/hash_append_fwd.hpp>
#include <string>
class X
{
private:
std::string a;
int b;
// not part of the salient state
void const* c;
public:
friend bool operator==( X const& x1, X const& x2 )
{
return x1.a == x2.a && x1.b == x2.b;
}
template<class Provider, class Hash, class Flavor>
friend void tag_invoke(
boost::hash2::hash_append_tag const&, Provider const&,
Hash& h, Flavor const& f, X const* v )
{
boost::hash2::hash_append(h, f, v->a);
boost::hash2::hash_append(h, f, v->b);
}
};
此重载需要满足哈希函数的三个要求。实际上,这意味着相等(`operator==`)和哈希(`tag_invoke`)的定义必须在需要包含哪些成员方面保持一致。
在上面的示例中,成员 `c` 不属于对象状态的一部分,因此它既不在 `operator==` 中进行比较,也不在 `tag_invoke` 中的对象**消息**中包含。
`tag_invoke` 的特定实现是类型特定的。通常,它需要将对象值的所有显著部分包含在生成的**消息**中,但确切的实现方式取决于类型。
另一个例子是,如何为“内联字符串”类型(一种将字符存储在类型本身中,直至达到某个最大计数的字符串)实现 `tag_invoke`
#include <boost/hash2/hash_append_fwd.hpp>
#include <algorithm>
#include <cstdint>
class Str
{
private:
static constexpr std::size_t N = 32;
std::uint8_t size_ = 0;
char data_[ N ] = {};
public:
friend constexpr bool operator==( Str const& x1, Str const& x2 )
{
return x1.size_ == x2.size_ && std::equal( x1.data_, x1.data_ + x1.size_, x2.data_ );
}
template<class Provider, class Hash, class Flavor>
friend constexpr void tag_invoke(
boost::hash2::hash_append_tag const&, Provider const&,
Hash& h, Flavor const& f, Str const* v )
{
boost::hash2::hash_append_range_and_size( h, f, v->data_, v->data_ + v->size_ );
}
};
注意
|
此示例仅供说明;实际上,上述类型可能会提供 `begin()`、`end()`、`data()` 和 `size()` 成员函数,这将使其成为**连续范围**,并且内置支持将正确处理。 |
我们忽略的 `tag_invoke` 的 `Provider` 参数接收一个 `hash_append_provider` 类型的对象。该对象包含与在 `<boost/hash2/hash_append_fwd.hpp>` 中声明的函数副本的静态成员函数。这使得 `tag_invoke` 重载可以在不需要包含 `<boost/hash2/hash_append_fwd.hpp>` 的情况下编写,从而使该头文件不依赖于 Hash2 中的任何内容。
例如,以下是我们如何替代地声明上面定义的用户定义类型 `X`
#include <string>
class X
{
private:
std::string a;
int b;
// not part of the salient state
void const* c;
public:
friend bool operator==( X const& x1, X const& x2 )
{
return x1.a == x2.a && x1.b == x2.b;
}
template<class Provider, class Hash, class Flavor>
friend void tag_invoke(
boost::hash2::hash_append_tag const&, Provider const& pr,
Hash& h, Flavor const& f, X const* v )
{
pr.hash_append(h, f, v->a);
pr.hash_append(h, f, v->b);
}
};
使用示例
与无序容器一起使用
要将我们的一个**哈希算法**(例如 `fnv1a_64`)与无序容器(例如 `boost::unordered_flat_map`)一起使用,我们需要创建一个适配器类,该类公开与 `std::hash
为此,在我们的适配器的 `operator()(T const& v)` 成员函数中,我们需要创建一个**哈希算法**的实例 `h`,使用 `hash_append(h, {}, v)` 将 `v` 发送给它,然后使用 `h.result()` 提取结果并将其作为 `std::size_t` 返回。
下面的最小工作示例演示了这种方法。
#include <boost/hash2/fnv1a.hpp>
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/get_integral_result.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <string>
template<class T, class H> class hash
{
public:
std::size_t operator()( T const& v ) const
{
H h;
boost::hash2::hash_append( h, {}, v );
return boost::hash2::get_integral_result<std::size_t>( h );
}
};
int main()
{
using hasher = hash<std::string, boost::hash2::fnv1a_64>;
boost::unordered_flat_map<std::string, int, hasher> map;
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
由于 `hash
using hasher = hash<std::string, boost::hash2::fnv1a_64>;
到
using hasher = hash<std::string, boost::hash2::siphash_64>;
这会起作用,但 SipHash 不打算在没有初始随机种子的情况下使用,而我们没有传递任何种子。为了纠正这一点,我们修改 `hash
#include <boost/hash2/siphash.hpp>
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/get_integral_result.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <string>
template<class T, class H> class hash
{
private:
std::uint64_t seed_;
public:
explicit hash( std::uint64_t seed ): seed_( seed )
{
}
std::size_t operator()( T const& v ) const
{
H h( seed_ );
boost::hash2::hash_append( h, {}, v );
return boost::hash2::get_integral_result<std::size_t>( h );
}
};
int main()
{
std::uint64_t seed = 0x0102030405060708ull;
using hasher = hash<std::string, boost::hash2::siphash_64>;
boost::unordered_flat_map<std::string, int, hasher> map( 0, hasher( seed ) );
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
注意
|
在实际代码中,种子不会是硬编码的常量;理想情况下,每个无序容器实例都应该有自己的随机且不可预测的种子。 |
由于所有符合我们库要求的**哈希算法**都可以使用 `uint64_t` 类型的初始种子构造,因此上述方法适用于其中任何一种。
这对于任何实际目的来说都足够了,但原则上,SipHash64 根据规范接受 16 字节的种子,而我们只(有效地)传递 8 字节。我们可以再次修改我们的 `hash`,这次使用一个接受字节序列作为种子的构造函数
#include <boost/hash2/siphash.hpp>
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/get_integral_result.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <string>
template<class T, class H> class hash
{
private:
H h_;
public:
hash( unsigned char const* p, std::size_t n ): h_( p, n )
{
}
std::size_t operator()( T const& v ) const
{
H h( h_ );
boost::hash2::hash_append( h, {}, v );
return boost::hash2::get_integral_result<std::size_t>( h );
}
};
int main()
{
unsigned char const seed[ 16 ] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
};
using hasher = hash<std::string, boost::hash2::siphash_64>;
boost::unordered_flat_map<std::string, int, hasher> map( 0, hasher( seed, sizeof(seed) ) );
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
如前所述,从字节序列构造是**哈希算法**接口的必需部分,因此上述方法适用于其中任何一种。
为了避免像我们在 `uint64_t` 情况中那样存储初始种子(这需要分配,因为 `n` 可以是任意的,因此需要使用 `std::vector
但一旦我们这样做了,我们可能会注意到我们可以使用支持的三种构造函数中的任何一种构造这个初始实例 `h_`,而不仅仅是接受字节序列的那个
#include <boost/hash2/siphash.hpp>
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/get_integral_result.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <string>
template<class T, class H> class hash
{
private:
H h_;
public:
hash(): h_()
{
}
explicit hash( std::uint64_t seed ): h_( seed )
{
}
hash( unsigned char const* p, std::size_t n ): h_( p, n )
{
}
std::size_t operator()( T const& v ) const
{
H h( h_ );
boost::hash2::hash_append( h, {}, v );
return boost::hash2::get_integral_result<std::size_t>( h );
}
};
int main()
{
using hasher = hash<std::string, boost::hash2::siphash_64>;
{
boost::unordered_flat_map<std::string, int, hasher> map;
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
{
std::uint64_t seed = 0x0102030405060708ull;
boost::unordered_flat_map<std::string, int, hasher> map( 0, hasher( seed ) );
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
{
unsigned char const seed[ 16 ] =
{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
};
boost::unordered_flat_map<std::string, int, hasher> map( 0, hasher(seed, sizeof(seed)) );
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
}
`hash
注意
|
在实际代码中,您可能希望省略默认构造函数,以避免意外使用未播种哈希算法的可能性。 |
我们可以对 `hash` 进行最后一次修改。在上面的示例中,我们无条件地使用了 SipHash 的 64 位变体,即使我们只需要 `std::size_t` 类型的结果,因为这是 `std::hash` 强制要求的。
如果 `std::size_t` 是 32 位,我们使用 `siphash_32`;如果它是 64 位,我们使用 `siphash_64`,这样性能会更好。
为此,我们可以让 `hash` 接受两个哈希算法,一个 32 位,一个 64 位,并让它自动选择合适的那个
#include <boost/hash2/siphash.hpp>
#include <boost/hash2/md5.hpp>
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/get_integral_result.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/core/type_name.hpp>
#include <type_traits>
#include <string>
#include <iostream>
template<class T, class H1, class H2 = H1> class hash
{
public:
using hash_type = typename std::conditional<
sizeof(typename H1::result_type) == sizeof(std::size_t), H1, H2
>::type;
private:
hash_type h_;
public:
hash(): h_()
{
}
explicit hash( std::uint64_t seed ): h_( seed )
{
}
hash( unsigned char const* p, std::size_t n ): h_( p, n )
{
}
std::size_t operator()( T const& v ) const
{
hash_type h( h_ );
boost::hash2::hash_append( h, {}, v );
return boost::hash2::get_integral_result<std::size_t>( h );
}
};
int main()
{
{
using hasher = hash<std::string, boost::hash2::siphash_32, boost::hash2::siphash_64>;
std::cout << boost::core::type_name<hasher>() << " uses "
<< boost::core::type_name<hasher::hash_type>() << std::endl;
boost::unordered_flat_map<std::string, int, hasher> map;
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
{
using hasher = hash<std::string, boost::hash2::md5_128>;
std::cout << boost::core::type_name<hasher>() << " uses "
<< boost::core::type_name<hasher::hash_type>() << std::endl;
boost::unordered_flat_map<std::string, int, hasher> map;
map[ "foo" ] = 1;
map[ "bar" ] = 2;
}
}
为了保持只传递一个哈希算法的情况正常工作,我们将第二个模板参数默认设置为第一个,这样如果只传递一个哈希算法,它将始终被使用。
md5sum
一个命令行实用程序,打印作为参数传递的文件列表的 MD5 摘要。
#include <boost/hash2/md5.hpp>
#include <array>
#include <string>
#include <cerrno>
#include <cstdio>
static void md5sum( std::FILE* f, char const* fn )
{
boost::hash2::md5_128 hash;
int const N = 4096;
unsigned char buffer[ N ];
for( ;; )
{
std::size_t n = std::fread( buffer, 1, N, f );
if( std::ferror( f ) )
{
std::fprintf( stderr, "'%s': read error: %s\n", fn, std::strerror( errno ) );
return;
}
if( n == 0 ) break;
hash.update( buffer, n );
}
std::string digest = to_string( hash.result() );
std::printf( "%s *%s\n", digest.c_str(), fn );
}
int main( int argc, char const* argv[] )
{
for( int i = 1; i < argc; ++i )
{
std::FILE* f = std::fopen( argv[i], "rb" );
if( f == 0 )
{
std::fprintf( stderr, "'%s': open error: %s\n", argv[i], std::strerror( errno ) );
continue;
}
md5sum( f, argv[i] );
std::fclose( f );
}
}
示例命令
md5sum apache_builds.json canada.json citm_catalog.json twitter.json
示例输出
7dc25b5fd9eb2217ed648dad23b311da *apache_builds.json 8767d618bff99552b4946078d3a90c0c *canada.json b4391581160654374bee934a3b91255e *citm_catalog.json bf7d37451840af4e8873b65763315cbf *twitter.json
hash2sum
一个命令行实用程序,使用指定的哈希算法打印文件列表的摘要。
哈希算法作为第一个命令行参数传递。
此示例需要 C++14。
#include <boost/hash2/md5.hpp>
#include <boost/hash2/sha1.hpp>
#include <boost/hash2/sha2.hpp>
#include <boost/hash2/sha3.hpp>
#include <boost/hash2/ripemd.hpp>
#include <boost/hash2/blake2.hpp>
#include <boost/mp11.hpp>
#include <array>
#include <string>
#include <cerrno>
#include <cstdio>
template<class Hash> void hash2sum( std::FILE* f, char const* fn )
{
Hash hash;
int const N = 4096;
unsigned char buffer[ N ];
for( ;; )
{
std::size_t n = std::fread( buffer, 1, N, f );
if( std::ferror( f ) )
{
std::fprintf( stderr, "'%s': read error: %s\n", fn, std::strerror( errno ) );
return;
}
if( n == 0 ) break;
hash.update( buffer, n );
}
std::string digest = to_string( hash.result() );
std::printf( "%s *%s\n", digest.c_str(), fn );
}
template<class Hash> void hash2sum( char const* fn )
{
std::FILE* f = std::fopen( fn, "rb" );
if( f == 0 )
{
std::fprintf( stderr, "'%s': open error: %s\n", fn, std::strerror( errno ) );
}
else
{
hash2sum<Hash>( f, fn );
std::fclose( f );
}
}
using namespace boost::mp11;
using namespace boost::hash2;
using hashes = mp_list<
md5_128,
sha1_160,
sha2_256,
sha2_224,
sha2_512,
sha2_384,
sha2_512_256,
sha2_512_224,
sha3_256,
sha3_224,
sha3_512,
sha3_384,
ripemd_160,
ripemd_128,
blake2b_512,
blake2s_256
>;
constexpr char const* names[] = {
"md5_128",
"sha1_160",
"sha2_256",
"sha2_224",
"sha2_512",
"sha2_384",
"sha2_512_256",
"sha2_512_224",
"sha3_256",
"sha3_224",
"sha3_512",
"sha3_384",
"ripemd_160",
"ripemd_128",
"blake2b_512",
"blake2s_256"
};
int main( int argc, char const* argv[] )
{
if( argc < 2 )
{
std::fputs( "usage: hash2sum <hash> <files...>\n", stderr );
return 2;
}
std::string hash( argv[1] );
bool found = false;
mp_for_each< mp_iota<mp_size<hashes>> >([&](auto I){
if( hash == names[I] )
{
using Hash = mp_at_c<hashes, I>;
for( int i = 2; i < argc; ++i )
{
hash2sum<Hash>( argv[i] );
}
found = true;
}
});
if( !found )
{
std::fprintf( stderr, "hash2sum: unknown hash algorithm name '%s'; use one of the following:\n\n", hash.c_str() );
for( char const* name: names )
{
std::fprintf( stderr, " %s\n", name );
}
return 1;
}
}
示例命令
hash2sum sha2_512_224 apache_builds.json canada.json citm_catalog.json twitter.json
示例输出
a95d7fde785fe24f9507fd1709014567bbc595867f1abaad96f50dbc *apache_builds.json b07e42587d10ec323a25fd8fc3eef2213fb0997beb7950350f4e8a4b *canada.json 4ceee5a83ad320fedb0dfddfb6f80af50b99677e87158e2d039aa168 *citm_catalog.json 854ebe0da98cadd426ea0fa3218d60bb52cf6494e435d2f385a37d48 *twitter.json
编译时哈希
此示例演示了在编译时计算嵌入在程序源代码中的数据数组的 MD5 摘要。它需要 C++14。
#include <boost/hash2/md5.hpp>
#include <iostream>
// xxd -i resource
constexpr unsigned char resource[] = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x6d, 0x90,
0xcf, 0x6e, 0x83, 0x30, 0x0c, 0xc6, 0xcf, 0x45, 0xea, 0x3b, 0x78, 0x9c,
0x4b, 0x02, 0x3d, 0x6e, 0xd0, 0x43, 0xff, 0x1c, 0x26, 0x55, 0x3b, 0x14,
0x6d, 0xd7, 0x2a, 0x04, 0x43, 0x22, 0x95, 0x84, 0x25, 0x66, 0x8c, 0x47,
0xda, 0x5b, 0x2e, 0x91, 0xd6, 0xcb, 0xd4, 0x93, 0x2d, 0xdb, 0xdf, 0xef,
0xb3, 0x5d, 0x2a, 0x1a, 0x6e, 0xbb, 0x75, 0x52, 0x2a, 0x14, 0x6d, 0x8c,
0x03, 0x92, 0x00, 0x45, 0x34, 0x66, 0xf8, 0x39, 0xe9, 0xaf, 0x2a, 0x75,
0xd8, 0x39, 0xf4, 0x2a, 0x05, 0x69, 0x0d, 0xa1, 0xa1, 0x2a, 0xcd, 0x5f,
0xe0, 0xfd, 0x72, 0xae, 0x5a, 0x2b, 0x79, 0x54, 0x73, 0x25, 0xbc, 0xda,
0xb2, 0x98, 0xa6, 0x91, 0xc0, 0xef, 0xa8, 0xc6, 0xb6, 0x4b, 0x88, 0x17,
0x6c, 0xb5, 0x43, 0x49, 0xda, 0xf4, 0x40, 0x16, 0xca, 0x80, 0x0f, 0xcc,
0x2a, 0x7d, 0xa8, 0x7f, 0x50, 0x2c, 0xb9, 0xd8, 0x31, 0xc6, 0x22, 0xf9,
0x8f, 0x58, 0xf2, 0xfb, 0xd6, 0x4f, 0x59, 0xb6, 0x4e, 0x56, 0x3f, 0x70,
0xb0, 0xe3, 0xe2, 0x74, 0xaf, 0x08, 0xf6, 0x38, 0x08, 0x03, 0x47, 0x31,
0xa3, 0xdf, 0xc0, 0x36, 0xcf, 0x8b, 0xd0, 0x3f, 0x6a, 0x4f, 0x4e, 0x37,
0x13, 0x61, 0x0b, 0x93, 0x69, 0xd1, 0x01, 0x29, 0x84, 0xbd, 0xb5, 0x9e,
0xa0, 0xb6, 0x1d, 0xcd, 0xc2, 0x21, 0x9c, 0xb5, 0x44, 0xe3, 0x71, 0x03,
0x1f, 0xe8, 0xbc, 0xb6, 0x06, 0x0a, 0x96, 0x07, 0xd3, 0x55, 0x8d, 0x08,
0x42, 0x4a, 0x3b, 0x8c, 0xc2, 0x2c, 0xf1, 0x86, 0x4e, 0xdf, 0xc2, 0xf4,
0xeb, 0xe1, 0xf4, 0x56, 0x9f, 0xae, 0xc5, 0x35, 0x67, 0xf4, 0x4d, 0x60,
0x5d, 0xf8, 0xcf, 0xb8, 0x80, 0xa0, 0x20, 0x89, 0xef, 0x7b, 0xe6, 0x7c,
0x9e, 0x67, 0xd6, 0x44, 0x13, 0x66, 0x5d, 0xcf, 0xff, 0x29, 0xd6, 0x49,
0x96, 0x85, 0x0b, 0x7e, 0x01, 0x36, 0x66, 0x95, 0x6b, 0x80, 0x01, 0x00,
0x00
};
template<std::size_t N> constexpr auto md5( unsigned char const(&a)[ N ] )
{
boost::hash2::md5_128 hash;
hash.update( a, N );
return hash.result();
}
constexpr auto resource_digest = md5( resource );
int main()
{
std::cout << "Resource digest: " << resource_digest << std::endl;
}
由于 `update` 的 `constexpr` 重载接受 `unsigned char const*`(`void const*` 在 `constexpr` 函数中不允许),如果要哈希的数据是 `char const[]` 类型的字符数组,直接将其传递给 `update` 将无法编译。在这种情况下,我们可以使用 `hash_append_range` 而不是调用 `update`,如下例所示。
#include <boost/hash2/sha2.hpp>
#include <boost/hash2/hash_append.hpp>
#include <iostream>
extern constexpr char const license[] =
"Boost Software License - Version 1.0 - August 17th, 2003\n"
"\n"
"Permission is hereby granted, free of charge, to any person or organization\n"
"obtaining a copy of the software and accompanying documentation covered by\n"
"this license (the \"Software\") to use, reproduce, display, distribute,\n"
"execute, and transmit the Software, and to prepare derivative works of the\n"
"Software, and to permit third-parties to whom the Software is furnished to\n"
"do so, all subject to the following:\n"
"\n"
"The copyright notices in the Software and this entire statement, including\n"
"the above license grant, this restriction and the following disclaimer,\n"
"must be included in all copies of the Software, in whole or in part, and\n"
"all derivative works of the Software, unless such copies or derivative\n"
"works are solely in the form of machine-executable object code generated by\n"
"a source language processor.\n"
"\n"
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
"FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\n"
"SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\n"
"FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\n"
"ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n"
"DEALINGS IN THE SOFTWARE.\n"
;
constexpr unsigned char secret[] = {
0xA4, 0x80, 0x0E, 0xE8, 0x20, 0x0B, 0x7C, 0x9A,
0xF1, 0x3E, 0x3D, 0xEC, 0x64, 0x4F, 0x64, 0xCA,
0x33, 0xCC, 0x84, 0xC8, 0x34, 0xE3, 0x08, 0xAE,
0x92, 0x89, 0xEB, 0xD0, 0x47, 0x39, 0x87, 0xD8,
};
template<std::size_t N> constexpr auto hmac_sha2_256( char const(&s)[ N ] )
{
boost::hash2::hmac_sha2_256 hmac( secret, sizeof(secret) );
// N-1, in order to not include the null terminator
boost::hash2::hash_append_range( hmac, {}, s, s + N - 1 );
return hmac.result();
}
constexpr auto license_mac = hmac_sha2_256( license );
int main()
{
std::cout << "License authentication code: " << license_mac << std::endl;
}
结果扩展
我们的一些哈希算法,例如 `xxhash_64` 和 `siphash_64`,内部状态超过 64 位,但只产生 64 位结果。
如果我们使用这些算法之一来生成文件或内容校验和,不容忍冲突,并且操作大量文件或项目(数百万),那么使用 128 位摘要可能会更好。
由于算法保持超过 64 位的状态,我们可以调用 `result()` 两次并获得有意义的 128 位结果。
以下示例演示了如何实现。它定义了一个算法 `xxhash_128`,该算法通过包装 `xxhash_64` 并适当地重新定义其 `result_type` 和 `result` 成员来实现
#include <boost/hash2/xxhash.hpp>
#include <boost/hash2/digest.hpp>
#include <boost/endian/conversion.hpp>
class xxhash_128: private boost::hash2::xxhash_64
{
public:
using result_type = boost::hash2::digest<16>;
using xxhash_64::xxhash_64;
using xxhash_64::update;
result_type result()
{
std::uint64_t r1 = xxhash_64::result();
std::uint64_t r2 = xxhash_64::result();
result_type r = {};
boost::endian::store_little_u64( r.data() + 0, r1 );
boost::endian::store_little_u64( r.data() + 8, r2 );
return r;
}
};
#include <string>
#include <iostream>
int main()
{
std::string tv( "The quick brown fox jumps over the lazy dog" );
xxhash_128 hash( 43 );
hash.update( tv.data(), tv.size() );
std::cout << hash.result() << std::endl;
}
实现特性
支持的编译器
该库需要 C++11。以下编译器
-
g++ 4.8 或更高版本
-
clang++ 3.9 或更高版本
-
Visual Studio 2015 及更高版本
正在 Github Actions 和 Appveyor 上进行测试。
参考
哈希算法
<boost/hash2/fnv1a.hpp>
namespace boost {
namespace hash2 {
class fnv1a_32;
class fnv1a_64;
} // namespace hash2
} // namespace boost
此头文件实现了 FNV-1a 算法,包括 32 位和 64 位变体。
fnv1a_32
class fnv1a_32
{
private:
std::uint32_t state_; // exposition only
public:
using result_type = std::uint32_t;
constexpr fnv1a_32();
explicit constexpr fnv1a_32( std::uint64_t seed );
fnv1a_32( void const* p, std::size_t n );
constexpr fnv1a_32( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr fnv1a_32();
默认构造函数。
- 效果
-
将 `state_` 初始化为 `0x811c9dc5`。
explicit constexpr fnv1a_32( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `seed` 不为零,则执行 `update(p, 8)`,其中 `p` 指向 `seed` 值的 Little-endian 表示。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
fnv1a_32( void const* p, std::size_t n );
constexpr fnv1a_32( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n)`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
fnv1a_64
class fnv1a_64
{
private:
std::uint64_t state_; // exposition only
public:
using result_type = std::uint64_t;
constexpr fnv1a_64();
explicit constexpr fnv1a_64( std::uint64_t seed );
fnv1a_64( void const* p, std::size_t n );
constexpr fnv1a_64( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr fnv1a_64();
默认构造函数。
- 效果
-
将 `state_` 初始化为 `0xcbf29ce484222325`。
explicit constexpr fnv1a_64( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `seed` 不为零,则执行 `update(p, 8)`,其中 `p` 指向 `seed` 值的 Little-endian 表示。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
fnv1a_64( void const* p, std::size_t n );
constexpr fnv1a_64( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n)`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
<boost/hash2/xxhash.hpp>
namespace boost {
namespace hash2 {
class xxhash_32;
class xxhash_64;
} // namespace hash2
} // namespace boost
此头文件实现了 XXH32 和 XXH64 算法。
xxhash_32
class xxhash_32
{
public:
using result_type = std::uint32_t;
constexpr xxhash_32();
explicit constexpr xxhash_32( std::uint64_t seed );
xxhash_32( void const* p, std::size_t n );
constexpr xxhash_32( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr xxhash_32();
默认构造函数。
- 效果
-
将 XXH32 算法的内部状态初始化为其初始值。
explicit constexpr xxhash_32( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用 `seed` 的低 32 位作为种子初始化 XXH32 算法的内部状态,然后如果 `seed` 的高 32 位不为零,则将其混合到状态中。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
xxhash_32( void const* p, std::size_t n );
constexpr xxhash_32( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
xxhash_64
class xxhash_64
{
public:
using result_type = std::uint64_t;
constexpr xxhash_64();
explicit constexpr xxhash_64( std::uint64_t seed );
xxhash_64( void const* p, std::size_t n );
constexpr xxhash_64( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr xxhash_64();
默认构造函数。
- 效果
-
将 XXH64 算法的内部状态初始化为其初始值。
explicit constexpr xxhash_64( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用 `seed` 作为种子初始化 XXH64 算法的内部状态。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
xxhash_64( void const* p, std::size_t n );
constexpr xxhash_64( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
<boost/hash2/xxh3.hpp>
#include <boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
class xxh3_128;
} // namespace hash2
} // namespace boost
此头文件实现了 XXH3-128 算法,定义于 此处。
xxh3_128
class xxh3_128
{
public:
using result_type = digest<16>;
constexpr xxh3_128();
explicit constexpr xxh3_128( std::uint64_t seed );
xxh3_128( void const* p, std::size_t n );
constexpr xxh3_128( unsigned char const* p, std::size_t n );
// XXH3-specific named constructors, matching the reference implementation
static constexpr xxh3_128 with_seed( std::uint64_t seed );
static xxh3_128 with_secret( void const* p, std::size_t n );
static constexpr xxh3_128 with_secret( unsigned char const* p, std::size_t n );
static xxh3_128 with_secret_and_seed( void const* p, std::size_t n, std::uint64_t seed );
static constexpr xxh3_128 with_secret_and_seed( unsigned char const* p, std::size_t n, std::uint64_t seed );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr xxh3_128();
默认构造函数。
- 效果
-
将 XXH3-128 算法的内部状态初始化为其初始值。
explicit constexpr xxh3_128( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用给定的 `seed` 初始化 XXH3 算法的内部状态。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
xxh3_128( void const* p, std::size_t n );
constexpr xxh3_128( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则从输入中导出秘密和种子,并由算法内部使用。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
命名构造函数
static constexpr xxh3_128 with_seed( std::uint64_t seed );
种子构造函数的别名,用于与规范兼容。
- 效果
-
使用给定的 `seed` 初始化 XXH3 算法的内部状态。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
static xxh3_128 with_secret( void const* p, std::size_t n );
static constexpr xxh3_128 with_secret( unsigned char const* p, std::size_t n );
- 效果
-
使用提供的密钥初始化算法状态,以最大程度地遵守 XXH3 规范。
如果 `n` 小于最小默认秘密长度 136 字节,则使用默认秘密。如果 `n` 大于默认密钥的大小(192 字节),则通过将提供的字节序列累加到 192 字节块中来合成秘密。否则,按照 XXH3 规范 中指定的方式使用提供的秘密。
static xxh3_128 with_secret_and_seed( void const* p, std::size_t n, std::uint64_t seed );
static constexpr xxh3_128 with_secret_and_seed( unsigned char const* p, std::size_t n, std::uint64_t seed );
- 效果
-
使用提供的种子和秘密初始化算法。
由于算法的定义方式,这相当于对所有输入 ≤ 240 字节使用 `with_seed`,对所有大于 240 字节的输入使用 `with_secret`。
<boost/hash2/siphash.hpp>
namespace boost {
namespace hash2 {
class siphash_32;
class siphash_64;
} // namespace hash2
} // namespace boost
此头文件实现了 SipHash 和 HalfSipHash 算法。
siphash_32
class siphash_32
{
public:
using result_type = std::uint32_t;
constexpr siphash_32();
explicit constexpr siphash_32( std::uint64_t seed );
siphash_32( void const* p, std::size_t n );
constexpr siphash_32( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr siphash_32();
默认构造函数。
- 效果
-
将 HalfSipHash 算法的内部状态初始化为使用 8 个零字节序列作为密钥。
explicit constexpr siphash_32( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用 `seed` 作为 HalfSipHash 算法的密钥,就好像它是一个 8 个组成字节的序列,按小端序排列。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
siphash_32( void const* p, std::size_t n );
constexpr siphash_32( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
如果 `n` 为 8,则按照算法指定初始化状态;否则,将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
siphash_64
class siphash_64
{
public:
using result_type = std::uint64_t;
constexpr siphash_64();
explicit constexpr siphash_64( std::uint64_t seed );
siphash_64( void const* p, std::size_t n );
constexpr siphash_64( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr siphash_64();
默认构造函数。
- 效果
-
将 SipHash 算法的内部状态初始化为使用 16 个零字节序列作为密钥。
explicit constexpr siphash_64( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用 `seed` 作为 SipHash 算法的密钥,就好像它是一个 8 个组成字节的序列,按小端序排列,后跟 8 个零字节。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
siphash_64( void const* p, std::size_t n );
constexpr siphash_64( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
如果 `n` 为 16,则按照算法指定初始化状态;否则,将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
<boost/hash2/hmac.hpp>
namespace boost {
namespace hash2 {
template<class H> class hmac;
} // namespace hash2
} // namespace boost
此头文件实现了 HMAC 算法。
hmac
template<class H> class hmac
{
public:
using result_type = typename H::result_type;
static constexpr std::size_t block_size = H::block_size;
constexpr hmac();
explicit constexpr hmac( std::uint64_t seed );
hmac( void const* p, std::size_t n );
constexpr hmac( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
类模板 `hmac` 接受加密**哈希算法** `H` 作为参数,并实现相应的**基于哈希的消息认证码** (HMAC) 算法。
例如,HMAC-SHA2-256 由 `hmac
构造函数
constexpr hmac();
默认构造函数。
- 效果
-
使用空字节序列作为秘密密钥初始化内部状态。
explicit constexpr hmac( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
如果 `seed` 为零,则将状态初始化为默认构造,否则,使用 `seed` 的小端表示的 8 个字节作为秘密密钥初始化状态。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
hmac( void const* p, std::size_t n );
hmac( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
使用 `[p, p+n)` 作为秘密密钥,按照 HMAC 算法指定初始化状态。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
<boost/hash2/md5.hpp>
#include <boost/hash2/hmac.hpp>
#include <boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
class md5_128;
using hmac_md5_128 = hmac<md5_128>;
} // namespace hash2
} // namespace boost
此头文件实现了 MD5 算法。
md5_128
class md5_128
{
public:
using result_type = digest<16>;
static constexpr std::size_t block_size = 64;
constexpr md5_128();
explicit constexpr md5_128( std::uint64_t seed );
md5_128( void const* p, std::size_t n );
constexpr md5_128( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr md5_128();
默认构造函数。
- 效果
-
将 MD5 算法的内部状态初始化为其初始值。
explicit constexpr md5_128( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `seed` 不为零,则执行 `update(p, 8); result();`,其中 `p` 指向 `seed` 值的 Little-endian 表示。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
md5_128( void const* p, std::size_t n );
constexpr md5_128( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
<boost/hash2/sha1.hpp>
#include <boost/hash2/hmac.hpp>
#include <boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
class sha1_160;
using hmac_sha1_160 = hmac<sha1_160>;
} // namespace hash2
} // namespace boost
此头文件实现了 SHA-1 算法。
sha1_160
class sha1_160
{
public:
using result_type = digest<20>;
static constexpr std::size_t block_size = 64;
constexpr sha1_160();
explicit constexpr sha1_160( std::uint64_t seed );
sha1_160( void const* p, std::size_t n );
constexpr sha1_160( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr sha1_160();
默认构造函数。
- 效果
-
将 SHA-1 算法的内部状态初始化为其初始值。
explicit constexpr sha1_160( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `seed` 不为零,则执行 `update(p, 8); result();`,其中 `p` 指向 `seed` 值的 Little-endian 表示。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
sha1_160( void const* p, std::size_t n );
constexpr sha1_160( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
<boost/hash2/sha2.hpp>
#include <boost/hash2/hmac.hpp>
#include <boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
class sha2_256;
class sha2_224;
class sha2_512;
class sha2_384;
class sha2_512_256;
class sha2_512_224;
using hmac_sha2_256 = hmac<sha2_256>;
using hmac_sha2_224 = hmac<sha2_224>;
using hmac_sha2_512 = hmac<sha2_512>;
using hmac_sha2_384 = hmac<sha2_384>;
using hmac_sha2_512_256 = hmac<sha2_512_256>;
using hmac_sha2_512_224 = hmac<sha2_512_224>;
} // namespace hash2
} // namespace boost
此头文件实现了 SHA-2 系列函数。
sha2_256
class sha2_256
{
using result_type = digest<32>;
static constexpr std::size_t block_size = 64;
constexpr sha2_256();
constexpr explicit sha2_256( std::uint64_t seed );
sha2_256( void const* p, std::size_t n );
constexpr sha2_256( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr sha2_256();
默认构造函数。
- 效果
-
将 SHA-256 算法的内部状态初始化为其初始值。
constexpr explicit sha2_256( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `seed` 不为零,则执行 `update(p, 8); result();`,其中 `p` 指向 `seed` 值的 Little-endian 表示。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
sha2_256( void const* p, std::size_t n );
constexpr sha2_256( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
sha2_224
SHA-224 算法与上述 SHA-256 算法相同。
唯一的区别是内部状态的初始值和消息摘要的大小,它是
using result_type = digest<28>;
否则,所有其他操作和常量都是相同的。
消息摘要通过将 SHA-256 算法的最终结果截断为其最左边的 224 位来获得。
sha2_512
class sha2_512
{
using result_type = digest<64>;
static constexpr std::size_t block_size = 128;
constexpr sha2_512();
constexpr explicit sha2_512( std::uint64_t seed );
sha2_512( void const* p, std::size_t n );
constexpr sha2_512( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr sha2_512();
默认构造函数。
- 效果
-
将 SHA-512 算法的内部状态初始化为其初始值。
constexpr explicit sha2_512( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `seed` 不为零,则执行 `update(p, 8); result();`,其中 `p` 指向 `seed` 值的 Little-endian 表示。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
sha2_512( void const* p, std::size_t n );
constexpr sha2_512( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
sha2_384
SHA-384 算法与上述 SHA-512 算法相同。
唯一的区别是内部状态的初始值和消息摘要的大小,它是
using result_type = digest<48>;
否则,所有其他操作和常量都是相同的。
消息摘要通过将 SHA-512 算法的最终结果截断为其最左边的 384 位来获得。
<boost/hash2/sha3.hpp>
#include <boost/hash2/digest.hpp>
#include <boost/hash2/hmac.hpp>
namespace boost {
namespace hash2 {
class sha3_256;
class sha3_224;
class sha3_512;
class sha3_384;
class shake_128;
class shake_256;
using hmac_sha3_256 = hmac<sha3_256>;
using hmac_sha3_224 = hmac<sha3_224>;
using hmac_sha3_512 = hmac<sha3_512>;
using hmac_sha3_384 = hmac<sha3_384>;
} // namespace hash2
} // namespace boost
此头文件实现了 SHA-3 系列函数。
固定摘要哈希函数(`sha3_256`、`sha3_224`、`sha3_512`、`sha3_384`)内部都使用相同的常量,并且以相同的方式初始化。它们之间的唯一区别是消息摘要的大小和输入块的大小。否则,摘要都以相同的方式收集,即通过截断 1600 位状态的底部部分。
`shake_128` 和 `shake_256` 函数类似于固定摘要函数,但它们使用不同的分隔符来表示摘要的最终化,并且扩展输出具有定义的行为,这与库中其他地方使用的默认伪随机行为不同。但它们的工作方式类似,即 1600 位状态的截断作为摘要返回。
sha3_256
class sha3_256
{
using result_type = digest<32>;
static constexpr std::size_t block_size = 136; // ( 1600 - 2 * 256 ) / 8 => 136
constexpr sha3_256();
constexpr explicit sha3_256( std::uint64_t seed );
sha3_256( void const* p, std::size_t n );
constexpr sha3_256( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr sha3_256();
默认构造函数。
- 效果
-
将 SHA3-256 算法的内部状态初始化为其初始值。
constexpr explicit sha3_256( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `seed` 不为零,则执行 `update(p, 8); result();`,其中 `p` 指向 `seed` 值的 Little-endian 表示。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
sha3_256( void const* p, std::size_t n );
constexpr sha3_256( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
sha3_224
SHA3-224 算法与上述 SHA3-256 算法相同。
唯一的区别是输入块的大小和消息摘要的大小,它是
using result_type = digest<28>;
static constexpr std::size_t block_size = 144; // ( 1600 - 2 * 224 ) / 8 => 144
否则,所有其他操作和常量都是相同的。
消息摘要通过将 Keccak-1600 算法的最终结果截断为其最左边的 224 位来获得。
sha3_512
SHA3-512 算法与上述 SHA3-256 算法相同。
唯一的区别是输入块的大小和消息摘要的大小,它是
using result_type = digest<64>;
static constexpr std::size_t block_size = 72; // ( 1600 - 2 * 512 ) / 8 => 72
否则,所有其他操作和常量都是相同的。
消息摘要通过将 Keccak-1600 算法的最终结果截断为其最左边的 512 位来获得。
sha3_384
SHA3-384 算法与上述 SHA3-256 算法相同。
唯一的区别是输入块的大小和消息摘要的大小,它是
using result_type = digest<48>;
static constexpr std::size_t block_size = 104; // ( 1600 - 2 * 384 ) / 8 => 104
否则,所有其他操作和常量都是相同的。
消息摘要通过将 Keccak-1600 算法的最终结果截断为其最左边的 384 位来获得。
shake_128
class shake_128
{
using result_type = digest<168>;
static constexpr std::size_t block_size = 168; // ( 1600 - 2 * 128 ) / 8 => 168
constexpr shake_128();
constexpr explicit shake_128( std::uint64_t seed );
shake_128( void const* p, std::size_t n );
constexpr shake_128( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr shake_128();
默认构造函数。
- 效果
-
将 SHAKE128 算法的内部状态初始化为其初始值。
constexpr explicit shake_128( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `seed` 不为零,则执行 `update(p, 8); result();`,其中 `p` 指向 `seed` 值的 Little-endian 表示。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
shake_128( void const* p, std::size_t n );
constexpr shake_128( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
更新
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
- 效果
-
从字节序列 `[p, p+n)` 更新 SHAKE128 算法的内部状态。
- 备注
-
连续调用 `update` 等同于一次调用,其字节序列是各个调用的连接。
结果
constexpr result_type result();
- 效果
-
填充累积的消息并最终确定摘要。
- 返回
-
1600 位状态的截断部分,直至指定的速率 `r`,即 `block_size`,如 FIPS 202 中算法 8 的步骤 8 所指定。
- 备注
-
重复调用 `result()` 以定义的方式扩展哈希函数的输出。调用 `update()` 将重置当前的最终化过程。
<boost/hash2/ripemd.hpp>
#include <boost/hash2/hmac.hpp>
#include <boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
class ripemd_160;
class ripemd_128;
using hmac_ripemd_160 = hmac<ripemd_160>;
using hmac_ripemd_128 = hmac<ripemd_128>;
} // namespace hash2
} // namespace boost
此头文件实现了 RIPEMD-160 和 RIPEMD-128 算法。
ripemd_160
class ripemd_160
{
using result_type = digest<20>;
static constexpr std::size_t block_size = 64;
constexpr ripemd_160();
explicit constexpr ripemd_160( std::uint64_t seed );
ripemd_160( void const* p, std::size_t n );
constexpr ripemd_160( unsigned char const* p, std::size_t n );
void update( void const * pv, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr ripemd_160();
默认构造函数。
- 效果
-
将 RIPEMD-160 算法的内部状态初始化为其初始值。
explicit constexpr ripemd_160( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `seed` 不为零,则执行 `update(p, 8); result();`,其中 `p` 指向 `seed` 值的 Little-endian 表示。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
ripemd_160( void const* p, std::size_t n );
constexpr ripemd_160( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
将状态初始化为默认构造,然后如果 `n` 不为零,则执行 `update(p, n); result()`。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
<boost/hash2/blake2.hpp>
#include <boost/hash2/digest.hpp>
#include <boost/hash2/hmac.hpp>
namespace boost {
namespace hash2 {
class blake2b_512;
class blake2s_256;
using hmac_blake2b_512 = hmac<blake2b_512>;
using hmac_blake2s_256 = hmac<blake2s_256>;
} // namespace hash2
} // namespace boost
此头文件实现了 BLAKE2 系列函数。
`blake2b_512` 是一种 512 位摘要函数,针对 64 位平台进行了优化,而 `blake2s_256` 是一种 256 位摘要函数,针对 8 到 32 位平台进行了优化。
BLAKE2 直接支持带密钥哈希,因此无需使用 hmac 类模板用于 MAC 目的(尽管为方便起见仍提供 typedefs)。与 `hmac
blake2b_512
class blake2b_512
{
using result_type = digest<64>;
static constexpr std::size_t block_size = 128;
constexpr blake2b_512();
constexpr explicit blake2b_512( std::uint64_t seed );
blake2b_512( void const* p, std::size_t n );
constexpr blake2b_512( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr blake2b_512();
默认构造函数。
- 效果
-
将 BLAKE2b 算法的内部状态初始化为其初始值。
constexpr explicit blake2b_512( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用 `seed` 的小端表示作为 8 字节秘密密钥初始化状态。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
blake2b_512( void const* p, std::size_t n );
constexpr blake2b_512( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
使用输入的前 64 字节(`k`)作为秘密密钥初始化状态。如果还有剩余输入,则通过调用 `update( p + k, n - k ); result();` 进行处理。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
blake2s_256
class blake2s_256
{
using result_type = digest<32>;
static constexpr std::size_t block_size = 64;
constexpr blake2s_256();
constexpr explicit blake2s_256( std::uint64_t seed );
blake2s_256( void const* p, std::size_t n );
constexpr blake2s_256( unsigned char const* p, std::size_t n );
void update( void const* p, std::size_t n );
constexpr void update( unsigned char const* p, std::size_t n );
constexpr result_type result();
};
构造函数
constexpr blake2s_256();
默认构造函数。
- 效果
-
将 BLAKE2s 算法的内部状态初始化为其初始值。
constexpr explicit blake2s_256( std::uint64_t seed );
接受整数种子值的构造函数。
- 效果
-
使用 `seed` 的小端表示作为 8 字节秘密密钥初始化状态。
- 备注
-
按照惯例,如果 `seed` 为零,则此构造函数的效果与默认构造相同。
blake2s_256( void const* p, std::size_t n );
constexpr blake2s_256( unsigned char const* p, std::size_t n );
接受字节序列种子的构造函数。
- 效果
-
使用输入的前 32 字节(`k`)作为秘密密钥初始化状态。如果还有剩余输入,则通过调用 `update( p + k, n - k ); result();` 进行处理。
- 备注
-
按照惯例,如果 `n` 为零,则此构造函数的效果与默认构造相同。
实用工具和特性
<boost/hash2/digest.hpp>
namespace boost {
namespace hash2 {
template<std::size_t N> class digest
{
private: // exposition only
unsigned char data_[ N ] = {};
public:
using value_type = unsigned char;
using reference = unsigned char&;
using const_reference = unsigned char const&;
using iterator = unsigned char*;
using const_iterator = unsigned char const*;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
// constructors
constexpr digest() = default;
constexpr digest( unsigned char const (&v)[ N ] ) noexcept;
// copy
constexpr digest( digest const& ) = default;
constexpr digest& operator=( digest const& ) = default;
// iteration
constexpr iterator begin() noexcept;
constexpr const_iterator begin() const noexcept;
constexpr iterator end() noexcept;
constexpr const_iterator end() const noexcept;
// data, size
constexpr unsigned char* data() noexcept;
constexpr unsigned char const* data() const noexcept;
constexpr size_type size() const noexcept;
constexpr size_type max_size() const noexcept;
// element access
constexpr reference operator[]( std::size_t i );
constexpr const_reference operator[]( std::size_t i ) const;
constexpr reference front() noexcept;
constexpr const_reference front() const noexcept;
constexpr reference back() noexcept;
constexpr const_reference back() const noexcept;
};
// comparisons
template<std::size_t N>
constexpr bool operator==( digest<N> const& a, digest<N> const& b ) noexcept;
template<std::size_t N>
constexpr bool operator!=( digest<N> const& a, digest<N> const& b ) noexcept;
// to_chars
template<std::size_t N>
constexpr char* to_chars( digest<N> const& v, char* first, char* last ) noexcept;
template<std::size_t N, std::size_t M>
constexpr void to_chars( digest<N> const& v, char (&w)[ M ] ) noexcept;
// operator<<
template<std::size_t N>
std::ostream& operator<<( std::ostream& os, digest<N> const& v );
// to_string
template<std::size_t N>
std::string to_string( digest<N> const& v );
} // namespace hash2
} // namespace boost
摘要
`digest
构造函数
constexpr digest() = default;
- 效果
-
将 `data_` 零初始化。
constexpr digest( unsigned char const (&v)[ N ] ) noexcept;
- 效果
-
从 `v` 的相应元素初始化 `data_` 的元素。
迭代
constexpr iterator begin() noexcept;
constexpr const_iterator begin() const noexcept;
- 返回
-
data_
.
constexpr iterator end() noexcept;
constexpr const_iterator end() const noexcept;
- 返回
-
data_ + N
.
访问器
constexpr unsigned char* data() noexcept;
constexpr unsigned char const* data() const noexcept;
- 返回
-
data_
.
constexpr size_type size() const noexcept;
constexpr size_type max_size() const noexcept;
- 返回
-
N
.
元素访问
constexpr reference operator[]( std::size_t i );
constexpr const_reference operator[]( std::size_t i ) const;
- 要求
-
i < size()
. - 返回
-
data_[ i ]
.
constexpr reference front() noexcept;
constexpr const_reference front() const noexcept;
- 返回
-
data_[ 0 ]
.
constexpr reference back() noexcept;
constexpr const_reference back() const noexcept;
- 返回
-
data_[ N-1 ]
.
比较
template<std::size_t N>
constexpr bool operator==( digest<N> const& a, digest<N> const& b ) noexcept;
- 返回
-
当 `a.data_` 的元素等于 `b.data_` 的相应元素时为 `true`,否则为 `false`。
template<std::size_t N>
constexpr bool operator!=( digest<N> const& a, digest<N> const& b ) noexcept;
- 返回
-
!(a == b)
.
格式化
template<std::size_t N>
constexpr char* to_chars( digest<N> const& v, char* first, char* last ) noexcept;
- 效果
-
将 `data_` 的内容作为十六进制字符串写入提供的输出范围 `[first, last)`。
- 返回
-
指向生成输出末尾之后一个位置的指针,如果 `[first, last)` 不够大则为 `nullptr`。
template<std::size_t N, std::size_t M>
constexpr void to_chars( digest<N> const& v, char (&w)[ M ] ) noexcept;
- 要求
-
M >= N*2 + 1
. - 效果
-
将 `data_` 的内容作为十六进制字符串写入提供的输出缓冲区 `w`,然后写入空终止符。
template<std::size_t N>
std::ostream& operator<<( std::ostream& os, digest<N> const& v );
- 效果
-
将 `data_` 的内容作为十六进制字符串写入 `os`。
- 返回
-
os
.
template<std::size_t N>
std::string to_string( digest<N> const& v );
- 返回
-
包含 `data_` 内容的十六进制格式字符串。
<boost/hash2/endian.hpp>
namespace boost {
namespace hash2 {
enum class endian;
} // namespace hash2
} // namespace boost
endian
enum class endian
{
little = /*...*/,
big = /*...*/,
native = /*little or big*/
};
枚举类型 `endian` 对应于 C++20 中的标准 `std::endian` 类型。它的值包括 `little`,表示小端字节序;`big`,表示大端字节序;以及 `native`,它等于 `little` 或 `big`,具体取决于当前平台是小端还是大端。
与 `std::endian` 不同,不支持 `little` 等于 `big` 或 `native` 既不等于 `little` 也不等于 `big` 的平台。
<boost/hash2/flavor.hpp>
#include <boost/hash2/endian.hpp>
namespace boost {
namespace hash2 {
struct default_flavor;
struct little_endian_flavor;
struct big_endian_flavor;
} // namespace hash2
} // namespace boost
头文件 boost/hash2/flavor.hpp
包含预定义的 flavor 类型。
(flavor 作为第二个参数传递给 hash_append
,以影响其行为。)
flavor 类型有两个成员:一个类型 size_type
和一个类型为 boost::hash2::endian
的值 byte_order
。
size_type
控制 hash_append_size
的参数如何处理(在哈希之前将其转换为 size_type
)。
byte_order
控制用于哈希标量类型的字节序。
default_flavor
struct default_flavor
{
using size_type = std::uint32_t;
static constexpr auto byte_order = endian::native;
};
default_flavor
请求标量类型的原生、依赖于字节序的哈希。这使得哈希值依赖于当前平台的字节序,但如果将大量的标量类型数组传递给 hash_append
,则可能显著加快速度。
很少需要显式使用 default_flavor
,因为它在没有向 hash_append
提供 flavor 时是默认值,例如:hash_append( h, {}, v );
<boost/hash2/get_integral_result.hpp>
namespace boost {
namespace hash2 {
template<class T, class Hash> constexpr T get_integral_result( Hash& h );
} // namespace hash2
} // namespace boost
get_integral_result
template<class T, class Hash> constexpr T get_integral_result( Hash& h );
- 要求
-
T
必须是一个非bool
的整型。Hash
必须是一个 哈希算法。 - 效果
-
调用
h.result()
一次或多次。 - 返回
-
一个值,它以大致均匀分布在
T
可能值上的方式从h.result()
调用的返回值中派生。 - 备注
-
当
Hash::result_type
是一个类数组类型时,get_integral_result
允许假定Hash
是一个高质量的哈希算法,因此h.result()
的值在Hash::result_type
的整个域中均匀分布。 - 示例
-
template<class T, class Hash> struct my_hash { std::size_t operator()( std::string const& st ) const noexcept { Hash hash; boost::hash2::hash_append( hash, {}, st ); return boost::hash2::get_integral_result<std::size_t>( hash ); } };
<boost/hash2/is_trivially_equality_comparable.hpp>
namespace boost {
namespace hash2 {
template<class T> struct is_trivially_equality_comparable;
} // namespace hash2
} // namespace boost
is_trivially_equality_comparable
template<class T> struct is_trivially_equality_comparable:
std::integral_constant< bool,
std::is_integral<T>::value || std::is_enum<T>::value || std::is_pointer<T>::value >
{
};
template<class T> struct is_trivially_equality_comparable<T const>:
is_trivially_equality_comparable<T>
{
};
库使用 is_trivially_equality_comparable
特性来检测 可平凡相等比较 的类型。
如果对于该类型的两个值 x
和 y
,x == y
等价于 std::memcmp( &x, &y, sizeof(x) ) == 0
,则该类型是 可平凡相等比较 的。
也就是说,对于可平凡相等比较的类型,比较它们的值与比较它们的存储字节表示是相同的。
这允许 hash_append
假定标识对象值的 消息 与对象的存储字节相同。
如果默认实现没有给出正确的结果,is_trivially_equality_comparable
可以为用户定义类型进行特化。
例如,对于以下类型
struct X
{
int v;
};
bool operator==( X const& x1, X const& x2 )
{
return x1.v == x2.v;
}
(假设它没有填充字节,即 sizeof(X) == sizeof(int)
) is_trivially_equality_comparable<X>::value
默认将为 false
,但该类型符合可平凡相等比较的要求,因此可以添加特化
template<> struct boost::hash2::is_trivially_equality_comparable<X>: std::true_type {};
或者,如果你想保险一点,
template<> struct boost::hash2::is_trivially_equality_comparable<X>:
std::integral_constant<bool, sizeof(X) == sizeof(int)> {};
另一方面,以下类型
enum E: unsigned {};
bool operator==( E e1, E e2 )
{
return e1 % 256 == e2 % 256;
}
不是可平凡相等比较的(因为 (E)0x204 == (E)0x704
,但 memcmp
将给出非零结果),但 is_trivially_equality_comparable<E>::value
默认将为 true
。
在这种(相当罕见的)情况下,可以添加特化以报告 false
template<> struct boost::hash2::is_trivially_equality_comparable<E>: std::false_type {};
<boost/hash2/is_endian_independent.hpp>
namespace boost {
namespace hash2 {
template<class T> struct is_endian_independent;
} // namespace hash2
} // namespace boost
is_endian_independent
template<class T> struct is_endian_independent:
std::integral_constant< bool, sizeof(T) == 1 >
{
};
template<class T> struct is_endian_independent<T const>:
is_endian_independent<T>
{
};
库使用 is_endian_independent
特性来检测 字节序无关 类型。
如果一个类型的内存表示在小端和大端平台上相同,则该类型是 字节序无关 的。
这包括所有单字节类型(sizeof
为 1)和所有其组成成员也是字节序无关的类型。
该特性的默认实现仅对单字节类型报告 true
。它可以为字节序无关的用户定义类型进行特化。
例如,以下类型
struct X
{
unsigned char a;
unsigned char b;
unsigned char c;
};
是字节序无关的,并且可以为其适当特化 is_endian_independent
template<> struct boost::hash2::is_endian_independent<X>: std::true_type {};
<boost/hash2/is_contiguously_hashable.hpp>
#include <boost/hash2/endian.hpp>
#include <boost/hash2/is_trivially_equality_comparable.hpp>
#include <boost/hash2/is_endian_independent.hpp>
namespace boost {
namespace hash2 {
template<class T, endian E> struct is_contiguously_hashable;
} // namespace hash2
} // namespace boost
is_contiguously_hashable
template<class T, endian E> struct is_contiguously_hashable:
std::integral_constant<bool,
is_trivially_equality_comparable<T>::value &&
(E == endian::native || is_endian_independent<T>::value)>
{
};
template<class T, std::size_t N, endian E> struct is_contiguously_hashable<T[N], E>:
is_contiguously_hashable<T, E>
{
};
库使用 is_contiguously_hashable
特性来检测 连续可哈希 类型。
如果一个类型在特定字节序 E
下的 消息(如果它不被认为是 连续可哈希 的话)与其底层存储字节表示相同,则该类型是 连续可哈希 的。
当 value
的类型在 flavor
请求的字节序 (decltype(flavor)::byte_order
) 下是 连续可哈希 时,hash_append(hash, flavor, value)
作为优化会调用一次 hash.update(&value, sizeof(value))
。
is_contiguously_hashable
不打算为用户定义类型进行特化。其实现依赖于 is_trivially_equality_comparable
和 is_endian_independent
,只要这些底层特性正确,它就是正确的。
<boost/hash2/has_constant_size.hpp>
namespace boost {
namespace hash2 {
template<class T> struct has_constant_size;
} // namespace hash2
} // namespace boost
has_constant_size
template<class T> struct has_constant_size<T>: std::integral_constant<bool, /*see below*/>
{
};
template<class T> struct has_constant_size<T const>: has_constant_size<T>
{
};
库使用 has_constant_size
特性来检测具有常量大小的容器和范围类型。这允许 hash_append
不将大小包含在 消息 中,因为它不构成对象状态。
如果对于该类型的所有值 v
,v.size()
具有相同的值,则容器或范围类型具有常量大小。
默认实现对类元组类型(std::tuple_size
被特化的类型,例如 std::array
)、boost::array
和 digest
报告 true
。
has_constant_size
可以为具有常量大小的用户定义容器和范围类型进行特化。
例如,boost::uuids::uuid
的大小常量为 16,因此可以适当添加特化
template<> struct boost::hash2::has_constant_size<boost::uuids::uuid>: std::true_type {};
哈希 C++ 对象
<boost/hash2/hash_append_fwd.hpp>
概要
namespace boost {
namespace hash2 {
template<class Hash, class Flavor, class T>
constexpr void hash_append( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor, class It>
constexpr void hash_append_range( Hash& h, Flavor const& f, It first, It last );
template<class Hash, class Flavor, class T>
constexpr void hash_append_size( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor, class It>
constexpr void hash_append_range_and_size( Hash& h, Flavor const& f, It first, It last );
template<class Hash, class Flavor, class It>
constexpr void hash_append_unordered_range( Hash& h, Flavor const& f, It first, It last );
struct hash_append_tag;
} // namespace hash2
} // namespace boost
头文件 boost/hash2/hash_append_fwd.hpp
声明了在 boost/hash2/hash_append.hpp
中实现的函数。
当代码希望为用户定义类型实现 hash_append
支持而无需物理依赖于 hash_append
的实现时,可以使用它。
示例
#include <boost/hash2/hash_append_fwd.hpp>
#include <vector>
class X
{
private:
int a = -1;
std::vector<int> b{ 1, 2, 3 };
template<class Provider, class Hash, class Flavor>
friend void tag_invoke(
boost::hash2::hash_append_tag const&, Provider const&,
Hash& h, Flavor const& f, X const* v )
{
boost::hash2::hash_append( h, f, v->a );
boost::hash2::hash_append( h, f, v->b );
}
public:
X() = default;
};
#include "X.hpp"
#include <boost/hash2/hash_append.hpp>
#include <boost/hash2/md5.hpp>
#include <iostream>
int main()
{
X x;
boost::hash2::md5_128 hash;
boost::hash2::hash_append( hash, {}, x );
std::cout << "MD5 digest of x: " << hash.result() << std::endl;
}
然而,请注意,即使不包含 <boost/hash2/hash_append_fwd.hpp>
,也可以通过使用 tag_invoke
的 Provider
参数来实现相同的效果
#include <vector>
class X
{
private:
int a = -1;
std::vector<int> b{ 1, 2, 3 };
template<class Provider, class Hash, class Flavor>
friend void tag_invoke(
boost::hash2::hash_append_tag const&, Provider const& pr,
Hash& h, Flavor const& f, X const* v )
{
pr.hash_append( h, f, v->a );
pr.hash_append( h, f, v->b );
}
public:
X() = default;
};
<boost/hash2/hash_append.hpp>
#include <boost/hash2/flavor.hpp>
namespace boost {
namespace hash2 {
template<class Hash, class Flavor = default_flavor, class T>
constexpr void hash_append( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_range( Hash& h, Flavor const& f, It first, It last );
template<class Hash, class Flavor = default_flavor, class T>
constexpr void hash_append_size( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_range_and_size( Hash& h, Flavor const& f, It first, It last );
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_unordered_range( Hash& h, Flavor const& f, It first, It last );
struct hash_append_tag;
struct hash_append_provider;
} // namespace hash2
} // namespace boost
hash_append
template<class Hash, class Flavor = default_flavor, class T>
constexpr void hash_append( Hash& h, Flavor const& f, T const& v );
将 v
的表示形式追加到存储在 h
中的消息。
- 效果
-
-
如果
is_contiguously_hashable<T, Flavor::byte_order>::value
为true
,则调用h.update(&v, sizeof(v))
; -
如果
std::is_integral<T>::value
为true
,则以Flavor::byte_order
请求的字节序获取v
的字节表示,然后调用h.update(p, n)
,其中p
是该表示的地址,n
是sizeof(v)
; -
如果
std::is_floating_point<T>::value
为 true,则如果v
是负零,首先将其替换为正零,然后调用hash_append(h, f, std::bit_cast<U>(v))
,其中U
是与T
大小相同的无符号整数类型; -
如果
std::is_pointer<T>::value
为true
,则调用hash_append(h, f, reinterpret_cast<std::uintptr_t>(v))
; -
如果
T
是std::nullptr_t
,则调用hash_append(h, f, static_cast<void*>(v))
; -
如果
T
是数组类型U[N]
,则调用hash_append_range(h, f, v + 0, v + N)
; -
如果存在适用于
T
的适当tag_invoke
重载,则调用(不限定的)tag_invoke(hash_append_tag(), hash_append_provider(), h, f, v)
; -
如果
std::is_enum<T>::value
为true
,则调用hash_append(h, f, w)
,其中w
是v
转换为T
的底层类型; -
如果
boost::container_hash::is_unordered_range<T>::value
为true
,则调用hash_append_unordered_range(h, f, v.begin(), v.end())
; -
如果
boost::container_hash::is_contiguous_range<T>::value
为true
并且-
has_constant_size<T>::value
为true
,则调用hash_append_range(h, f, v.data(), v.data() + v.size())
; -
has_constant_size<T>::value
为false
,则调用hash_append_range_and_size(h, f, v.data(), v.data() + v.size())
;
-
-
如果
boost::container_hash::is_range<T>::value
为true
并且-
has_constant_size<T>::value
为true
,则调用hash_append_range(h, f, v.begin(), v.end())
; -
has_constant_size<T>::value
为false
,则调用hash_append_range_and_size(h, f, v.begin(), v.end())
;
-
-
如果
boost::container_hash::is_tuple_like<T>::value
为true
,则为每个元组元素w
调用hash_append(h, f, w)
; -
如果
boost::container_hash::is_described_class<T>::value
为true
,则为v
的每个基类子对象b
调用hash_append(h, f, b)
,然后为v
的每个成员子对象m
调用hash_append(h, f, m)
; -
否则,结果是编译时错误。
-
- 备注
-
如果上述描述导致没有进行任何调用(例如,对于大小为零的常量范围,或没有基类和成员的描述结构),则会调用
hash_append(h, f, '\x00')
以满足hash_append
始终至少导致一次对Hash::update
的调用的要求。
hash_append_range
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_range( Hash& h, Flavor const& f, It first, It last );
将范围 [first, last)
中元素的表示形式追加到存储在 h
中的消息。
- 要求
-
It
必须是一个 迭代器 类型。[first, last)
必须是一个有效的 迭代器范围。 - 效果
-
-
如果
It
是T*
并且is_contiguously_hashable<T, Flavor::byte_order>::value
为true
,则调用h.update(first, (last - first) * sizeof(T));
。 -
否则,对于范围
[first, last)
中表示的每个元素v
,调用hash_append(h, f, v);
。
-
- 备注
-
如果在常量表达式中调用
hash_append_range
,则仅对unsigned char*
和unsigned char const*
应用连续可哈希优化。
hash_append_size
template<class Hash, class Flavor = default_flavor, class T>
constexpr void hash_append_size( Hash& h, Flavor const& f, T const& v );
将 v
的表示形式(转换为 Flavor::size_type
)追加到存储在 h
中的消息。
- 要求
-
T
必须是整型。 - 效果
-
等价于
hash_append(h, f, static_cast<typename Flavor::size_type>(v));
hash_append_range_and_size
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_range_and_size( Hash& h, Flavor const& f, It first, It last );
将范围 [first, last)
中元素的表示形式,后跟范围的大小,追加到存储在 h
中的消息。
- 要求
-
It
必须是一个 迭代器 类型。[first, last)
必须是一个有效的 迭代器范围。 - 效果
-
等价于
hash_append_range(h, f, first, last); hash_append(h, f, m);
,其中m
是std::distance(first, last)
。
hash_append_unordered_range
template<class Hash, class Flavor = default_flavor, class It>
constexpr void hash_append_unordered_range( Hash& h, Flavor const& f, It first, It last );
以一种不影响结果顺序的方式,从范围 [first, last)
中元素的表示形式构造一个值,然后将该值以及范围的大小追加到存储在 h
中的消息。
- 要求
-
It
必须是一个 迭代器 类型。[first, last)
必须是一个有效的 迭代器范围。 - 效果
-
对于范围
[first, last)
中表示的每个元素v
,通过以下方式获取哈希值r
Hash h2(h); hash_append(h2, f, v); auto r = h2.result();
然后以不敏感于其顺序的方式组合所获得的
r
值,生成组合值q
。调用hash_append(h, f, q)
,然后调用hash_append(h, f, m)
,其中m
是std::distance(first, last)
。
hash_append_tag
struct hash_append_tag
{
};
hash_append_tag
是一个标签类型,用作 tag_invoke
重载的第一个参数,以标识 hash_append
操作。
hash_append_provider
struct hash_append_provider
{
template<class Hash, class Flavor, class T>
static constexpr void hash_append( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor, class It>
static constexpr void hash_append_range( Hash& h, Flavor const& f, It first, It last );
template<class Hash, class Flavor, class T>
static constexpr void hash_append_size( Hash& h, Flavor const& f, T const& v );
template<class Hash, class Flavor, class It>
static constexpr void hash_append_range_and_size( Hash& h, Flavor const& f,
It first, It last );
template<class Hash, class Flavor, class It>
static constexpr void hash_append_unordered_range( Hash& h, Flavor const& f,
It first, It last );
};
类型为 hash_append_provider
的对象作为 tag_invoke
重载的第二个参数传递。
其所有成员函数都调用 boost::hash2
中同名的相应函数。
版权和许可证
本文档的版权归 2020 年、2024 年 Peter Dimov 和 Christian Mazakas 所有,并根据 Boost 软件许可证,版本 1.0 发布。