The safest path is probably to reuse standard hashing for your atomic types and combine them as you suggested. AFAIK there are no hash combination routines in the standard, but Boost does provide one:
#include <boost/functional/hash.hpp>
#include <functional>
namespace std
{
template<>
struct hash<Key>
{
public:
std::size_t
operator()(Key const& k) const
{
size_t hash = 0;
boost::hash_combine(hash, std::hash<short>()(k.a));
boost::hash_combine(hash, std::hash<int>()(k.b));
boost::hash_combine(hash, std::hash<int>()(k.c));
boost::hash_combine(hash, std::hash<int>()(k.d));
return hash;
}
};
}
If depending on Boost is not an option, their hash combination routine is small enough to be reasonably and shamelessly stolen:
template <class T>
inline void hash_combine(std::size_t& seed, const T& v)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
If your four integral value are purely random (e.g. they can take any value in range with an equal probability), this is probably very close to being optimal. If your values are more specific - one has only three possible values for instance or they are correlated - you could do slightly better. However, this will perform "well" in any circumstance.
Anyway, I don't think you should be too worried unless you're doing something extremely specific, or at least until actual performance issues arise. It's still time to change the hashing algorithm then with no other impact.
a,b,canddactually are. What does it mean for your key to be unique? Do all of them need to be distinct? Is there a single identifier with some additional information?