template <class Data>
struct HashNode{
// Probably should be shared_ptr<HashNode<Data>>, but that's
// now adding even more inefficiencies like a ref_count per element
struct HashNode<Data>* nextPointerChain;
Data d; // Maybe Data* d if you're sharing it with other data-structures?
};
template <class Data, int size>
struct ChainHashTable{
HashNode<Data> theTable[size];
};
template <class Data, int size>
struct LinearProbingHashTable{
Data theTable[size];
vector<bool> occupied; // set to "size", 0 means empty and 1 means occupied
};
nextPointerChain takes up 8 bytes, even if its nullptr. No other data-structure needs to have the nextPointerChain. If we use shared_ptr<HashNode<Data>> as per typical modern C++, there's even more inefficiencies that I forgot about. EDIT: You probably can get away with unique_ptr, now that I think of it.----------
Pointers aren't free btw. If you're sharing the pointer with many different parts of your program, that's definitely one of those things you'll want to start getting rid of. Not only does a pointer cost 8 bytes, but its also _SIGNIFICANTLY_ slower and cache-unfriendly in practice.
L1 cache works by reading data in-and-around anything you access. If you're only using 8-bytes of a cache line for pointer-indirection (8/64), you're wasting 56-bytes of your fetch.
Linear Probing is extremely fast because it tends to blitz through L1 cache thanks to locality of data. Simple Linear Probing (or Robin-hood augmented probing) is probably the fastest, and simplest, approach for modern CPUs.