utility_hash

utility_hash (해시 함수 객체)

std::hash는 주어진 키 타입에 대한 해시 값을 계산하는 함수 객체를 생성하는 클래스 템플릿이에요. std::unordered_set, std::unordered_map 같은 무순서 연관 컨테이너가 기본 해시 함수로 사용해요. 이 페이지에서는 std::hash의 정의, 활성화 조건, 표준 라이브러리에서 제공하는 특수화 목록을 확인할 수 있어요.

출처: cppreference

본문

정의

다음 헤더에 정의되어 있어요:

헤더
<bitset>
<coroutine>
<chrono> (C++26부터)
<filesystem>
<functional>
<memory>
<optional>
<stacktrace>
<string>
<string_view>
<system_error>
<text_encoding>
<thread>
<typeindex>
<variant>
<vector>
template < class Key > struct hash;

(C++11부터)

무순서 연관 컨테이너 std::unordered_set, std::unordered_multiset, std::unordered_map, std::unordered_multimap은 기본 해시 함수로 std::hash 템플릿의 특수화를 사용해요.

활성화 조건

어떤 타입 Key에 대해 각 특수화 std::hash<Key>는 활성화(enable)되거나 비활성화(disable)돼요.

  • 프로그램이나 사용자가 std::hash<Key>를 제공하지 않으면 비활성화돼요.
  • 그렇지 않으면 다음 조건을 모두 만족할 때 std::hash<Key>가 활성화돼요:
    • 다음 요구 사항을 모두 만족해야 해요: Hash (Key를 함수 호출 인자 타입으로 사용), DefaultConstructible, CopyAssignable, Swappable.
    • 다음 값들이 주어졌을 때: hstd::hash<Key> 타입의 객체, k1k2Key 타입의 객체. 다음 요구 사항을 모두 만족해야 해요: k1 == k2true이면 h(k1) == h(k2)true여야 해요. std::hash<Key>가 프로그램 정의 특수화가 아니라면 h(k1)은 예외를 던지지 않아요.
  • 그 외에는 std::hash<Key>는 비활성화돼요.

비활성화된 특수화는 Hash 요구 사항도, FunctionObject 요구 사항도 만족하지 않으며, 다음 값들이 모두 false가 돼요:

  • std::is_default_constructible<std::hash<Key>>::value
  • std::is_copy_constructible<std::hash<Key>>::value
  • std::is_move_constructible<std::hash<Key>>::value
  • std::is_copy_assignable<std::hash<Key>>::value
  • std::is_move_assignable<std::hash<Key>>::value

즉, 존재는 하지만 사용할 수 없어요.

중첩 타입

타입 정의
argument_type (C++17부터 deprecated) Key
result_type (C++17부터 deprecated) std::size_t

(C++20까지)

멤버 함수

멤버 함수 설명
(constructor) 해시 함수 객체를 생성해요 (공개 멤버 함수)
operator() 인자의 해시 값을 계산해요 (공개 멤버 함수)

표준 라이브러리 특수화

템플릿 std::hash를 선언하는 각 헤더는 다음 타입들에 대해 활성화된 std::hash 특수화도 제공해요:

  • cv 한정이 없는 모든 산술 타입 (cv-unqualified arithmetic types)
  • cv 한정이 없는 모든 열거형 타입
  • cv 한정이 없는 모든 포인터 타입
  • std::nullptr_t

그 외에도 일부 헤더는 라이브러리 타입에 대한 다른 활성화된 std::hash 특수화를 제공해요 (아래 참고).

표준 라이브러리가 제공하는 모든 std::hash 특수화 중 다음을 제외하면 모든 멤버 함수가 noexcept예요:

  • std::hash<std::optional>
  • std::hash<std::variant>
  • std::hash<std::unique_ptr>
  • std::hash<std::indirect> (C++26부터)
  • std::hash<std::chrono::duration> (C++26부터)
  • std::hash<std::chrono::time_point> (C++26부터)
  • std::hash<std::chrono::zoned_time> (C++26부터)

(C++17부터)

라이브러리 타입 특수화

언어 지원 라이브러리

특수화 설명
std::hash<std::coroutine_handle> (C++20) std::coroutine_handle에 대한 해시 지원 (클래스 템플릿 특수화)

진단 라이브러리

특수화 설명
std::hash<std::error_code> (C++11) std::error_code에 대한 해시 지원
std::hash<std::error_condition> (C++17) std::error_condition에 대한 해시 지원
std::hash<std::type_index> (C++11) std::type_index에 대한 해시 지원
std::hash<std::stacktrace_entry> (C++23) std::stacktrace_entry에 대한 해시 지원
std::hash<std::basic_stacktrace> (C++23) std::basic_stacktrace에 대한 해시 지원

메모리 관리 라이브러리

특수화 설명
std::hash<std::unique_ptr> (C++11) std::unique_ptr에 대한 해시 지원
std::hash<std::shared_ptr> (C++11) std::shared_ptr에 대한 해시 지원
std::hash<std::indirect> (C++26) std::indirect에 대한 해시 지원

일반 유틸리티 라이브러리

특수화 설명
std::hash<std::optional> (C++17) std::optional에 대한 해시 지원
std::hash<std::variant> (C++17) std::variant에 대한 해시 지원
std::hash<std::monostate> (C++17) std::monostate에 대한 해시 지원
std::hash<std::bitset> (C++11) std::bitset에 대한 해시 지원

컨테이너 라이브러리

특수화 설명
std::hash<std::vector<bool>> (C++11) std::vector<bool>에 대한 해시 지원

문자열 라이브러리

특수화 설명
std::hash<std::basic_string> (C++11) 문자열에 대한 해시 지원
std::hash<std::string_view> (C++17) string view에 대한 해시 지원
std::hash<std::wstring_view> (C++17)
std::hash<std::u8string_view> (C++20)
std::hash<std::u16string_view> (C++17)
std::hash<std::u32string_view> (C++17)

텍스트 처리 라이브러리

특수화 설명
std::hash<std::text_encoding> (C++26) std::text_encoding에 대한 해시 지원

시간 라이브러리

특수화 설명
std::hash<std::chrono::duration> (C++26) std::chrono::duration에 대한 해시 지원
std::hash<std::chrono::time_point> (C++26) std::chrono::time_point에 대한 해시 지원
std::hash<std::chrono::day> (C++26) std::chrono::day에 대한 해시 지원
std::hash<std::chrono::month> (C++26) std::chrono::month에 대한 해시 지원
std::hash<std::chrono::year> (C++26) std::chrono::year에 대한 해시 지원
std::hash<std::chrono::weekday> (C++26) std::chrono::weekday에 대한 해시 지원
std::hash<std::chrono::weekday_indexed> (C++26) std::chrono::weekday_indexed에 대한 해시 지원
std::hash<std::chrono::weekday_last> (C++26) std::chrono::weekday_last에 대한 해시 지원
std::hash<std::chrono::month_day> (C++26) std::chrono::month_day에 대한 해시 지원
std::hash<std::chrono::month_day_last> (C++26) std::chrono::month_day_last에 대한 해시 지원
std::hash<std::chrono::month_weekday> (C++26) std::chrono::month_weekday에 대한 해시 지원
std::hash<std::chrono::month_weekday_last> (C++26) std::chrono::month_weekday_last에 대한 해시 지원
std::hash<std::chrono::year_month> (C++26) std::chrono::year_month에 대한 해시 지원
std::hash<std::chrono::year_month_day> (C++26) std::chrono::year_month_day에 대한 해시 지원
std::hash<std::chrono::year_month_day_last> (C++26) std::chrono::year_month_day_last에 대한 해시 지원
std::hash<std::chrono::year_month_weekday> (C++26) std::chrono::year_month_weekday에 대한 해시 지원
std::hash<std::chrono::year_month_weekday_last> (C++26) std::chrono::year_month_weekday_last에 대한 해시 지원
std::hash<std::chrono::zoned_time> (C++26) std::chrono::zoned_time에 대한 해시 지원
std::hash<std::chrono::leap_second> (C++26) std::chrono::leap_second에 대한 해시 지원

입출력 라이브러리

특수화 설명
std::hash<std::filesystem::path> (C++17) std::filesystem::path에 대한 해시 지원

동시성 지원 라이브러리

특수화 설명
std::hash<std::thread::id> (C++11) std::thread::id에 대한 해시 지원

참고

실제 해시 함수는 구현에 따라 달라요. 위에서 명시한 기준 외에 다른 품질 기준을 충족할 필요는 없어요. 특히 일부 구현은 정수를 그 자체로 매핑하는 단순(항등) 해시 함수를 사용하기도 해요. 즉, 이런 해시 함수는 무순서 연관 컨테이너와 함께 사용하도록 설계되었지, 예를 들어 암호화 해시로는 적합하지 않아요.

해시 함수는 프로그램의 단일 실행 안에서 같은 입력에 대해 같은 결과를 내기만 하면 돼요. 이 덕분에 충돌 서비스 거부 공격을 막기 위한 솔트(salted) 해시를 사용할 수 있어요.

C 문자열에 대한 특수화는 없어요. std::hash<const char*>는 포인터 값(메모리 주소)의 해시를 생성하며, 문자 배열의 내용을 검사하지 않아요.

std::pair와 표준 컨테이너 타입에 대한 추가 특수화와 해시를 결합하는 유틸리티 함수는 boost::hash에서 사용할 수 있어요.

예제

#include <cstddef>
#include <functional>
#include <iomanip>
#include <iostream>
#include <string>
#include <unordered_set>

struct S
{
    std::string first_name;
    std::string last_name;
    bool operator==(const S&) const = default; // since C++20
};

// Before C++20.
// bool operator==(const S& lhs, const S& rhs)
// {
//     return lhs.first_name == rhs.first_name && lhs.last_name == rhs.last_name;
// }

// Custom hash can be a standalone function object.
struct MyHash
{
    std::size_t operator()(const S& s) const noexcept
    {
        std::size_t h1 = std::hash<std::string>{}(s.first_name);
        std::size_t h2 = std::hash<std::string>{}(s.last_name);
        return h1 ^ (h2 << 1); // or use boost::hash_combine
    }
};

// Custom specialization of std::hash can be injected in namespace std.
template<>
struct std::hash<S>
{
    std::size_t operator()(const S& s) const noexcept
    {
        std::size_t h1 = std::hash<std::string>{}(s.first_name);
        std::size_t h2 = std::hash<std::string>{}(s.last_name);
        return h1 ^ (h2 << 1); // or use boost::hash_combine
    }
};

int main()
{
    std::string str = "Meet the new boss...";
    std::size_t str_hash = std::hash<std::string>{}(str);
    std::cout << "hash(" << std::quoted(str) << ") =\t" << str_hash << '\n';
    
    S obj = {"Hubert", "Farnsworth"};
    // Using the standalone function object.
    std::cout << "hash(" << std::quoted(obj.first_name) << ", "
              << std::quoted(obj.last_name) << ") =\t"
              << MyHash{}(obj) << " (using MyHash) or\n\t\t\t\t"
              << std::hash<S>{}(obj) << " (using injected specialization)\n";
    
    // Custom hash makes it possible to use custom types in unordered containers.
    // The example will use the injected std::hash<S> specialization above,
    // to use MyHash instead, pass it as a second template argument.
    std::unordered_set<S> names = {obj, {"Bender", "Rodriguez"}, {"Turanga", "Leela"}};
    for (auto const& s: names)
        std::cout << std::quoted(s.first_name) << ' '
                  << std::quoted(s.last_name) << '\n';
}

가능한 출력:

hash("Meet the new boss...") =  10656026664466977650
hash("Hubert", "Farnsworth") =  12922914235676820612 (using MyHash) or
                                12922914235676820612 (using injected specialization)
"Bender" "Rodriguez"
"Turanga" "Leela"
"Hubert" "Farnsworth"

결함 보고

다음 동작을 변경하는 결함 보고서는 이전에 발표된 C++ 표준에 소급 적용되었어요.

DR 적용 대상 발표된 동작 올바른 동작
LWG 2119 C++11 확장 정수 타입에 대한 특수화가 없었음 제공됨
LWG 2148 C++11 열거형에 대한 특수화가 없었음 제공됨
LWG 2543 C++11 std::hash가 SFINAE 친화적이지 않을 수 있었음 SFINAE 친화적으로 변경됨
LWG 2817 C++11 std::nullptr_t에 대한 특수화가 없었음 제공됨

더 알아보기

cppreference