ENH: additional HashTable emplace/insert/set methods (#1286)

- support move insert/set and emplace insertion.

  These adjustments can be used for improved memory efficiency, and
  allow hash tables of non-copyable objects (eg, std::unique_ptr).

- extend special HashTable output treatment to include pointer-like
  objects such as autoPtr and unique_ptr.

ENH: HashTable::at() method with checking. Fatal if entry does not exist.
This commit is contained in:
Mark Olesen
2019-05-06 08:34:39 +02:00
committed by Andrew Heather
parent e30dc962b3
commit ac317699d8
11 changed files with 227 additions and 70 deletions

View File

@ -2,7 +2,7 @@
========= | ========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration | \\ / O peration |
\\ / A nd | Copyright (C) 2017-2018 OpenCFD Ltd. \\ / A nd | Copyright (C) 2017-2019 OpenCFD Ltd.
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
| Copyright (C) 2011 OpenFOAM Foundation | Copyright (C) 2011 OpenFOAM Foundation
@ -25,9 +25,9 @@ License
Description Description
\*---------------------------------------------------------------------------*/ \*---------------------------------------------------------------------------*/
#include <memory>
#include <iostream> #include <iostream>
#include "autoPtr.H" #include "autoPtr.H"
#include "HashPtrTable.H" #include "HashPtrTable.H"
@ -56,14 +56,14 @@ void printTable(const HashPtrTable<T>& table)
Info<< ")" << endl; Info<< ")" << endl;
// Values only, with for-range // Iterate across values, with for-range
Info<< "values ("; Info<< "values (";
for (auto val : table) for (const auto& ptr : table)
{ {
Info<< ' '; Info<< ' ';
if (val) if (ptr)
{ {
Info<< *val; Info<< *ptr;
} }
else else
{ {
@ -86,8 +86,27 @@ int main()
myTable.set("natlog", new double(2.718282)); myTable.set("natlog", new double(2.718282));
myTable.insert("sqrt2", autoPtr<double>::New(1.414214)); myTable.insert("sqrt2", autoPtr<double>::New(1.414214));
HashTable<std::unique_ptr<double>, word, string::hash> myTable1;
myTable1.set("abc", std::unique_ptr<double>(new double(42.1)));
myTable1.set("pi", std::unique_ptr<double>(new double(3.14159)));
myTable1.set("natlog", std::unique_ptr<double>(new double(2.718282)));
HashTable<autoPtr<double>, word, string::hash> myTable2;
myTable2.set("abc", autoPtr<double>(new double(42.1)));
myTable2.set("pi", autoPtr<double>(new double(3.14159)));
myTable2.set("natlog", autoPtr<double>(new double(2.718282)));
myTable2.insert("sqrt2", autoPtr<double>::New(1.414214));
// Info<< myTable << endl; // Info<< myTable << endl;
printTable(myTable); printTable(myTable);
Info<< myTable2 << nl;
auto iter2 = myTable2.find("pi");
Info<<"PI: " << **iter2 << nl;
HashPtrTable<double> copy(myTable); HashPtrTable<double> copy(myTable);

View File

@ -249,6 +249,28 @@ int main(int argc, char *argv[])
Info<< nl << "Ending scope" << nl; Info<< nl << "Ending scope" << nl;
} }
{
Info<< nl << "Table<labelList> copy/move/emplace insertion" << nl;
HashTable<labelList> ltable1(0);
ltable1.insert("abc", identity(2));
ltable1.insert("def", identity(3));
ltable1.insert("ghi", identity(4));
ltable1.emplace("jkl", 10, -35);
ltable1.emplace("mno");
labelList list1(identity(4, -4));
Info<<"move insert " << list1 << nl;
ltable1.insert("pqr", std::move(list1));
Info<<"after insert " << list1 << nl;
Info<< nl << "HashTable<labelList>: "
<< ltable1 << nl;
}
Info<< "\nEnd\n" << endl; Info<< "\nEnd\n" << endl;
return 0; return 0;

View File

@ -145,15 +145,15 @@ int main(int argc, char *argv[])
const label nElem = 1000000; const label nElem = 1000000;
argList::noBanner(); argList::noBanner();
argList::addBoolOption("std", "use std::unordered_map or std::set");
argList::addBoolOption("set", "test HashSet");
argList::addBoolOption("find", "test find"); argList::addBoolOption("find", "test find");
argList::addBoolOption("set", "test HashSet");
argList::addBoolOption("std", "std::unordered_map or std::unordered_set");
argList args(argc, argv); argList args(argc, argv);
const bool optStd = args.found("std");
const bool optSet = args.found("set");
const bool optFnd = args.found("find"); const bool optFnd = args.found("find");
const bool optSet = args.found("set");
const bool optStd = args.found("std");
cpuTime timer; cpuTime timer;
@ -171,7 +171,7 @@ int main(int argc, char *argv[])
if (false) if (false)
{ {
// verify that resizing around (0) doesn't fail // Verify that resizing around (0) doesn't fail
HashTable<label, label, Hash<label>> map(32); HashTable<label, label, Hash<label>> map(32);
printInfo(Info, map) << endl; printInfo(Info, map) << endl;
@ -243,7 +243,7 @@ int main(int argc, char *argv[])
#ifdef ORDERED #ifdef ORDERED
Info<< "using stl::map" << endl; Info<< "using stl::map" << endl;
#else #else
Info<< "using stl::unordered_set" << endl; Info<< "using stl::unordered_map" << endl;
#endif #endif
for (label loopi = 0; loopi < nLoops; ++loopi) for (label loopi = 0; loopi < nLoops; ++loopi)

View File

@ -48,17 +48,21 @@ using namespace Foam;
template<class T, class Key, class Hash> class HashSorter; template<class T, class Key, class Hash> class HashSorter;
template<class T, class Key, class Hash> template<class T, class Key, class Hash>
Ostream& operator<<(Ostream& os, const HashSorter<T, Key, Hash>& sorter); Ostream& operator<<
(
Ostream& os,
const HashSorter<T, Key, Hash>& sorter
);
template<class T, class Key, class Hash> template<class T, class Key, class Hash>
class HashSorter class HashSorter
{ {
const HashTable<T,Key,Hash>& table; const HashTable<T, Key, Hash>& table;
public: public:
HashSorter(const HashTable<T,Key,Hash>& ht) HashSorter(const HashTable<T, Key, Hash>& ht)
: :
table(ht) table(ht)
{} {}

View File

@ -47,7 +47,7 @@ SourceFiles
namespace Foam namespace Foam
{ {
// Forward declarations // Forward Declarations
class bitSet; class bitSet;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -143,7 +143,11 @@ struct plusEqOp
//- List of values from HashTable, optionally sorted. //- List of values from HashTable, optionally sorted.
template<class T, class Key, class Hash> template<class T, class Key, class Hash>
List<T> values(const HashTable<T, Key, Hash>& tbl, const bool doSort=false) List<T> values
(
const HashTable<T, Key, Hash>& tbl,
const bool doSort=false
)
{ {
List<T> output(tbl.size()); List<T> output(tbl.size());

View File

@ -422,28 +422,28 @@ public:
//- Combine entries from HashSets //- Combine entries from HashSets
template<class Key, class Hash> template<class Key, class Hash>
HashSet<Key,Hash> operator| HashSet<Key, Hash> operator|
( (
const HashSet<Key,Hash>& hash1, const HashSet<Key, Hash>& hash1,
const HashSet<Key,Hash>& hash2 const HashSet<Key, Hash>& hash2
); );
//- Create a HashSet that only contains entries found in both HashSets //- Create a HashSet that only contains entries found in both HashSets
template<class Key, class Hash> template<class Key, class Hash>
HashSet<Key,Hash> operator& HashSet<Key, Hash> operator&
( (
const HashSet<Key,Hash>& hash1, const HashSet<Key, Hash>& hash1,
const HashSet<Key,Hash>& hash2 const HashSet<Key, Hash>& hash2
); );
//- Create a HashSet that only contains unique entries (xor) //- Create a HashSet that only contains unique entries (xor)
template<class Key, class Hash> template<class Key, class Hash>
HashSet<Key,Hash> operator^ HashSet<Key, Hash> operator^
( (
const HashSet<Key,Hash>& hash1, const HashSet<Key, Hash>& hash1,
const HashSet<Key,Hash>& hash2 const HashSet<Key, Hash>& hash2
); );

View File

@ -300,11 +300,12 @@ Foam::label Foam::HashTable<T, Key, Hash>::countEntries
template<class T, class Key, class Hash> template<class T, class Key, class Hash>
template<class... Args>
bool Foam::HashTable<T, Key, Hash>::setEntry bool Foam::HashTable<T, Key, Hash>::setEntry
( (
const bool overwrite,
const Key& key, const Key& key,
const T& obj, Args&&... args
const bool overwrite
) )
{ {
if (!capacity_) if (!capacity_)
@ -330,9 +331,10 @@ bool Foam::HashTable<T, Key, Hash>::setEntry
if (!curr) if (!curr)
{ {
// Not found, insert it at the head // Not found, insert it at the head
table_[index] = new node_type(key, obj, table_[index]); table_[index] =
++size_; new node_type(table_[index], key, std::forward<Args>(args)...);
++size_;
if (double(size_)/capacity_ > 0.8 && capacity_ < maxTableSize) if (double(size_)/capacity_ > 0.8 && capacity_ < maxTableSize)
{ {
#ifdef FULLDEBUG #ifdef FULLDEBUG
@ -360,7 +362,7 @@ bool Foam::HashTable<T, Key, Hash>::setEntry
// or that it behaves the same as a copy construct. // or that it behaves the same as a copy construct.
delete curr; delete curr;
ep = new node_type(key, obj, ep); ep = new node_type(ep, key, std::forward<Args>(args)...);
// Replace current element - within list or insert at the head // Replace current element - within list or insert at the head
if (prev) if (prev)
@ -838,7 +840,7 @@ bool Foam::HashTable<T, Key, Hash>::operator==
{ {
const const_iterator other(this->cfind(iter.key())); const const_iterator other(this->cfind(iter.key()));
if (!other.found() || other.val() != iter.val()) if (!other.good() || other.val() != iter.val())
{ {
return false; return false;
} }

View File

@ -97,7 +97,7 @@ SourceFiles
namespace Foam namespace Foam
{ {
// Forward declarations // Forward Declarations
template<class T> class List; template<class T> class List;
template<class T> class UList; template<class T> class UList;
@ -155,7 +155,8 @@ class HashTable
//- Assign a new hash-entry to a possibly already existing key. //- Assign a new hash-entry to a possibly already existing key.
// \return True if the new entry was set. // \return True if the new entry was set.
bool setEntry(const Key& key, const T& obj, const bool overwrite); template<class... Args>
bool setEntry(const bool overwrite, const Key& key, Args&&... args);
public: public:
@ -257,6 +258,12 @@ public:
//- Return true if the hash table is empty //- Return true if the hash table is empty
inline bool empty() const; inline bool empty() const;
//- Find and return a hashed entry. FatalError if it does not exist.
inline T& at(const Key& key);
//- Find and return a hashed entry. FatalError if it does not exist.
inline const T& at(const Key& key) const;
//- Return true if hashed entry is found in table //- Return true if hashed entry is found in table
inline bool found(const Key& key) const; inline bool found(const Key& key) const;
@ -361,16 +368,27 @@ public:
// Edit // Edit
//- Insert a new entry, not overwriting existing entries. //- Emplace insert a new entry, not overwriting existing entries.
// \return True if the entry inserted, which means that it did // \return True if the entry did not previously exist in the table.
// not previously exist in the table. template<class... Args>
inline bool emplace(const Key& key, Args&&... args);
//- Copy insert a new entry, not overwriting existing entries.
// \return True if the entry did not previously exist in the table.
inline bool insert(const Key& key, const T& obj); inline bool insert(const Key& key, const T& obj);
//- Assign a new entry, overwriting existing entries. //- Move insert a new entry, not overwriting existing entries.
// // \return True if the entry did not previously exist in the table.
inline bool insert(const Key& key, T&& obj);
//- Copy assign a new entry, overwriting existing entries.
// \return True, since it always overwrites any entries. // \return True, since it always overwrites any entries.
inline bool set(const Key& key, const T& obj); inline bool set(const Key& key, const T& obj);
//- Move assign a new entry, overwriting existing entries.
// \return True, since it always overwrites any entries.
inline bool set(const Key& key, T&& obj);
//- Erase an entry specified by given iterator //- Erase an entry specified by given iterator
// This invalidates the iterator until the next ++ operation. // This invalidates the iterator until the next ++ operation.
// //
@ -513,20 +531,20 @@ public:
inline T& operator()(const Key& key, const T& deflt); inline T& operator()(const Key& key, const T& deflt);
//- Copy assign //- Copy assign
void operator=(const HashTable<T, Key, Hash>& rhs); void operator=(const this_type& rhs);
//- Copy assign from an initializer list //- Copy assign from an initializer list
void operator=(std::initializer_list<std::pair<Key, T>> rhs); void operator=(std::initializer_list<std::pair<Key, T>> rhs);
//- Move assign //- Move assign
void operator=(HashTable<T, Key, Hash>&& rhs); void operator=(this_type&& rhs);
//- Equality. Tables are equal if all keys and values are equal, //- Equality. Tables are equal if all keys and values are equal,
//- independent of order or underlying storage size. //- independent of order or underlying storage size.
bool operator==(const HashTable<T, Key, Hash>& rhs) const; bool operator==(const this_type& rhs) const;
//- The opposite of the equality operation. //- The opposite of the equality operation.
bool operator!=(const HashTable<T, Key, Hash>& rhs) const; bool operator!=(const this_type& rhs) const;
//- Add entries into this HashTable //- Add entries into this HashTable
this_type& operator+=(const this_type& rhs); this_type& operator+=(const this_type& rhs);
@ -584,8 +602,7 @@ protected:
// This can be used directly instead of comparing to end() // This can be used directly instead of comparing to end()
inline bool good() const; inline bool good() const;
//- True if iterator points to an entry //- True if iterator points to an entry - same as good()
// This can be used directly instead of comparing to end()
inline bool found() const; inline bool found() const;
//- The key associated with the iterator //- The key associated with the iterator
@ -703,7 +720,7 @@ public:
{} {}
// Member functions/operators // Member Functions/Operators
//- Non-const access to referenced object (value) //- Non-const access to referenced object (value)
using Iterator<false>::val; using Iterator<false>::val;
@ -719,7 +736,7 @@ public:
template<class TypeT = T> template<class TypeT = T>
typename std::enable_if typename std::enable_if
< <
std::is_pointer<TypeT>::value, Detail::isPointer<TypeT>::value,
T T
>::type operator->() const { return this->val(); } >::type operator->() const { return this->val(); }
@ -773,7 +790,7 @@ public:
{} {}
// Member functions/operators // Member Functions/Operators
//- Const access to referenced value //- Const access to referenced value
using Iterator<true>::val; using Iterator<true>::val;
@ -789,7 +806,7 @@ public:
template<class TypeT = T> template<class TypeT = T>
typename std::enable_if typename std::enable_if
< <
std::is_pointer<TypeT>::value, Detail::isPointer<TypeT>::value,
const T const T
>::type operator->() const { return this->val(); } >::type operator->() const { return this->val(); }

View File

@ -36,6 +36,7 @@ SourceFiles
#define HashTableDetail_H #define HashTableDetail_H
#include "zero.H" #include "zero.H"
#include <memory>
#include <utility> #include <utility>
#include <type_traits> #include <type_traits>
@ -44,14 +45,32 @@ SourceFiles
namespace Foam namespace Foam
{ {
// Forward declarations // Forward Declarations
class Ostream; class Ostream;
template<class T> class autoPtr;
namespace Detail namespace Detail
{ {
/*---------------------------------------------------------------------------*\ /*---------------------------------------------------------------------------*\
Class Detail::HashTablePair Declaration Class isPointer Declaration
\*---------------------------------------------------------------------------*/
//- Test for pointer-like behaviour
template<class T>
struct isPointer : public std::is_pointer<T> {};
//- An autoPtr is pointer-like
template<class T>
struct isPointer<autoPtr<T>> : public std::true_type {};
//- A unique_ptr is pointer-like
template<class T>
struct isPointer<std::unique_ptr<T>> : public std::true_type {};
/*---------------------------------------------------------------------------*\
Class HashTablePair Declaration
\*---------------------------------------------------------------------------*/ \*---------------------------------------------------------------------------*/
//- Internal storage type for HashTable entries //- Internal storage type for HashTable entries
@ -97,16 +116,17 @@ struct HashTablePair
void operator=(const HashTablePair&) = delete; void operator=(const HashTablePair&) = delete;
//- Construct from key, value, next pointer //- Construct from next pointer, key, contents
template<class... Args>
HashTablePair HashTablePair
( (
HashTablePair* next,
const key_type& key, const key_type& key,
const mapped_type& val, Args&&... args
HashTablePair* next
) )
: :
key_(key), key_(key),
val_(val), val_(std::forward<Args>(args)...),
next_(next) next_(next)
{} {}
@ -131,7 +151,7 @@ struct HashTablePair
//- Write (key, val) pair - for pointer types //- Write (key, val) pair - for pointer types
template<class TypeT = T> template<class TypeT = T>
typename std::enable_if<std::is_pointer<TypeT>::value, void>::type typename std::enable_if<Detail::isPointer<TypeT>::value, void>::type
print(Ostream& os) const print(Ostream& os) const
{ {
os << key_; os << key_;
@ -144,7 +164,7 @@ struct HashTablePair
//- Write (key, val) pair - for non-pointer types //- Write (key, val) pair - for non-pointer types
template<class TypeT = T> template<class TypeT = T>
typename std::enable_if<!std::is_pointer<TypeT>::value, void>::type typename std::enable_if<!Detail::isPointer<TypeT>::value, void>::type
print(Ostream& os) const print(Ostream& os) const
{ {
os << key_ << ' ' << val_; os << key_ << ' ' << val_;
@ -153,7 +173,7 @@ struct HashTablePair
/*---------------------------------------------------------------------------*\ /*---------------------------------------------------------------------------*\
Class Detail::HashTableSingle Declaration Class HashTableSingle Declaration
\*---------------------------------------------------------------------------*/ \*---------------------------------------------------------------------------*/
//- Internal storage type for HashSet entries //- Internal storage type for HashSet entries
@ -196,12 +216,13 @@ struct HashTableSingle
void operator=(const HashTableSingle&) = delete; void operator=(const HashTableSingle&) = delete;
//- Construct from key, (ununsed) value, next pointer //- Construct from next pointer, key, (ununsed) contents
template<class... Args>
HashTableSingle HashTableSingle
( (
HashTableSingle* next,
const key_type& key, const key_type& key,
const mapped_type&, Args&&...
HashTableSingle* next
) )
: :
key_(key), key_(key),

View File

@ -61,12 +61,46 @@ inline bool Foam::HashTable<T, Key, Hash>::empty() const
} }
template<class T, class Key, class Hash>
inline T& Foam::HashTable<T, Key, Hash>::at(const Key& key)
{
const iterator iter(this->find(key));
if (!iter.good())
{
FatalErrorInFunction
<< key << " not found in table. Valid entries: "
<< toc()
<< exit(FatalError);
}
return iter.val();
}
template<class T, class Key, class Hash>
inline const T& Foam::HashTable<T, Key, Hash>::at(const Key& key) const
{
const const_iterator iter(this->cfind(key));
if (!iter.good())
{
FatalErrorInFunction
<< key << " not found in table. Valid entries: "
<< toc()
<< exit(FatalError);
}
return iter.val();
}
template<class T, class Key, class Hash> template<class T, class Key, class Hash>
inline bool Foam::HashTable<T, Key, Hash>::found(const Key& key) const inline bool Foam::HashTable<T, Key, Hash>::found(const Key& key) const
{ {
if (size_) if (size_)
{ {
return Iterator<true>(this, key).found(); return Iterator<true>(this, key).good();
} }
return false; return false;
@ -116,6 +150,18 @@ Foam::HashTable<T, Key, Hash>::cfind
} }
template<class T, class Key, class Hash>
template<class... Args>
inline bool Foam::HashTable<T, Key, Hash>::emplace
(
const Key& key,
Args&&... args
)
{
return this->setEntry(false, key, std::forward<Args>(args)...);
}
template<class T, class Key, class Hash> template<class T, class Key, class Hash>
inline bool Foam::HashTable<T, Key, Hash>::insert inline bool Foam::HashTable<T, Key, Hash>::insert
( (
@ -123,7 +169,18 @@ inline bool Foam::HashTable<T, Key, Hash>::insert
const T& val const T& val
) )
{ {
return this->setEntry(key, val, false); // No overwrite return this->setEntry(false, key, val);
}
template<class T, class Key, class Hash>
inline bool Foam::HashTable<T, Key, Hash>::insert
(
const Key& key,
T&& val
)
{
return this->setEntry(false, key, std::forward<T>(val));
} }
@ -134,7 +191,18 @@ inline bool Foam::HashTable<T, Key, Hash>::set
const T& val const T& val
) )
{ {
return this->setEntry(key, val, true); // Overwrite return this->setEntry(true, key, val); // Overwrite
}
template<class T, class Key, class Hash>
inline bool Foam::HashTable<T, Key, Hash>::set
(
const Key& key,
T&& val
)
{
return this->setEntry(true, key, std::forward<T>(val)); // Overwrite
} }
@ -146,7 +214,7 @@ inline const T& Foam::HashTable<T, Key, Hash>::lookup
) const ) const
{ {
const const_iterator iter(this->cfind(key)); const const_iterator iter(this->cfind(key));
return iter.found() ? iter.val() : deflt; return iter.good() ? iter.val() : deflt;
} }
@ -157,7 +225,7 @@ inline T& Foam::HashTable<T, Key, Hash>::operator[](const Key& key)
{ {
const iterator iter(this->find(key)); const iterator iter(this->find(key));
if (!iter.found()) if (!iter.good())
{ {
FatalErrorInFunction FatalErrorInFunction
<< key << " not found in table. Valid entries: " << key << " not found in table. Valid entries: "
@ -174,7 +242,7 @@ inline const T& Foam::HashTable<T, Key, Hash>::operator[](const Key& key) const
{ {
const const_iterator iter(this->cfind(key)); const const_iterator iter(this->cfind(key));
if (!iter.found()) if (!iter.good())
{ {
FatalErrorInFunction FatalErrorInFunction
<< key << " not found in table. Valid entries: " << key << " not found in table. Valid entries: "
@ -191,7 +259,7 @@ inline T& Foam::HashTable<T, Key, Hash>::operator()(const Key& key)
{ {
const iterator iter(this->find(key)); const iterator iter(this->find(key));
if (iter.found()) if (iter.good())
{ {
return iter.val(); return iter.val();
} }
@ -210,7 +278,7 @@ inline T& Foam::HashTable<T, Key, Hash>::operator()
{ {
const iterator iter(this->find(key)); const iterator iter(this->find(key));
if (iter.found()) if (iter.good())
{ {
return iter.val(); return iter.val();
} }

View File

@ -39,7 +39,7 @@ bool Foam::HashTable<T, Key, Hash>::addEntry(Istream& is, const bool overwrite)
is >> key >> val; is >> key >> val;
const bool ok = this->setEntry(key, val, overwrite); const bool ok = this->setEntry(overwrite, key, val);
is.fatalCheck is.fatalCheck
( (