2017-03-06 7 views
5

Norma mówi, że po około specjalizujący szablony z biblioteki standardowej (przez What can and can't I specialize in the std namespace?)Czy legalne jest wyspecjalizowanie funkcji biblioteki standardowej dla shared_ptr typu zdefiniowanego przez użytkownika?

Program może dodać szablon specjalizacji dla każdego Standard Template Library do namespace std tylko jeżeli zgłoszenie zależy od czytelnymi zdefiniowany typ i specjalizacja spełnia standardowe wymagania biblioteki dla oryginalnego szablonu i nie jest wyraźnie zabroniona.

Czy specjalizacja standardowych szablonów biblioteki ze standardową klasą bibliotek specjalizującą się w klasie zdefiniowanej przez użytkownika jest legalna?

Na przykład specjalizujący się w std::hash dla std::shared_ptr<MyType>?

Po przeczytaniu powyższego akapitu i połączonego pytania wygląda na to, że powinno być, ponieważ deklaracja specjalizacji jest zależna od MyType, jednak "chyba że wyraźnie zabronione" niepokoi mnie nieco.

Poniższy przykład kompiluje i działa zgodnie z oczekiwaniami (AppleClang 7.3), ale czy jest legalny?

#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); 
} 
+2

Tak, jest to zgodne z prawem, nie ma ograniczeń dotyczących specjalizacji 'std :: hash' z wyjątkiem * DefaultConstructible, CopyAssignable, Swappable i Destructible * (i wszystkich innych wymagań, które nie mają nic wspólnego z *" jawnie zabronione "*) . Jednym z przykładów specjalizacji * "wyraźnie zabronione" * jest specjalizacja 'std :: numeric_limits' dla niearytmetycznych standardowych typów. – Holt

+0

Powinieneś rzucić okiem na odnośnik Argument Dependend, nie musisz dodawać funkcji do przestrzeni nazw standardu. –

Odpowiedz

3

Tak, to jest legalne.

Jest to w pewnym sensie prawna specjalizacja dla std::shared_ptr<int> w jednym punkcie; Nie wiem, czy załatali tę niejednoznaczność w standardzie jako wadę, czy nie.

Należy zauważyć, że jest to kiepska implementacja skrótu do użytku globalnego. Po pierwsze dlatego, że nie obsługuje zerowych wskaźników współużytkowania. Po drugie, ponieważ mieszanie wspólnego wskaźnika jak zawsze wartości int jest wątpliwe. Jest to nawet niebezpieczne, ponieważ jeśli wspólny wskaźnik do int w kontenerze ma tę zmianę int, po prostu przerwałeś program.

Zastanów się nad własnymi hasherami dla tego rodzaju przypadków.

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); 
    } 
    }; 

} 

Teraz pozwala to na 3 punkty personalizacji.

Po pierwsze, jeśli zastąpisz std::size_t hash(T const&) w przestrzeni nazw zawierającej T, to go podnosi.

W przypadku braku specjalizacji, notstd::hasher_impl<T, void> dla Twojego typu , odbiera ją.

Po trzecie, jeśli oba te elementy zawiodą, wywołają one std::hash<T>, zbierając wszelkie specjalizacje.

Następnie można zrobić:

std::unordered_set<std::shared_ptr<MyType>, ::notstd::hasher> mySet; 

i dodać:

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); 
    } 
}; 

które powinny dać inteligentną hash na na shared_ptr<MyType>.

To zapobiega niebezpieczeństwu, że ktoś zmienia id na shared_ptr<MyType>, który łamie każdy pojemnik zawierający shared_ptr<MyType> w sposób nie lokalny.

Współdzielonym stanem jest diabeł; rozważ napisanie kopii na wskaźniku pisania, jeśli naprawdę martwisz się, że kopiowanie tych rzeczy będzie kosztowne.

Powiązane problemy