In
hash_table(std::size_t m): The body is simply zero-initialization of themarkarray. The same can be achieved with:hash_table(std::size_t m) : M(power_of_2_ceiling(m)), table(new std::pair<K, V>[M]), mark(new bool[M]{}), N(0) { }In
hash_table(hash_table<K, V>&& map): Is there a reason to setMandNto zero? A moved object only needs to be properly destructable, but the destructor does not useMorN.You can refer to the current instantiation of a template class without specifying the arguments, i.e.
hash_table(hash_table&& map)would be fine for the move constructor, too.To use
std::unordered_mapyou need to include<unordered_map>.hash_table(std::unordered_map<K, V>&& map): You have a rvalue reference constructor here, but none for const references. That seems inconsistent.hash_table(std::unordered_map<K, V>&& map)initially does the same ashash_table(std::size_t), you could simply call it:hash_table(std::unordered_map<K, V>&& map) : hash_table(map.size()) { for (auto &p : map) { insert(std::move(p)); } }hash_table<K, V>& operator=(hash_table<K, V> other): There is no reason to pass by value here. There should be one copy assignment operator taking a const reference and one move assignment operator taking an rvalue reference.swap(*this, other);would then also be the wrong behavior. You need to explicitly handle memory reallocations here.(dropped)With a proper move assignment operator you would not really need to define a
swap. The default implementation ofstd::swapwill already do the correct thing and probably not too much performance difference, see here.(dropped)You could use just one array of type
std::tuple<K,V,bool>(or a custom struct) instead of two arraystableandmark. Then you would not have to repeat the memory management code twice, and with the access pattern related data is closer in memory. Together with my point 2. this would reduce overhead even more.Your only method to return elements
findreturns aconst std::pair<K,V>*, implying that it is impossible to modify the value of an entry in-place. I think that is a rather strong limitation. Of course one may not change the key in-place, but the value should be modifiable. Therefore the type should bestd::pair<const K,V>*andtablealso should be of typestd::pair<const K,V>.The maximal size of your
hash_tableis kind of arbitarily chosen and once it is full there will be an exception thrown. This might not have been the scope of your intentions but really the size should grow when a certain load is reached and rehashing should happen.Your
inserttakes a rvalue reference. This means that only temporaries ormoved elements can be inserted. There should also be an overload for const references (and withoutmove).size()should beconst.
In
hash_table(std::size_t m): The body is simply zero-initialization of themarkarray. The same can be achieved with:hash_table(std::size_t m) : M(power_of_2_ceiling(m)), table(new std::pair<K, V>[M]), mark(new bool[M]{}), N(0) { }In
hash_table(hash_table<K, V>&& map): Is there a reason to setMandNto zero? A moved object only needs to be properly destructable, but the destructor does not useMorN.You can refer to the current instantiation of a template class without specifying the arguments, i.e.
hash_table(hash_table&& map)would be fine for the move constructor, too.To use
std::unordered_mapyou need to include<unordered_map>.hash_table(std::unordered_map<K, V>&& map): You have a rvalue reference constructor here, but none for const references. That seems inconsistent.hash_table(std::unordered_map<K, V>&& map)initially does the same ashash_table(std::size_t), you could simply call it:hash_table(std::unordered_map<K, V>&& map) : hash_table(map.size()) { for (auto &p : map) { insert(std::move(p)); } }hash_table<K, V>& operator=(hash_table<K, V> other): There is no reason to pass by value here. There should be one copy assignment operator taking a const reference and one move assignment operator taking an rvalue reference.swap(*this, other);would then also be the wrong behavior. You need to explicitly handle memory reallocations here.With a proper move assignment operator you would not really need to define a
swap. The default implementation ofstd::swapwill already do the correct thing and probably not too much performance difference, see here.You could use just one array of type
std::tuple<K,V,bool>(or a custom struct) instead of two arraystableandmark. Then you would not have to repeat the memory management code twice, and with the access pattern related data is closer in memory. Together with my point 2. this would reduce overhead even more.Your only method to return elements
findreturns aconst std::pair<K,V>*, implying that it is impossible to modify the value of an entry in-place. I think that is a rather strong limitation. Of course one may not change the key in-place, but the value should be modifiable. Therefore the type should bestd::pair<const K,V>*andtablealso should be of typestd::pair<const K,V>.The maximal size of your
hash_tableis kind of arbitarily chosen and once it is full there will be an exception thrown. This might not have been the scope of your intentions but really the size should grow when a certain load is reached and rehashing should happen.Your
inserttakes a rvalue reference. This means that only temporaries ormoved elements can be inserted. There should also be an overload for const references (and withoutmove).size()should beconst.
In
hash_table(std::size_t m): The body is simply zero-initialization of themarkarray. The same can be achieved with:hash_table(std::size_t m) : M(power_of_2_ceiling(m)), table(new std::pair<K, V>[M]), mark(new bool[M]{}), N(0) { }In
hash_table(hash_table<K, V>&& map): Is there a reason to setMandNto zero? A moved object only needs to be properly destructable, but the destructor does not useMorN.You can refer to the current instantiation of a template class without specifying the arguments, i.e.
hash_table(hash_table&& map)would be fine for the move constructor, too.To use
std::unordered_mapyou need to include<unordered_map>.hash_table(std::unordered_map<K, V>&& map): You have a rvalue reference constructor here, but none for const references. That seems inconsistent.hash_table(std::unordered_map<K, V>&& map)initially does the same ashash_table(std::size_t), you could simply call it:hash_table(std::unordered_map<K, V>&& map) : hash_table(map.size()) { for (auto &p : map) { insert(std::move(p)); } }(dropped)
(dropped)
You could use just one array of type
std::tuple<K,V,bool>(or a custom struct) instead of two arraystableandmark. Then you would not have to repeat the memory management code twice, and with the access pattern related data is closer in memory. Together with my point 2. this would reduce overhead even more.Your only method to return elements
findreturns aconst std::pair<K,V>*, implying that it is impossible to modify the value of an entry in-place. I think that is a rather strong limitation. Of course one may not change the key in-place, but the value should be modifiable. Therefore the type should bestd::pair<const K,V>*andtablealso should be of typestd::pair<const K,V>.The maximal size of your
hash_tableis kind of arbitarily chosen and once it is full there will be an exception thrown. This might not have been the scope of your intentions but really the size should grow when a certain load is reached and rehashing should happen.Your
inserttakes a rvalue reference. This means that only temporaries ormoved elements can be inserted. There should also be an overload for const references (and withoutmove).size()should beconst.
Here are a few things I noticed reading your code:
You should probably use more descriptive identifiers for
MandNhere.Instead of dynamic allocation you could also use
std::vector<...>fortableandmark. That would allow to simplify your code considerably. For example you could drop the custom copy/move constructors/assignment operators and the destructor and use the implicitly defined ones instead. You could also use the provided.size()method ofstd::vectorto getMinstead of saving it explicitly.
If you didn't use std::vector for performance considerations, then I doubt that is a problem. Element access is through one indirection anyway and you do not have resize operations. Simply use reserve in your constructor to allocate the proper size immediately. Only the required memory for std::vector might be a bit larger (additional integer for current size/reserved size and doubling of length M).
Even if you still don't want to use std::vector, then you should consider writting an additional class (heap_array?) to handle the memory management or you could use std::unique_ptr, which would at least make your custom move constructor/assignment operator and the destructor redundant.
In
hash_table(std::size_t m): The body is simply zero-initialization of themarkarray. The same can be achieved with:hash_table(std::size_t m) : M(power_of_2_ceiling(m)), table(new std::pair<K, V>[M]), mark(new bool[M]{}), N(0) { }In
hash_table(hash_table<K, V>&& map): Is there a reason to setMandNto zero? A moved object only needs to be properly destructable, but the destructor does not useMorN.You can refer to the current instantiation of a template class without specifying the arguments, i.e.
hash_table(hash_table&& map)would be fine for the move constructor, too.To use
std::unordered_mapyou need to include<unordered_map>.hash_table(std::unordered_map<K, V>&& map): You have a rvalue reference constructor here, but none for const references. That seems inconsistent.hash_table(std::unordered_map<K, V>&& map)initially does the same ashash_table(std::size_t), you could simply call it:hash_table(std::unordered_map<K, V>&& map) : hash_table(map.size()) { for (auto &p : map) { insert(std::move(p)); } }hash_table<K, V>& operator=(hash_table<K, V> other): There is no reason to pass by value here. There should be one copy assignment operator taking a const reference and one move assignment operator taking an rvalue reference.swap(*this, other);would then also be the wrong behavior. You need to explicitly handle memory reallocations here.With a proper move assignment operator you would not really need to define a
swap. The default implementation ofstd::swapwill already do the correct thing and probably not too much performance difference, see here.You could use just one array of type
std::tuple<K,V,bool>(or a custom struct) instead of two arraystableandmark. Then you would not have to repeat the memory management code twice, and with the access pattern related data is closer in memory. Together with my point 2. this would reduce overhead even more.Your only method to return elements
findreturns aconst std::pair<K,V>*, implying that it is impossible to modify the value of an entry in-place. I think that is a rather strong limitation. Of course one may not change the key in-place, but the value should be modifiable. Therefore the type should bestd::pair<const K,V>*andtablealso should be of typestd::pair<const K,V>.The maximal size of your
hash_tableis kind of arbitarily chosen and once it is full there will be an exception thrown. This might not have been the scope of your intentions but really the size should grow when a certain load is reached and rehashing should happen.Your
inserttakes a rvalue reference. This means that only temporaries ormoved elements can be inserted. There should also be an overload for const references (and withoutmove).size()should beconst.