// bdlcc_skiplist.h -*-C++-*- // ---------------------------------------------------------------------------- // NOTICE // // This component is not up to date with current BDE coding standards, and // should not be used as an example for new development. // ---------------------------------------------------------------------------- #ifndef INCLUDED_BDLCC_SKIPLIST #define INCLUDED_BDLCC_SKIPLIST #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a generic thread-safe Skip List. // //@CLASSES: // bdlcc::SkipList: generic thread-aware ordered map // bdlcc::SkipListPair: type for opaque pointers // bdlcc::SkipListPairHandle: scope mechanism for safe item references // //@SEE_ALSO: // //@DESCRIPTION: This component provides a thread-safe value-semantic // associative Skip List container. A Skip List stores objects of a // parameterized 'DATA' type, ordered by values of a parameterized 'KEY' type. // 'DATA' objects can be added, looked up, and removed quickly on the basis of // their 'KEY' value. In addition, 'bdlcc::SkipList' provides methods to // change the 'KEY' value associated with an object in the list such that it is // efficiently moved to an appropriate location within the list for the new // 'KEY' value. // // Associations (pairings of data objects with key values) in the list are // identified by 'bdlcc::SkipListPairHandle' objects or 'bdlcc::SkipListPair' // pointers. 'bdlcc::SkipListPair' pointers must be used with caution: See the // "'bdlcc::SkipListPair' Usage Rules" below. 'bdlcc::SkipListPair' and // 'bdlcc::SkipListPairHandle' objects are optionally populated when new // associations are added, and are also populated whenever associations are // looked up (either by key or by position). Note that in addition to // 'addPairReferenceRaw', member functions of 'bdlcc::SkipList' such as // 'front', 'back', and 'find' also add a reference to the specified element. // ///Template Requirements ///--------------------- // The 'bdlcc::SkipList' ordered associative container is parameterized on two // types, 'KEY' and 'DATA'. Each type must have a public copy constructor, and // it is important to declare the "Uses bslma Allocator" trait if the type // accepts a 'bslma::Allocator' in its constructor (see 'bslalg_typetraits'). // In addition, operators '=', '<', and '==' must be defined for the type // 'KEY'; for correct behavior, operator '<' must define a Strict Weak Ordering // on 'KEY' values. // ///Glossary ///-------- // Some terms used frequently in this documentation: // //: Back: //: The last element in the list. The key value at the back is greater //: than or equal to every other key value in the list. //: //: Front: //: The beginning of the list. The key value at the front is less than or //: equal to every other key value in the list. //: //: Pair: //: An element of the list; a pairing (association) of a data object with a //: key value. Also a type name used for *references* to such objects //: ('bdlcc::SkipListPair' objects cannot be constructed directly). //: //: PairHandle: //: An object (of type 'bdlcc::SkipListPairHandle') with scope and copy //: semantics that makes it easier to manage and use than a raw //: 'bdlcc::SkipListPair *'. //: //: R: //: Stands for "Reverse search" (see '"R" Methods' documentation below). //: //: Reference: //: An object referring to a pair; either a 'bdlcc::SkipListPair *' which //: has not yet been released, or a 'bdlcc::SkipListPairHandle' object. // ///"R" Methods: Optimized Search From The Back Of The List ///------------------------------------------------------- // The regular methods (no R suffix) of 'bdlcc::SkipList' that result in a // search through the list, search from the front of the list (i.e., in // ascending order). // // All methods of 'bdlcc::SkipList' that result in a search through the list // have corresponding "R" versions: for example, there are 'add' and 'addR' // methods, 'find' and 'findR' methods, etc. The "R" versions of these methods // search from the back of the list (i.e., in descending (reverse) order). Use // of an "R" method is a hint to the Skip List that the desired key is more // likely to be near the back than the front. In the event of duplicate keys, // 'find' will find the first matching key, and 'findR' will find the last // matching key. Note that if there are pairs in the list with duplicate keys, // the specific pair found by 'find' may (or may not) be different from the one // found by 'findR'. // ///Referring to Elements in the Container ///-------------------------------------- // 'bdlcc::SkipList' has two 'handle' types for referring to elements in the // container: // //: o 'bdlcc::SkipList::Pair *' -- raw pointer, no destructor, //: 'bdlcc::SkipList::releaseReferenceRaw' must be called on these pointers //: before the container is destroyed. //: //: o 'bdlcc::SkipList::PairHandle' -- 'class', has a destructor which will //: release the pair handle when it goes out of scope via RAII. If the pair //: handle will not be destroyed before the container is, it is necessary to //: call 'bdlcc::SkipList::PairHandle::release' before the container is //: destroyed. // // The 'PairHandle' type has an implicit conversion to 'Pair *'. In most cases // 'bdlcc::SkipList' provides dual functions supporting 'Pair *' and // 'PairHandle'. Some functions, however, only support 'Pair *' parameters; // for these functions, either a 'Pair *' or a 'PairHandle' may be passed. // // Unless the client has some reason to prefer the 'Pair *' interface, the // 'PairHandle' interface is recommended since it provides RAII, making it // harder to leak nodes. // // Note that in some build modes, 'SkipList' will attempt to detect leaked // nodes, i.e., those that were referred to by 'Pair *'s for which // 'releaseReferenceRaw' hasn't been called, and nodes referred to by // 'PairHandle's that haven't been destroyed or 'release'd at the time of the // skip list's destruction. // ///Thread Safety ///------------- // 'bdlcc::SkipList' is thread-safe and thread-aware; that is, multiple threads // may use their own Skip List objects or may concurrently use the same object. // // Note that safe usage of the component depends upon correct usage of // 'bdlcc::SkipListPair' objects (see above). // // 'bdlcc::SkipListPairHandle' is only *const* *thread-safe*. It is not safe // for multiple threads to invoke non-'const' methods on the same 'PairHandle' // object concurrently. // // 'bdlcc::SkipListPair' is a name used for opaque pointers; the concept of // thread safety does not apply to it. // ///Exception Safety ///---------------- // 'bdlcc::SkipList' is exception-neutral: no method invokes 'throw' or // 'catch'. Insertion methods ('add', 'addR', etc) invoke the copy // constructors of the contained 'KEY' and 'DATA' types; if those constructors // throw an exception, the list provides a full rollback guarantee (it will // have the same state it had prior to the call to 'add'). The assignment // operator may also indirectly cause 'bad_alloc' to be thrown if the system is // out of memory, but in that case there is *no* guarantee of rollback on the // left-hand list. // // No method of 'bdlcc::SkipListPairHandle' can throw. // // 'bdlcc::SkipListPair' is only a name used for opaque pointers; the concept // of exception safety does not apply to it. // ///Usage ///----- // This section illustrates intended use of this component. // ///Example 1: Creating a Scheduler ///- - - - - - - - - - - - - - - - // The "R" methods of 'bdlcc::SkipList' make it ideal for use in a scheduler, // in which events are likely to be scheduled after existing events. In such // an implementation, events are stored in the list with their scheduled // execution times as 'KEY' objects: Searching near the end of the list for the // right location for new events, and removing events from the front of the // list for execution, are very efficient operations. Being thread- enabled // also makes 'bdlcc::SkipList' well-suited to use in a scheduler - a // "dispatcher" thread can safety use the list at the same time that events are // being scheduled from other threads. The following is an implementation of a // simple scheduler class using 'bdlcc::SkipList'. Note that the mutex in the // scheduler is used only in connection with the scheduler's condition variable // - thread-safe access to the 'bdlcc::SkipList' object does *not* require any // synchronization. //.. // class SimpleScheduler // { // // TYPES // typedef bdlcc::SkipList<bdlt::Datetime, bsl::function<void()> > List; // // // DATA // List d_list; // bslmt::ThreadUtil::Handle d_dispatcher; // bslmt::Condition d_notEmptyCond; // bslmt::Condition d_emptyCond; // bslmt::Barrier d_startBarrier; // bslmt::Mutex d_condMutex; // bsls::AtomicInt d_doneFlag; // // private: // // NOT IMPLEMENTED // SimpleScheduler(const SimpleScheduler&); // // private: // // PRIVATE MANIPULATORS // void dispatcherThread() // // Run a thread that executes functions off 'd_list'. // { // d_startBarrier.wait(); // // while (!d_doneFlag) { // List::PairHandle firstItem; // if (0 == d_list.front(&firstItem)) { // // The list is not empty. // // bsls::TimeInterval when = // bdlt::IntervalConversionUtil::convertToTimeInterval( // firstItem.key() - bdlt::CurrentTime::utc()); // if (when.totalSecondsAsDouble() <= 0) { // // Execute now and remove from schedule, then iterate. // // d_list.remove(firstItem); // firstItem.data()(); // // List::PairHandle tmpItem; // // bslmt::LockGuard<bslmt::Mutex> guard(&d_condMutex); // // if (0 == d_list.length()) { // d_emptyCond.broadcast(); // } // } // else { // // Wait until the first scheduled item is due. // // bslmt::LockGuard<bslmt::Mutex> guard(&d_condMutex); // List::PairHandle newFirst; // if (!d_doneFlag && (0 != d_list.front(&newFirst) || // newFirst.key() == firstItem.key())) { // d_notEmptyCond.timedWait(&d_condMutex, // bdlt::CurrentTime::now() + when); // } // } // } // else { // // The list is empty; wait on the condition variable. // // bslmt::LockGuard<bslmt::Mutex> guard(&d_condMutex); // if (d_list.isEmpty() && !d_doneFlag) { // d_notEmptyCond.wait(&d_condMutex); // } // } // } // } // // public: // // CREATORS // explicit // SimpleScheduler(bslma::Allocator *basicAllocator = 0) // : d_list(basicAllocator) // , d_startBarrier(2) // , d_doneFlag(false) // // Creator. // { // int rc = bslmt::ThreadUtil::create( // &d_dispatcher, // bdlf::BindUtil::bind(&SimpleScheduler::dispatcherThread, // this)); // BSLS_ASSERT(0 == rc); (void)rc; // d_startBarrier.wait(); // } // // ~SimpleScheduler() // // d'tor // { // stop(); // } // // // MANIPULATORS // void drain() // // Block until the scheduler has no jobs. // { // bslmt::LockGuard<bslmt::Mutex> guard(&d_condMutex); // // while (!d_doneFlag && 0 != d_list.length()) { // d_emptyCond.wait(&d_condMutex); // } // } // // void scheduleEvent(const bsl::function<void()>& event, // const bdlt::Datetime& when) // // Schedule the specified 'event' to occur at the specified 'when'. // { // // Use 'addR' since this event will probably be placed near the end // // of the list. // // bool newFrontFlag; // d_list.addR(when, event, &newFrontFlag); // if (newFrontFlag) { // // This event is scheduled before all other events. Wake up // // the dispatcher thread. // // d_notEmptyCond.signal(); // } // } // // void stop() // // Stop the scheduler. // { // bslmt::LockGuard<bslmt::Mutex> guard(&d_condMutex); // // d_list.removeAll(); // // d_doneFlag = true; // d_notEmptyCond.signal(); // d_emptyCond.broadcast(); // // if (bslmt::ThreadUtil::invalidHandle() != d_dispatcher) { // bslmt::ThreadUtil::Handle dispatcher = d_dispatcher; // { // bslmt::LockGuardUnlock<bslmt::Mutex> g(&d_condMutex); // bslmt::ThreadUtil::join(dispatcher); // } // d_dispatcher = bslmt::ThreadUtil::invalidHandle(); // } // } // }; //.. // We can verify the correct behavior of 'SimpleScheduler'. First, we need a // wrapper around vector<int>::push_back, since this function is overloaded and // cannot be bound directly: //.. // void pushBackWrapper(bsl::vector<int> *vector, int item) // // Push the specified 'item' onto the specified 'vector'. // { // vector->push_back(item); // } //.. // Now, in 'main', verify that the scheduler executes events when expected: //.. // SimpleScheduler scheduler; // // bsl::vector<int> values; // // const bdlt::Datetime start = bdlt::CurrentTime::utc(); // bdlt::Datetime scheduleTime; //.. // Add events out of sequence and ensure they are executed in the proper order. //.. // scheduleTime = start; // scheduleTime.addMilliseconds(2250); // scheduler.scheduleEvent(bdlf::BindUtil::bind(&pushBackWrapper, &values, 2), // scheduleTime); // // scheduleTime = start; // scheduleTime.addMilliseconds(750); // scheduler.scheduleEvent(bdlf::BindUtil::bind(&pushBackWrapper, &values, 0), // scheduleTime); // // scheduleTime = start; // scheduleTime.addMilliseconds(1500); // scheduler.scheduleEvent(bdlf::BindUtil::bind(&pushBackWrapper, &values, 1), // scheduleTime); // // assert(values.empty()); // // scheduler.drain(); // // bdlt::Datetime finish = bdlt::CurrentTime::utc(); // // assert(3 == values.size()); // assert(0 == values[0]); // assert(1 == values[1]); // assert(2 == values[2]); // // const double elapsed = bdlt::IntervalConversionUtil::convertToTimeInterval( // finish - start).totalSecondsAsDouble(); // // assert(2.25 <= elapsed); // assert(elapsed < 2.75); //.. // Note that the destructor of 'scheduler' will call 'stop()'. #include <bdlscm_version.h> #include <bdlb_print.h> #include <bdlb_printmethods.h> #include <bslmt_mutexassert.h> #include <bslmt_lockguard.h> #include <bslmt_condition.h> #include <bslmt_mutex.h> #include <bslmt_threadutil.h> #include <bslalg_scalarprimitives.h> #include <bslma_allocator.h> #include <bslma_default.h> #include <bslma_usesbslmaallocator.h> #include <bslmf_conditional.h> #include <bslmf_nestedtraitdeclaration.h> #include <bsls_alignmentfromtype.h> #include <bsls_assert.h> #include <bsls_atomic.h> #include <bsls_keyword.h> #include <bsls_libraryfeatures.h> #include <bsls_review.h> #include <bsls_types.h> #include <bsls_util.h> #include <bsl_algorithm.h> #include <bsl_functional.h> #include <bsl_ostream.h> #include <bsl_vector.h> #ifndef BDE_DONT_ALLOW_TRANSITIVE_INCLUDES #include <bslalg_typetraits.h> #endif // BDE_DONT_ALLOW_TRANSITIVE_INCLUDES #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR # include <memory_resource> #endif #include <vector> namespace BloombergLP { namespace bdlcc { template <class KEY, class DATA> class SkipList; // ========================= // local class SkipList_Node // ========================= template<class KEY, class DATA> struct SkipList_Node { // This component-private structure is a node in the SkipList. // TYPES typedef SkipList_Node<KEY, DATA> Node; struct Ptrs { // PUBLIC DATA Node *d_next_p; Node *d_prev_p; }; // PUBLIC DATA bsls::AtomicInt d_refCount; int d_level; // values in range '[ 0 .. 31 ]' DATA d_data; KEY d_key; Ptrs d_ptrs[1]; // Must be last; each node has space for // extra 'Ptrs' allocated based on its // level. }; // ==================================== // local class SkipList_DoubleLockGuard // ==================================== class SkipList_DoubleLockGuard { // DATA bslmt::LockGuard<bslmt::Mutex> d_firstGuard, d_lastGuard; public: // CREATOR SkipList_DoubleLockGuard(bslmt::Mutex *lock1, bslmt::Mutex *lock2); // Lock both 'lock1' and 'lock2', the one in the lower memory location // first. }; // ========================================= // local class SkipList_RandomLevelGenerator // ========================================= class SkipList_RandomLevelGenerator { // This component-private class handles randomizing the levelization of // list nodes. // PRIVATE TYPES enum { k_MAX_LEVEL = 31, // Also defined in SkipList and // PoolManager k_SEED = 0x12b9b0a1 // arbitrary #ifndef BDE_OMIT_INTERNAL_DEPRECATED , BCEC_MAX_LEVEL = k_MAX_LEVEL , BCEC_SEED = k_SEED #endif // BDE_OMIT_INTERNAL_DEPRECATED }; // DATA bsls::AtomicInt d_seed; // current random seed bsls::AtomicInt d_randomBits; // 14 random bits and a sentinel bit at the // 15th position public: // CREATORS SkipList_RandomLevelGenerator(); // Construct a thread-aware random-level generator. // MANIPULATORS int randomLevel(); // Return a random integer between 0 and k_MAX_LEVEL. }; // ==================================== // local class bdlcc::SkipList_PoolUtil // ==================================== class SkipList_PoolManager; struct SkipList_PoolUtil { // This component-private utility handles the lock-free pool of list nodes. // TYPES typedef SkipList_PoolManager PoolManager; // CLASS METHODS static void *allocate(PoolManager *poolManager, int level); // Reserve sufficient space for a node at the specified 'level' from // the specified 'poolManager', and return the address of the reserved // memory. static PoolManager *createPoolManager(int *objectSizes, int numLevels, bslma::Allocator *basicAllocator); // Create a new pooled node allocator that manages nodes up to the // specified 'numLevels' as described by the specified 'objectSizes'. // For 'i' in '[0, numLevels)', a node at level 'i' will have size // 'objectSizes[i]' bytes. Use the specified 'basicAllocator' to // supply memory. Return the address of the new allocator. Note that // the behavior is undefined if 'basicAllocator' is 0. static void deallocate(PoolManager *poolManager, void *address); // Return the node at the specified 'address' to the specified // 'poolManager'. The behavior is undefined if 'address' was not // allocated from 'poolManager'. static void deletePoolManager(bslma::Allocator *basicAllocator, PoolManager *poolManager); // Destroy the specified 'poolManager' which was allocated from the // specified 'basicAllocator'. The behavior is undefined if // 'poolManager' was not allocated from 'basicAllocator'. }; // ======================================= // local class SkipList_NodeCreationHelper // ======================================= template<class KEY, class DATA> class SkipList_NodeCreationHelper { // This component-private structure is a scoped guard that initializes new // nodes and releases them in case of exception. // PRIVATE TYPES typedef SkipList_PoolManager PoolManager; typedef SkipList_PoolUtil PoolUtil; typedef SkipList_Node<KEY, DATA> Node; // DATA Node *d_node_p; // the node, or 0 if no managed node PoolManager *d_poolManager_p; // pool from which node was allocated bool d_keyFlag; // 'true' if the key was constructed bslma::Allocator *d_allocator_p; // held BSLMF_NESTED_TRAIT_DECLARATION(SkipList_NodeCreationHelper, bslma::UsesBslmaAllocator); public: // CREATORS SkipList_NodeCreationHelper(PoolManager *poolManager, Node *node, bslma::Allocator *basicAllocator = 0); // Create a new scoped guard object to assist in exception-safe // initialization of the specified 'node', which was allocated from the // specified 'poolManager'. Optionally specify a 'basicAllocator' // used to supply memory. If 'basicAllocator' is 0, the currently // installed default allocator is used. ~SkipList_NodeCreationHelper(); // Destroy this scoped guard. If the guard currently manages a node, // destroy its data as necessary and return it to the pool. // MANIPULATORS void construct(const KEY& key, const DATA& data); // Attempt to copy-construct the specified 'key' and 'data' into the // node specified at construction; then release the node from // management. Note that if an exception is thrown during the // invocation of either constructor, the node will remain under // management and thus the destructor of this object will do the // appropriate cleanup. The behavior is undefined if 'construct' has // already been invoked on this scoped guard object. }; // ================== // class SkipListPair // ================== template <class KEY, class DATA> class SkipListPair { // Pointers to objects of this class are used in the "raw" API of // 'SkipList'; however, objects of the class are never constructed as the // class serves only to provide type-safe pointers. // // In addition, this class defines 'key' and 'data' member functions that // pass 'this' to static methods of 'SkipList'. // Note these data elements are never accessed. A pointer to this type // will be cast to a pointer to 'SkipList_Node' so make sure we are // adequately aligned to avoid compiler warnings. // DATA SkipList_Node<KEY, DATA> d_node; // never directly accessed private: // NOT IMPLEMENTED SkipListPair(); SkipListPair(const SkipListPair&); SkipListPair& operator=(const SkipListPair&); public: // ACCESSORS DATA& data() const; // Return a reference to the modifiable "data" of this pair. const KEY& key() const; // Return a reference to the non-modifiable "key" value of this pair. }; // ======================== // class SkipListPairHandle // ======================== template <class KEY, class DATA> class SkipListPairHandle { // Objects of this class refer to an association (pair) in a 'SkipList'. A // 'bdlcc::SkipListPairHandle' is implicitly convertible to a 'const Pair*' // and thus may be used anywhere in the 'SkipList' API that a 'const Pair*' // is expected. // PRIVATE TYPES typedef SkipListPair<KEY, DATA> Pair; // DATA SkipList<KEY, DATA> *d_list_p; Pair *d_node_p; // FRIENDS friend class SkipList<KEY, DATA>; private: // PRIVATE CREATORS SkipListPairHandle(SkipList<KEY, DATA> *list, Pair *reference); // Construct a new pair handle for the specified 'list' that manages // the specified 'reference'. Note that it is assumed that the // creating (calling) scope already owns the 'reference'. // PRIVATE MANIPULATORS void reset(const SkipList<KEY, DATA> *list, Pair *reference); // Change this 'SkipListPairHandle' to refer to manage the specified // 'reference' in the specified 'list'. If this 'SkipListPairHandle' // refers to a pair, release the reference. Note that it is assumed // that the calling scope already owns the 'reference'. public: // CREATORS SkipListPairHandle(); // Construct a new PairHandle that does not refer to a pair. SkipListPairHandle(const SkipListPairHandle& original); // Construct a new pair reference for the same list and pair as the // specified 'original'. ~SkipListPairHandle(); // Destroy this 'SkipListPairHandle'. If this 'SkipListPairHandle' // refers to a pair in the list, release the reference. // MANIPULATORS SkipListPairHandle& operator=(const SkipListPairHandle& rhs); // Change this 'SkipListPairHandle' to refer to the same list and pair // as the specified 'rhs'. If this 'SkipListPairHandle' initially // refers to a pair, release the reference. Return '*this'. void release(); // Release the reference (if any) managed by this 'SkipListPairHandle'. void releaseReferenceRaw(SkipList<KEY, DATA> **list, Pair **reference); // Invoke 'release' and populate the specified 'list' and 'reference' // pointers with the list and reference values of this // 'SkipListPairHandle'. // ACCESSORS operator const Pair*() const; // Return the address of the pair referred to by this // 'SkipListPairHandle', or 0 if this handle does not manage a // reference. DATA& data() const; // Return a reference to the "data" value of the pair referred to by // this object. The behavior is undefined unless 'isValid' returns // 'true'. const KEY& key() const; // Return a reference to the non-modifiable "key" value of the pair // referred to by this object. The behavior is undefined unless // 'isValid' returns 'true'. bool isValid() const; // Return 'true' if this PairHandle currently refers to a pair, and // 'false' otherwise. }; // ============== // class SkipList // ============== template<class KEY, class DATA> class SkipList { // This class provides a generic thread-safe Skip List (an ordered // associative container). It supports an almost complete set of *value* // *semantic* operations, including copy construction, assignment, equality // comparison, and 'ostream' printing (but not BDEX serialization). public: // CONSTANTS enum { e_SUCCESS = 0, e_NOT_FOUND = 1, e_DUPLICATE = 2, e_INVALID = 3 #ifndef BDE_OMIT_INTERNAL_DEPRECATED , BCEC_SUCCESS = e_SUCCESS , BCEC_NOT_FOUND = e_NOT_FOUND , BCEC_DUPLICATE = e_DUPLICATE , BCEC_INVALID = e_INVALID , RET_SUCCESS = e_SUCCESS , RET_NOT_FOUND = e_NOT_FOUND , RET_DUPLICATE = e_DUPLICATE , RET_INVALID = e_INVALID #endif // BDE_OMIT_INTERNAL_DEPRECATED }; // TYPES typedef SkipListPair<KEY, DATA> Pair; typedef SkipListPairHandle<KEY, DATA> PairHandle; private: // PRIVATE TYPES typedef SkipList_PoolManager PoolManager; typedef SkipList_PoolUtil PoolUtil; typedef SkipList_Node<KEY, DATA> Node; typedef SkipList_NodeCreationHelper<KEY, DATA> NodeGuard; typedef bslmt::Mutex Lock; typedef bslmt::LockGuard<bslmt::Mutex> LockGuard; typedef SkipList_DoubleLockGuard DoubleLockGuard; template <class VECTOR, class VALUE_TYPE> class IsVector; class PairFactory; class PairHandleFactory; // PRIVATE CONSTANTS enum { k_MAX_NUM_LEVELS = 32, // Also defined in RandomLevelGenerator // and PoolManager k_MAX_LEVEL = 31 }; // DATA SkipList_RandomLevelGenerator d_rand; bsls::AtomicInt d_listLevel; Node *d_head_p; Node *d_tail_p; mutable Lock d_lock; int d_length; PoolManager *d_poolManager_p; // owned bslma::Allocator *d_allocator_p; // held // FRIENDS friend class SkipListPair<KEY, DATA>; friend class SkipListPairHandle<KEY, DATA>; template <class KEY2, class DATA2> friend bool operator==(const SkipList<KEY2, DATA2>&, const SkipList<KEY2, DATA2>&); template <class KEY2, class DATA2> friend bool operator!=(const SkipList<KEY2, DATA2>&, const SkipList<KEY2, DATA2>&); // PRIVATE CLASS METHODS static DATA& data(const Pair *reference); // Return a non-'const' reference to the "data" value of the pair // identified by the specified 'reference'. static const KEY& key(const Pair *reference); // Return a 'const' reference to the "key" value of the pair identified // by the specified 'reference'. static inline BSLS_KEYWORD_CONSTEXPR bsls::Types::IntPtr offsetOfPtrs(); // Return the offset in bytes of 'd_ptrs' from the start of the // 'SkipList_Node' struct. (similar to // 'offsetof(SkipList_Node, d_ptrs)' but with no requirement that // 'DATA' or 'KEY' be PODs) static Node *pairToNode(Pair *reference); // Cast the specified 'reference' to a 'Node *'. static Node *pairToNode(const Pair *reference); // Const-cast the specified 'reference' to a 'Node *'. // PRIVATE MANIPULATORS void addNode(bool *newFrontFlag, Node *newNode); // Acquire the lock, add the specified 'newNode' to the list, and // release the lock. If the specified 'newFrontFlag' is not 0, load // into it a 'true' value if the node is at the front of the list, and // a 'false' value otherwise. void addNodeImpR(bool *newFrontFlag, Node *newNode, bool lock); // Acquire the lock if the specified 'lock' is 'true', add the // specified 'newNode' to the list, and release the lock (if acquired). // Search for the correct position for 'newNode' from the back of the // list (in descending order by key value). If the specified // 'newFrontFlag' is not 0, load into it a 'true' value if the node is // at the front of the list, and a 'false' value otherwise. void addNodeR(bool *newFrontFlag, Node *newNode); // Invoke 'addNodeImpR' with lock='true'. IMPLEMENTATION NOTE: this // *particular* flavor of "addNode" is factored into an // optionally-non-locking version to facilitate writing the assignment // operator. If the specified 'newFrontFlag' is not 0, load into it a // 'true' value if the node is at the front of the list, and a 'false' // value otherwise. // // Acquire the lock, add the specified 'newNode' to the list, and // release the lock (if acquired). Search for the correct position for // 'newNode' from the back of the list (in descending order by key // value). If 'newFrontFlag' is not 0, load into it a 'true' value if // the node is at the front of the list, and a 'false' value otherwise. int addNodeUnique(bool *newFrontFlag, Node *newNode); // Acquire the lock, add the specified 'newNode' to the list, and // release the lock. If the specified 'newFrontFlag' is not 0, load // into it a 'true' value if the node is at the front of the list, and // a 'false' value otherwise. Return 0 on success, and a nonzero value // (with no effect on the list) if a node with the same "key" value as // 'newNode' is in the list. int addNodeUniqueR(bool *newFrontFlag, Node *newNode); // Acquire the lock, add the specified 'newNode' to the list, and // release the lock. Search for the correct position for 'newNode' // from the back of the list (in descending order by key value). If // the specified 'newFrontFlag' is not 0, load into it a 'true' value // if the node is at the front of the list, and a 'false' value // otherwise. Return 0 on success, and a nonzero value (with no effect // on the list) if a node with the same "key" value as 'newNode' is in // the list. Node *allocateNode(int level, const KEY& key, const DATA& data); // Allocate a node from the node pool of this list, and set its key // value to the specified 'key' and data value to the specified 'data'. // Set the node's level to the specified 'level' if 'level' is less // than or equal to the highest level of any node previously in the // list, or to one greater than that value otherwise. Return the // allocated node. Note that this method neither acquires nor requires // the lock. void initialize(); // Populate the members of a new Skip List. This private manipulator // must be called only once, by the constructor. void insertImp(bool *newFrontFlag, Node *location[], Node *node); // Insert the specified 'node' into this list immediately before the // specified 'location' (which is populated by either // 'lookupImpLowerBound' or 'lookupImpLowerBoundR'). Load into the // specified 'newFrontFlag' a 'true' value if the node is inserted at // the front of the list, and 'false' otherwise. This method must be // called under the lock. void moveImp(bool *newFrontFlag, Node *location[], Node *node); // Insert the specified 'node' into this list immediately before the // specified 'location' (which is populated by either // 'lookupImpLowerBound' or 'lookupImpLowerBoundR', // 'lookupImpUpperBound', or lookupImpUpperBoundR'). Load into the // specified 'newFrontFlag' a 'true' value if the node is inserted at // the front of the list, and 'false' otherwise. This method must be // called under the lock. // // Like 'insertImp', but 'node' must already be present in the list. // This internal method must be called under the lock. Node *popFrontImp(); // Acquire the lock, remove the front of the list, and release the // lock. Return the node that was at the front of the list, or 0 if // the list was empty. void releaseNode(Node *node); // Decrement the reference count of the specified 'node', and if it // reaches 0, destroy 'node' and return it to the pool. Note that this // method neither acquires nor requires the lock. template <class VECTOR> int removeAllMaybeUnlock(VECTOR *removed, bool unlock); // Remove all items from this list, and append to the specified // 'removed' vector objects referring to the removed nodes. Note that // the objects appended to 'removed' will be in ascending order by key // value. Note that 'removed' may be 0, in which case removed nodes // are released. Return the number of items that were removed from the // list. The behavior is undefined unless the mutex is already locked // before it is called. template <class VECTOR> int removeAllImp(VECTOR *removed); // Remove all items from this list, and append to the specified // 'removed' vector objects referring to the removed nodes. Note that // the objects appended to 'removed' will be in ascending order by key // value. Note that 'removed' may be 0, in which case removed nodes // are released. Return the number of items that were removed from the // list. The behavior is undefined unless the mutex is already locked // before it is called. int removeNode(Node *node); // Acquire the lock, remove the specified 'node' from the list, and // release the lock. Return 0 on success, and 'e_NOT_FOUND' if the // 'node' is no longer in the list. int updateNode(bool *newFrontFlag, Node *node, const KEY& newKey, bool allowDuplicates); // Acquire the lock, move the specified 'node' to the correct position // for the specified 'newKey', and release the lock. Update the key // value of 'node' to the 'newKey' value. If the specified // 'newFrontFlag' is not 0, load into it a 'true' value if the new // location of the node is the front of the list, and a 'false' value // otherwise. If there will be multiple instances of 'newKey' in the // list after the update, the updated node will be the *first* node // with the key 'newKey'. Return 0 on success, 'e_NOT_FOUND' if the // node is no longer in the list, or 'e_DUPLICATE' if the specified // 'allowDuplicates' is 'false' and 'newKey' already appears in the // list. int updateNodeR(bool *newFrontFlag, Node *node, const KEY& newKey, bool allowDuplicates); // Acquire the lock, move the specified 'node' to the correct position // for the specified 'newKey', and release the lock. The search for // the correct location for 'newKey' proceeds from the back of the list // in descending order by by key value. Update the key value of 'node' // to the 'newKey' value. If the specified 'newFrontFlag' is not 0, // load into it a 'true' value if the new location of the node is the // front of the list, and a 'false' value otherwise. If there will be // multiple instances of 'newKey' in the list after the update, the // updated node will be the *last* node with the key 'newKey'. Return // 0 on success, 'e_NOT_FOUND' if the node is no longer in the list, or // 'e_DUPLICATE' if the specified 'allowDuplicates' is 'false' and // 'newKey' already appears in the list. // PRIVATE ACCESSORS Node *backNode() const; // Return the node at the back of the list, or 0 if the list is empty. // This method acquires and releases the lock. void checkInvariants() const; // This function is normally never called -- it is useful in debugging. // If this function is called from anywhere other than the destructor, // it is important that the mutex be locked. Node *findNode(const KEY& key) const; // Return the node with the specified 'key', or 0 if no node could be // found. This method acquires and releases the lock. Node *findNodeR(const KEY& key) const; // Return the node with the specified 'key', or 0 if no node could be // found. This method acquires and releases the lock. Node *findNodeLowerBound(const KEY& key) const; // Return the first node in this list whose key is not less than the // specified 'key', found by searching the list from the front (in // ascending order of key value), and 0 if no such node exists. This // method acquires and releases the lock. Node *findNodeLowerBoundR(const KEY& key) const; // Return the first node in this list whose key is not less than the // specified 'key', found by searching the list from the back (in // descending order of key value), and 0 if no such node exists. This // method acquires and releases the lock. Node *findNodeUpperBound(const KEY& key) const; // Return the first node in this list whose key is greater than the // specified 'key', found by searching the list from the front (in // ascending order of key value), and 0 if no such node exists. This // method acquires and releases the lock. Node *findNodeUpperBoundR(const KEY& key) const; // Return the first node in this list whose key is greater than the // specified 'key', found by searching the list from the back (in // descending order of key value), and 0 if no such node exists. This // method acquires and releases the lock. Node *frontNode() const; // Return the node at the front of the list, or 0 if the list is empty. // This method acquires and releases the lock. void lookupImpLowerBound(Node *location[], const KEY& key) const; // Populate the specified 'location' array with the first node whose // key is not less than the specified 'key' at each level in the list, // found by searching the list from the front (in ascending order of // key value); if no such node exists at a given level, the // tail-of-list sentinel is populated for that level. This method must // be called under the lock. void lookupImpLowerBoundR(Node *location[], const KEY& key) const; // Populate the specified 'location' array with the first node whose // key is not less than the specified 'key' at each level in the list, // found by searching the list from the back (in descending order of // key value); if no such node exists at a given level, the // tail-of-list sentinel is populated for that level. This method must // be called under the lock. void lookupImpUpperBound(Node *location[], const KEY& key) const; // Populate the specified 'location' array with the first node whose // key is greater than the specified 'key' at each level in the list, // found by searching the list from the front (in ascending order of // key value); if no such node exists at a given level, the // tail-of-list sentinel is populated for that level. This method must // be called under the lock. void lookupImpUpperBoundR(Node *location[], const KEY& key) const; // Populate the specified 'location' array with the first node whose // key is greater than the specified 'key' at each level in the list, // found by searching the list from the back (in descending order of // key value); if no such node exists at a given level, the // tail-of-list sentinel is populated for that level. This method must // be called under the lock. Node *nextNode(Node *node) const; // Return the node after to the specified 'node', or 0 if 'node' is at // the back of the list. This method acquires and releases the lock. Node *prevNode(Node *node) const; // Return the node prior to the specified 'node', or 0 if 'node' is at // the front of the list. This method acquires and releases the lock. int skipBackward(Node **node) const; // If the item identified by the specified 'node' is not at the front // of the list, load a reference to the previous item in the list into // 'node'; otherwise load 0 into 'node'. Return 0 on success, and // 'e_NOT_FOUND' (with no effect on the value of 'node') if 'node' is // no longer in the list. This method acquires and releases the lock. int skipForward(Node **node) const; // If the item identified by the specified 'node' is not at the back of // the list, load a reference to the next item in the list into 'node'; // otherwise load 0 into 'node'. Return 0 on success, and // 'e_NOT_FOUND' (with no effect on the value of 'node') if 'node' is // no longer in the list. This method acquires and releases the lock. private: // NOT IMPLEMENTED void addPairReferenceRaw(const PairHandle&); void releaseReferenceRaw(const PairHandle&); // These methods are declared 'private' and not implemented to prevent // the accidental casting of a 'SkipListPairHandle' to a // 'SkipListPair *'. public: // TRAITS BSLMF_NESTED_TRAIT_DECLARATION(SkipList, bslma::UsesBslmaAllocator); // CLASS METHODS static int level(const Pair *reference); // Return the level of the pair identified by the specified // 'reference'. This method is provided for testing. // CREATORS explicit SkipList(bslma::Allocator *basicAllocator = 0); // Create a new Skip List. Optionally specify a 'basicAllocator' used // to supply memory. If 'basicAllocator' is 0, the currently installed // default allocator is used. SkipList(const SkipList& original, bslma::Allocator *basicAllocator = 0); // Create a new Skip List initialized to the value of the specified // 'original' list. Optionally specify a 'basicAllocator' used to // supply memory. If 'basicAllocator' is 0, the currently installed // default allocator is used. ~SkipList(); // Destroy this Skip List. The behavior is undefined if references are // outstanding to any pairs in the list. // MANIPULATORS SkipList& operator=(const SkipList& rhs); // Assign to this Skip List the value of the specified 'rhs' list and // return a reference to the modifiable list. void releaseReferenceRaw(const Pair *reference); // Release the specified 'reference'. After calling this method, the // value of 'reference' must not be used or released again. // Insertion Methods void add(const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list. Load into the // the optionally specified 'newFrontFlag' a 'true' value if the pair // is at the front of the list, and a 'false' value otherwise. void add(PairHandle *result, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list, and load into // the specified 'result' a reference to the pair in the list. Load // into the optionally specified 'newFrontFlag' a 'true' value if the // pair is at the front of the list, and a 'false' value otherwise. void addAtLevelRaw(Pair **result, int level, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list at the specified // 'level', and load into the specified 'result' a reference to the // pair in the list. The 'result' reference must be released (using // 'releaseReferenceRaw') when it is no longer needed. Load into the // the optionally specified 'newFrontFlag' a 'true' value if the pair // is at the front of the list, and a 'false' value otherwise. The // behavior is undefined if 'level' is greater than the // implementation-defined maximum level of this class, or if 'level' is // negative. Note that this method is provided for testing purposes. int addAtLevelUniqueRaw(Pair **result, int level, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list at the specified // 'level', and load into the specified 'result' a reference to the // pair in the list. The 'result' reference must be released (using // 'releaseReferenceRaw') when it is no longer needed. Load into the // the optionally specified 'newFrontFlag' a 'true' value if the pair // is at the front of the list, and a 'false' value otherwise. The // behavior is undefined if 'level' is greater than the // implementation-defined maximum level of this class, or if 'level' is // negative. Return 0 on success, and a non-zero value (with no effect // on the list) if 'key' is already in the list. Note that this method // is provided for testing purposes. void addRaw(Pair **result, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list, and load into // the specified 'result' a reference to the pair in the list. The // 'result' reference must be released (using 'releaseReferenceRaw') // when it is no longer needed. Load into the optionally specified // 'newFrontFlag' a 'true' value if the pair is at the front of the // list, and a 'false' value otherwise. int addUnique(const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list. Load into the // the optionally specified 'newFrontFlag' a 'true' value if the pair // is at the front of the list, and a 'false' value otherwise. Return // 0 on success, and a non-zero value (with no effect on the list) if // 'key' is already in the list. int addUnique(PairHandle *result, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list, and load into // the specified 'result' a reference to the pair in the list. Load // into the optionally specified 'newFrontFlag' a 'true' value if the // pair is at the front of the list, and a 'false' value otherwise. // Return 0 on success, and a non-zero value (with no effect on the // list) if 'key' is already in the list. int addUniqueRaw(Pair **result, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list, and load into // the specified 'result' a reference to the pair in the list. The // 'result' reference must be released (using 'releaseReferenceRaw') // when it is no longer needed. Load into the optionally specified // 'newFrontFlag' a 'true' value if the pair is at the front of the // list, and a 'false' value otherwise. Return 0 on success, and a // non-zero value (with no effect on the list) if 'key' is already in // the list. // Insertion Methods (Reverse Search) void addAtLevelRawR(Pair **result, int level, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list at the specified // 'level', and load into the specified 'result' a reference to the // pair in the list. Search for the correct position for 'key' from // the back of the list (in descending order by key value). The // 'result' reference must be released (using 'releaseReferenceRaw') // when it is no longer needed. Load into the optionally specified // 'newFrontFlag' a 'true' value if the pair is at the front of the // list, and a 'false' value otherwise. The behavior is undefined if // 'level' is greater than the implementation-defined maximum level of // this class, or if 'level' is negative. Note that this method is // provided for testing purposes. int addAtLevelUniqueRawR(Pair **result, int level, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list at the specified // 'level', and load into the specified 'result' a reference to the // pair in the list. Search for the correct position for 'key' from // the back of the list (in descending order by key value). The // 'result' reference must be released (using 'releaseReferenceRaw') // when it is no longer needed. Load into the optionally specified // 'newFrontFlag' a 'true' value if the pair is at the front of the // list, and a 'false' value otherwise. The behavior is undefined if // 'level' is greater than the implementation-defined maximum level of // this class, or if 'level' is negative. Return 0 on success, and a // non-zero value (with no effect on the list) if 'key' is already in // the list. Note that this method is provided for testing purposes. void addR(const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list. Search for the // correct position for 'key' from the back of the list (in descending // order by key value). Load into the optionally specified // 'newFrontFlag' a 'true' value if the pair is at the front of the // list, and a 'false' value otherwise. void addR(PairHandle *result, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list, and load into // the specified 'result' a reference to the pair in the list. Search // for the correct position for 'key' from the back of the list (in // descending order by key value). Load into the optionally specified // 'newFrontFlag' a 'true' value if the pair is at the front of the // list, and a 'false' value otherwise. void addRawR(Pair **result, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list, and load into // the specified 'result' a reference to the pair in the list. Search // for the correct position for 'key' from the back of the list (in // descending order by key value). The 'result' reference must be // released (using 'releaseReferenceRaw') when it is no longer needed. // Load into the optionally specified 'newFrontFlag' a 'true' value if // the pair is at the front of the list, and a 'false' value otherwise. int addUniqueR(const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list. Search for the // correct position for 'key' from the back of the list (in descending // order by key value). Load into the optionally specified // 'newFrontFlag' a 'true' value if the pair is at the front of the // list, and a 'false' value otherwise. Return 0 on success, and a // non-zero value (with no effect on the list) if 'key' is already in // the list. int addUniqueR(PairHandle *result, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list, and load into // the specified 'result' a reference to the pair in the list. Search // for the correct position for 'key' from the back of the list (in // descending order by key value). Load into the optionally specified // 'newFrontFlag' a 'true' value if the pair is at the front of the // list, and a 'false' value otherwise. Return 0 on success, and a // non-zero value (with no effect on the list) if 'key' is already in // the list. int addUniqueRawR(Pair **result, const KEY& key, const DATA& data, bool *newFrontFlag = 0); // Add the specified 'key' / 'data' pair to this list, and load into // the specified 'result' a reference to the pair in the list. Search // for the correct position for 'key' from the back of the list (in // descending order by key value). The 'result' reference must be // released (using 'releaseReferenceRaw') when it is no longer needed. // Load into the optionally specified 'newFrontFlag' a 'true' value if // the pair is at the front of the list, and a 'false' value otherwise. // Return 0 on success, and a non-zero value (with no effect on the // list) if 'key' is already in the list. // Removal Methods int popFront(PairHandle *item = 0); // Remove the first item from the list and load a reference to it into // the optionally specified 'item'. Return 0 on success, and a // non-zero value if the list is empty. int popFrontRaw(Pair **item); // Remove the first item from the list and load a reference to it into // the specified 'item'. This reference must be released (using // 'releaseReferenceRaw') when it is no longer needed. Return 0 on // success, and a non-zero value if the list is empty. int remove(const Pair *reference); // Remove the item identified by the specified 'reference' from the // list. Return 0 on success, and a non-zero value if the pair has // already been removed from the list. int removeAll(); int removeAll(bsl::vector<PairHandle> *removed); int removeAll(std::vector<PairHandle> *removed); #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR int removeAll(std::pmr::vector<PairHandle> *removed); #endif // Remove all items from this list. Optionally specify 'removed', a // vector to which to append handles to the removed nodes. The items // appended to 'removed' will be in ascending order by key value. // Return the number of items that were removed from this list. Note // that all references in 'removed' must be released (i.e., destroyed) // before this skip list is destroyed. Note that if 'removed' is not // specified, all removed elements will be released by this method. int removeAllRaw(bsl::vector<Pair *> *removed); int removeAllRaw(std::vector<Pair *> *removed); #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR int removeAllRaw(std::pmr::vector<Pair *> *removed); #endif // Remove all items from this list. Append to the specified // 'removed' vector pointers that can be used to refer to the removed // items. *Each* such pointer must be released (using // 'releaseReferenceRaw') when it is no longer needed. The pairs // appended to 'removed' will be in ascending order by key value. // Return the number of items that were removed from this list. // Update Methods int update(const Pair *reference, const KEY& newKey, bool *newFrontFlag = 0, bool allowDuplicates = true); // Assign the specified 'newKey' value to the pair identified by the // specified 'reference', moving the pair within the list as necessary. // Load into the optionally specified 'newFrontFlag' a 'true' value if // the new location of the pair is the front of the list. Return 0 on // success, 'e_NOT_FOUND' if the pair referred to by 'reference' is no // longer in the list, or 'e_DUPLICATE' if the optionally specified // 'allowDuplicates' is 'false' and 'newKey' already appears in the // list. int updateR(const Pair *reference, const KEY& newKey, bool *newFrontFlag = 0, bool allowDuplicates = true); // Assign the specified 'newKey' value to the pair identified by the // specified 'reference', moving the pair within the list as necessary. // Search for the new position from the back of the list (in descending // order by key value). Load into the optionally specified // 'newFrontFlag' a 'true' value if the new location of the pair is the // front of the list. Return 0 on success, 'e_NOT_FOUND' if the pair // referred to by 'reference' is no longer in the list, or // 'e_DUPLICATE' if the optionally specified 'allowDuplicates' is // 'false' and 'newKey' already appears in the list. // ACCESSORS Pair *addPairReferenceRaw(const Pair *reference) const; // Increment the reference count for the list element referred to by // the specified 'reference'. There must be a corresponding call to // 'releaseReferenceRaw' when the reference is no longer needed. The // behavior is undefined 'item' has already been released. Return // 'reference'. int back(PairHandle *back) const; // Load into the specified 'back' a reference to the last item in the // list. Return 0 on success, and a non-zero value (with no effect on // 'back') if the list is empty. int backRaw(Pair **back) const; // Load into the specified 'back' a reference to the last item in the // list. The 'back' reference must be released (using // 'releaseReferenceRaw') when it is no longer needed. Return 0 on // success, and a non-zero value if the list is empty. Note that if // the list is empty, the value of '*back' is undefined. bool exists(const KEY& key) const; // Return 'true' if there is a pair in the list with the specified // 'key', and 'false' otherwise. int front(PairHandle *front) const; // Load into the specified 'front' a reference to the first item in the // list. Return 0 on success, and a non-zero value (with no effect on // 'front') if the list is empty. int frontRaw(Pair **front) const; // Load into the specified 'front' a reference to the first item in the // list. The 'front' reference must be released (using // 'releaseReferenceRaw') when it is no longer needed. Return 0 on // success, and a non-zero value if the list is empty. bool isEmpty() const; // Return 'true' if this list is empty, and 'false' otherwise. int length() const; // Return the number of items in this list. bsl::ostream& print(bsl::ostream& stream, int level = 0, int spacesPerLevel = 4) const; // Format this list object to the specified output 'stream' at the // (absolute value of) the optionally specified indentation 'level' and // return a reference to 'stream'. If 'level' is specified, optionally // specify 'spacesPerLevel', the number of spaces per indentation level // for this and all of its nested objects. If 'level' is negative, // suppress indentation of the first line. If 'spacesPerLevel' is // negative, suppress all indentation AND format the entire output on // one line. If 'stream' is not valid on entry, this operation has no // effect. // simple forward finds int find(PairHandle *item, const KEY& key) const; // Load into the specified 'item' a reference to the element in this // list with the specified 'key' found by searching the list from the // front (in ascending order of key value). If multiple elements // having 'key' are in the container, load 'item' with the *first* // matching element. Return 0 on success, and a non-zero value (with // no effect on 'item') if no such element exists. If there are // multiple elements in the list with the 'key', it is undefined which // one is returned. int findRaw(Pair **item, const KEY& key) const; // Load into the specified 'item' a reference to the element in this // list with the specified 'key' found by searching the list from the // front (in ascending order of key value). Return 0 on success, and a // non-zero value (with no effect on 'item') if no such element exists. // If there are multiple elements in the list with the 'key', it is // undefined which one is returned. The 'item' reference must be // released (using 'releaseReferenceRaw') when it is no longer needed. // simple reverse finds int findR(PairHandle *item, const KEY& key) const; // Load into the specified 'item' a reference to the element in this // list with the specified 'key' found by searching the list from the // back (in descending order of key value). If multiple elements // having 'key' are in the container, load 'item' with the *last* // matching element. 'key' are present, find the last one. Return 0 // on success, and a non-zero value (with no effect on 'item') if no // such element exists. If there are multiple elements in the list // with the 'key', it is undefined which one is returned. int findRRaw(Pair **item, const KEY& key) const; // Load into the specified 'item' a reference to the element in this // list with the specified 'key' found by searching the list from the // back (in descending order of key value). Return 0 on success, and a // non-zero value (with no effect on 'item') if no such element exists. // If there are multiple elements in the list with the 'key', it is // undefined which one is returned. The 'item' reference must be // released (using 'releaseReferenceRaw') when it is no longer needed. // find lower bound int findLowerBound(PairHandle *item, const KEY& key) const; // Load into the specified 'item' a reference to the first element in // this list whose key value is not less than the specified 'key' found // by searching the list from the front (in ascending order of key // value). Return 0 on success, and a non-zero value (with no effect // on 'item') if no such element exists. int findLowerBoundRaw(Pair **item, const KEY& key) const; // Load into the specified 'item' a reference to the first element in // this list whose key value is not less than the specified 'key' found // by searching the list from the front (in ascending order of key // value). Return 0 on success, and a non-zero value (with no effect // on 'item') if no such element exists. The 'item' reference must be // released (using 'releaseReferenceRaw') when it is no longer needed. // find lower bound reverse int findLowerBoundR(PairHandle *item, const KEY& key) const; // Load into the specified 'item' a reference to the first element in // this list whose key value is not less than the specified 'key' found // by searching the list from the back (in descending order of key // value). Return 0 on success, and a non-zero value (with no effect // on 'item') if no such element exists. int findLowerBoundRRaw(Pair **item, const KEY& key) const; // Load into the specified 'item' a reference to the first element in // this list whose key value is not less than the specified 'key' found // by searching the list from the back (in descending order of key // value). Return 0 on success, and a non-zero value (with no effect // on 'item') if no such element exists. The 'item' reference must be // released (using 'releaseReferenceRaw') when it is no longer needed. // find upper bound int findUpperBound(PairHandle *item, const KEY& key) const; // Load into the specified 'item' a reference to the first element in // this list whose key value is greater than the specified 'key' found // by searching the list from the front (in ascending order of key // value). Return 0 on success, and a non-zero value (with no effect // on 'item') if no such element exists. int findUpperBoundRaw(Pair **item, const KEY& key) const; // Load into the specified 'item' a reference to the first element in // this list whose key value is greater than the specified 'key' found // by searching the list from the front (in ascending order of key // value). Return 0 on success, and a non-zero value (with no effect // on 'item') if no such element exists. The 'item' reference must be // released (using 'releaseReferenceRaw') when it is no longer needed. // find upper bound reverse int findUpperBoundR(PairHandle *item, const KEY& key) const; // Load into the specified 'item' a reference to the first element in // this list whose key value is greater than the specified 'key' found // by searching the list from the back (in descending order of key // value). Return 0 on success, and a non-zero value (with no effect // on 'item') if no such element exists. int findUpperBoundRRaw(Pair **item, const KEY& key) const; // Load into the specified 'item' a reference to the first element in // this list whose key value is greater than the specified 'key' found // by searching the list from the back (in descending order of key // value). Return 0 on success, and a non-zero value (with no effect // on 'item') if no such element exists. The 'item' reference must be // released (using 'releaseReferenceRaw') when it is no longer needed. // next, previous, & skip* int next(PairHandle *next, const Pair *reference) const; // Load into the specified 'next' a reference to the item that appears // in the list after the item identified by the specified 'reference'. // Return 0 on success, or a non-zero value if 'reference' refers to // the back of the list. int nextRaw(Pair **next, const Pair *reference) const; // Load into the specified 'next' a reference to the item that appears // in the list after the item identified by the specified 'reference'. // The 'next' reference must be released (using 'releaseReferenceRaw') // when it is no longer needed. Return 0 on success, or a non-zero // value if 'reference' refers to the back of the list. int previous(PairHandle *prevPair, const Pair *reference) const; // Load into the specified 'prevPair' a reference to the pair that // appears in the list before the pair identified by the specified // 'reference'. Return 0 on success, or a non-zero value if // 'reference' refers to the front of the list. int previousRaw(Pair **prevPair, const Pair *reference) const; // Load into the specified 'prevPair' a reference to the pair that // appears in the list before the pair identified by the specified // 'reference'. The 'prevPair' reference must be released (using // 'releaseReferenceRaw') when it is no longer needed. Return 0 on // success, or a non-zero value if 'reference' refers to the front of // the list. int skipBackward(PairHandle *item) const; // If the item identified by the specified 'item' is not at the front // of the list, load a reference to the previous item in the list into // 'item'; otherwise reset the value of 'item'. Return 0 on success, // and 'e_NOT_FOUND' (with no effect on the value of 'item') if 'item' // is no longer in the list. int skipBackwardRaw(Pair **item) const; // If the item identified by the specified 'item' is not at the front // of the list, load a reference to the previous item in the list into // 'item'; otherwise reset the value of 'item'. Return 0 on success, // and 'e_NOT_FOUND' (with no effect on the value of 'item') if 'item' // is no longer in the list. int skipForward(PairHandle *item) const; // If the item identified by the specified 'item' is not at the end of // the list, load a reference to the next item in the list into 'item'; // otherwise reset the value of 'item'. Return 0 on success, and // 'e_NOT_FOUND' (with no effect on the value of 'item') if 'item' is // no longer in the list. int skipForwardRaw(Pair **item) const; // If the item identified by the specified 'item' is not at the end of // the list, load a reference to the next item in the list into 'item'; // otherwise reset the value of 'item'. Return 0 on success, and // 'e_NOT_FOUND' (with no effect on the value of 'item') if 'item' is // no longer in the list. // Aspects bslma::Allocator *allocator() const; // Return the allocator used by this object to supply memory. }; // FREE OPERATORS template <class KEY, class DATA> bool operator==(const SkipList<KEY, DATA>& lhs, const SkipList<KEY, DATA>& rhs); // Return 'true' if the specified 'lhs' list has the same value as the // specified 'rhs' list, and 'false' otherwise. Two lists A and B have the // same value if they have the same number of elements, and if for all i in // the range [0, numberOfElements), the i'th pair from the front of A has // the same key and data values as the i'th pair from the front of B. Note // that if there are duplicate key values in a list, the order of iteration // over those pairs may be different than for another list that was // constructed from the same sequence of values (and thus the lists may not // compare equal). template <class KEY, class DATA> bool operator!=(const SkipList<KEY, DATA>& lhs, const SkipList<KEY, DATA>& rhs); // Return 'true' if the specified 'lhs' list list has a different value // from the specified 'rhs' list, and 'false' otherwise. Two lists A and B // have different values if they have a different of elements, or if there // exists an i in the range [0, numberOfElements) such that the i'th pair // from the front of A differs in key or data values from i'th pair from // the front of B. template<class KEY, class DATA> bsl::ostream& operator<<(bsl::ostream& stream, const SkipList<KEY, DATA>& list); // Write the specified 'list' to the specified output 'stream' and return a // reference to the modifiable 'stream'. // ======================== // class SkipList::IsVector // ======================== template <class KEY, class DATA> template <class VECTOR, class VALUE_TYPE> class SkipList<KEY, DATA>::IsVector { // This 'struct' has a 'value' that evaluates to 'true' if the specified // 'VECTOR' is a 'bsl', 'std', or 'std::pmr' 'vector<VALUE_TYPE>'. public: // PUBLIC CLASS DATA static const bool value = bsl::is_same<bsl::vector<VALUE_TYPE>, VECTOR>::value #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR || bsl::is_same<std::pmr::vector<VALUE_TYPE>, VECTOR>::value #endif || bsl::is_same<std::vector<VALUE_TYPE>, VECTOR>::value; }; // =========================== // class SkipList::PairFactory // =========================== template <class KEY, class DATA> class SkipList<KEY, DATA>::PairFactory { public: // CREATORS explicit PairFactory(SkipList *); // Create a 'PairFactory'. // ACCESSOR Pair *operator()(Node *node) const; // Convert the specified 'node' to a 'Pair *'. }; // ================================= // class SkipList::PairHandleFactory // ================================= template <class KEY, class DATA> class SkipList<KEY, DATA>::PairHandleFactory { // DATA SkipList *d_list_p; public: // CREATORS explicit PairHandleFactory(SkipList *list); // Create a 'PairHandleFactory' bound to the specified 'list'. // ACCESSOR PairHandle operator()(Node *node) const; // Return a 'PairHandle' bound to the list this object is bound to, and // referring to the specified 'node'. }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // ------------------------ // SkipList_DoubleLockGuard // ------------------------ inline SkipList_DoubleLockGuard::SkipList_DoubleLockGuard(bslmt::Mutex *lock1, bslmt::Mutex *lock2) : d_firstGuard(bsl::min(lock1, lock2, bsl::less<bslmt::Mutex *>())) , d_lastGuard( bsl::max(lock1, lock2, bsl::less<bslmt::Mutex *>())) {} // ------------------ // class SkipListPair // ------------------ // ACCESSORS template <class KEY, class DATA> inline DATA& SkipListPair<KEY, DATA>::data() const { return SkipList<KEY, DATA>::data(this); } template <class KEY, class DATA> inline const KEY& SkipListPair<KEY, DATA>::key() const { return SkipList<KEY, DATA>::key(this); } // ------------------------ // class SkipListPairHandle // ------------------------ // CREATORS template <class KEY, class DATA> inline SkipListPairHandle<KEY, DATA>::SkipListPairHandle() : d_list_p(0) , d_node_p(0) { } template <class KEY, class DATA> inline SkipListPairHandle<KEY, DATA>::SkipListPairHandle( SkipList<KEY, DATA> *list, Pair *reference) : d_list_p(list) , d_node_p(reference) { } template <class KEY, class DATA> inline SkipListPairHandle<KEY, DATA>::SkipListPairHandle( const SkipListPairHandle& original) : d_list_p(original.d_list_p) , d_node_p(original.d_node_p ? d_list_p->addPairReferenceRaw(original.d_node_p) : 0) { } template <class KEY, class DATA> inline SkipListPairHandle<KEY, DATA>::~SkipListPairHandle() { release(); } // MANIPULATORS template <class KEY, class DATA> inline SkipListPairHandle<KEY, DATA>& SkipListPairHandle<KEY, DATA>::operator=(const SkipListPairHandle& rhs) { reset(rhs.d_list_p, 0); d_node_p = rhs.d_node_p ? d_list_p->addPairReferenceRaw(rhs.d_node_p) : 0; return *this; } template <class KEY, class DATA> inline void SkipListPairHandle<KEY, DATA>::release() { if (d_node_p) { BSLS_ASSERT(0 != d_list_p); d_list_p->releaseReferenceRaw(d_node_p); d_node_p = 0; } } template <class KEY, class DATA> inline void SkipListPairHandle<KEY, DATA>::releaseReferenceRaw( SkipList<KEY, DATA> **list, Pair **reference) { BSLS_ASSERT(list); BSLS_ASSERT(reference); *list = d_list_p; *reference = d_node_p; release(); } template <class KEY, class DATA> inline void SkipListPairHandle<KEY, DATA>::reset(const SkipList<KEY, DATA> *list, Pair *reference) { release(); d_list_p = const_cast<SkipList<KEY, DATA> *>(list); d_node_p = reference; } // ACCESSORS template <class KEY, class DATA> inline DATA& SkipListPairHandle<KEY, DATA>::data() const { BSLS_ASSERT_SAFE(isValid()); return SkipList<KEY, DATA>::data(d_node_p); } template <class KEY, class DATA> inline bool SkipListPairHandle<KEY, DATA>::isValid() const { return d_node_p != 0 && d_list_p != 0; } template <class KEY, class DATA> inline const KEY& SkipListPairHandle<KEY, DATA>::key() const { BSLS_ASSERT_SAFE(isValid()); return SkipList<KEY, DATA>::key(d_node_p); } } // close package namespace // The scoping of "Pair" below should not be necessary, but xlC (versions 8 and // 9) requires it. template <class KEY, class DATA> inline bdlcc::SkipListPairHandle<KEY, DATA>:: operator const bdlcc::SkipListPair<KEY, DATA>*() const { return d_node_p; } namespace bdlcc { // --------------------------------- // class SkipList_NodeCreationHelper // --------------------------------- template<class KEY, class DATA> inline SkipList_NodeCreationHelper<KEY, DATA>::SkipList_NodeCreationHelper( PoolManager *poolManager, Node *node, bslma::Allocator *basicAllocator) : d_node_p(node) , d_poolManager_p(poolManager) , d_keyFlag(false) , d_allocator_p(bslma::Default::allocator(basicAllocator)) { } template<class KEY, class DATA> inline SkipList_NodeCreationHelper<KEY, DATA>::~SkipList_NodeCreationHelper() { if (d_node_p) { if (d_keyFlag) { d_node_p->d_key.~KEY(); } PoolUtil::deallocate(d_poolManager_p, d_node_p); } } template<class KEY, class DATA> inline void SkipList_NodeCreationHelper<KEY, DATA>::construct(const KEY& key, const DATA& data) { BSLS_ASSERT(d_node_p); bslalg::ScalarPrimitives::copyConstruct( BSLS_UTIL_ADDRESSOF(d_node_p->d_key), key, d_allocator_p); d_keyFlag = true; bslalg::ScalarPrimitives::copyConstruct( BSLS_UTIL_ADDRESSOF(d_node_p->d_data), data, d_allocator_p); d_node_p = 0; } // --------------------------- // class SkipList::PairFactory // --------------------------- // CREATORS template<class KEY, class DATA> inline SkipList<KEY, DATA>::PairFactory::PairFactory(SkipList *) {} // ACCESSOR template<class KEY, class DATA> inline typename SkipList<KEY, DATA>::Pair *SkipList<KEY, DATA>::PairFactory::operator()(Node *node) const { return reinterpret_cast<Pair *>(node); } // --------------------------------- // class SkipList::PairHandleFactory // --------------------------------- // CREATORS template<class KEY, class DATA> inline SkipList<KEY, DATA>::PairHandleFactory::PairHandleFactory(SkipList *list) : d_list_p(list) {} // ACCESSOR template<class KEY, class DATA> inline typename SkipList<KEY, DATA>::PairHandle SkipList<KEY, DATA>::PairHandleFactory::operator()(Node *node) const { return PairHandle(d_list_p, reinterpret_cast<Pair *>(node)); } // -------------- // class SkipList // -------------- // PRIVATE CLASS METHODS template<class KEY, class DATA> inline DATA& SkipList<KEY, DATA>::data(const Pair *reference) { BSLS_ASSERT(reference); Node *node = static_cast<Node *>(static_cast<void *>( const_cast<Pair *>(reference))); return node->d_data; } template<class KEY, class DATA> inline const KEY& SkipList<KEY, DATA>::key(const Pair *reference) { BSLS_ASSERT(reference); const Node *node = static_cast<const Node *>( static_cast<const void *>(reference)); return node->d_key; } template<class KEY, class DATA> inline BSLS_KEYWORD_CONSTEXPR bsls::Types::IntPtr SkipList<KEY, DATA>::offsetOfPtrs() { typedef bsls::Types::IntPtr IntPtr; // The null pointer dereference is just used for taking offsets and sizes // in the 'Node' struct. Note that we don't want to just create a default // constructed 'Node', because if 'KEY' or 'DATA' lack default constructors // then 'Node' has no default constructor. return reinterpret_cast<IntPtr>(&reinterpret_cast<Node*>(0)->d_ptrs); } template<class KEY, class DATA> inline typename SkipList<KEY, DATA>::Node *SkipList<KEY, DATA>::pairToNode(Pair *reference) { return static_cast<Node *>(static_cast<void *>(reference)); } template<class KEY, class DATA> inline typename SkipList<KEY, DATA>::Node *SkipList<KEY, DATA>::pairToNode(const Pair *reference) { return static_cast<Node *>(static_cast<void *>( const_cast<Pair *>(reference))); } // PRIVATE MANIPULATORS template<class KEY, class DATA> void SkipList<KEY, DATA>::addNode(bool *newFrontFlag, Node *newNode) { LockGuard guard(&d_lock); BSLS_ASSERT(newNode); BSLS_ASSERT(0 == newNode->d_ptrs[0].d_next_p); Node *location[k_MAX_NUM_LEVELS]; lookupImpLowerBound(location, newNode->d_key); insertImp(newFrontFlag, location, newNode); } template<class KEY, class DATA> void SkipList<KEY, DATA>::addNodeImpR(bool *newFrontFlag, Node *newNode, bool lock) { LockGuard lockGuard(&d_lock, !lock); if (!lock) { lockGuard.release(); } BSLS_ASSERT(newNode); BSLS_ASSERT(0 == newNode->d_ptrs[0].d_next_p); Node *location[k_MAX_NUM_LEVELS]; lookupImpUpperBoundR(location, newNode->d_key); insertImp(newFrontFlag, location, newNode); } template<class KEY, class DATA> inline void SkipList<KEY, DATA>::addNodeR(bool *newFrontFlag, Node *newNode) { addNodeImpR(newFrontFlag, newNode, true); // true -> lock } template<class KEY, class DATA> int SkipList<KEY, DATA>::addNodeUnique(bool *newFrontFlag, Node *newNode) { LockGuard guard(&d_lock); BSLS_ASSERT(newNode); BSLS_ASSERT(0 == newNode->d_ptrs[0].d_next_p); Node *location[k_MAX_NUM_LEVELS]; lookupImpLowerBound(location, newNode->d_key); Node *q = location[0]; if (q != d_tail_p && q->d_key == newNode->d_key) { return e_DUPLICATE; // RETURN } insertImp(newFrontFlag, location, newNode); return 0; } template<class KEY, class DATA> int SkipList<KEY, DATA>::addNodeUniqueR(bool *newFrontFlag, Node *newNode) { LockGuard guard(&d_lock); BSLS_ASSERT(newNode); BSLS_ASSERT(0 == newNode->d_ptrs[0].d_next_p); Node *location[k_MAX_NUM_LEVELS]; lookupImpLowerBoundR(location, newNode->d_key); Node *q = location[0]; if (q != d_tail_p && q->d_key == newNode->d_key) { return e_DUPLICATE; // RETURN } insertImp(newFrontFlag, location, newNode); return 0; } template<class KEY, class DATA> SkipList_Node<KEY, DATA> * SkipList<KEY, DATA>::allocateNode(int level, const KEY& key, const DATA& data) { int listLevel = d_listLevel; if (level > listLevel) { level = listLevel + 1; } Node *node = reinterpret_cast<Node *>(PoolUtil::allocate(d_poolManager_p, level)); NodeGuard nodeGuard(d_poolManager_p, node, d_allocator_p); nodeGuard.construct(key, data); ++node->d_refCount; node->d_ptrs[0].d_next_p = 0; return node; } template<class KEY, class DATA> void SkipList<KEY, DATA>::initialize() { typedef bsls::Types::IntPtr IntPtr; static const int alignMask = bsls::AlignmentFromType<Node>::VALUE - 1; // Assert that this method has not been invoked. BSLS_ASSERT(0 == d_poolManager_p); int nodeSizes[k_MAX_NUM_LEVELS]; const IntPtr offset = offsetOfPtrs(); for (int i = 0; i < k_MAX_NUM_LEVELS; ++i) { IntPtr nodeSize = offset + (i + 1) * sizeof(typename Node::Ptrs); nodeSize = (nodeSize + alignMask) & ~alignMask; nodeSizes[i] = static_cast<int>(nodeSize); } d_poolManager_p = PoolUtil::createPoolManager(nodeSizes, k_MAX_NUM_LEVELS, d_allocator_p); d_head_p = reinterpret_cast<Node *>(PoolUtil::allocate(d_poolManager_p, k_MAX_LEVEL)); d_tail_p = reinterpret_cast<Node *>(PoolUtil::allocate(d_poolManager_p, k_MAX_LEVEL)); for (int i = 0; i < k_MAX_NUM_LEVELS; ++i) { d_head_p->d_ptrs[i].d_prev_p = 0; d_head_p->d_ptrs[i].d_next_p = d_tail_p; d_tail_p->d_ptrs[i].d_prev_p = d_head_p; d_tail_p->d_ptrs[i].d_next_p = 0; } } template<class KEY, class DATA> void SkipList<KEY, DATA>::insertImp(bool *newFrontFlag, Node *location[], Node *node) { BSLS_ASSERT(location); BSLS_ASSERT(node); int level = node->d_level; if (level > d_listLevel) { BSLS_ASSERT(level == d_listLevel + 1); d_listLevel = level; node->d_ptrs[level].d_prev_p = d_head_p; node->d_ptrs[level].d_next_p = d_tail_p; d_head_p->d_ptrs[level].d_next_p = node; d_tail_p->d_ptrs[level].d_prev_p = node; level--; } for (int k = level; k >= 0; --k) { Node *p = location[k]->d_ptrs[k].d_prev_p; Node *q = location[k]; node->d_ptrs[k].d_prev_p = p; node->d_ptrs[k].d_next_p = q; p->d_ptrs[k].d_next_p = node; q->d_ptrs[k].d_prev_p = node; } if (newFrontFlag) { *newFrontFlag = (node->d_ptrs[0].d_prev_p == d_head_p); } ++d_length; } template<class KEY, class DATA> void SkipList<KEY, DATA>::moveImp(bool *newFrontFlag, Node *location[], Node *node) { BSLS_ASSERT(location); BSLS_ASSERT(node); int level = node->d_level; BSLS_ASSERT(level <= d_listLevel); for (int k = 0; k <= level; ++k) { Node *newP = location[k]->d_ptrs[k].d_prev_p; Node *newQ = location[k]; if (newP == node || newQ == node) { // The node's already in the right place. Since we started at // level 0, there's no more work to do. break; } Node *oldP = node->d_ptrs[k].d_prev_p; Node *oldQ = node->d_ptrs[k].d_next_p; oldQ->d_ptrs[k].d_prev_p = oldP; oldP->d_ptrs[k].d_next_p = oldQ; node->d_ptrs[k].d_prev_p = newP; node->d_ptrs[k].d_next_p = newQ; newP->d_ptrs[k].d_next_p = node; newQ->d_ptrs[k].d_prev_p = node; } if (newFrontFlag) { *newFrontFlag = (node->d_ptrs[0].d_prev_p == d_head_p); } } template<class KEY, class DATA> SkipList_Node<KEY, DATA> *SkipList<KEY, DATA>::popFrontImp() { LockGuard guard(&d_lock); Node *node = d_head_p->d_ptrs[0].d_next_p; if (node == d_tail_p) { return 0; // RETURN } int level = node->d_level; for (int k = level; k >= 0; --k) { Node *q = node->d_ptrs[k].d_next_p; q->d_ptrs[k].d_prev_p = d_head_p; d_head_p->d_ptrs[k].d_next_p = q; } node->d_ptrs[0].d_next_p = 0; --d_length; return node; } template<class KEY, class DATA> inline void SkipList<KEY, DATA>::releaseNode(Node *node) { BSLS_ASSERT(node); const int refCnt = --node->d_refCount; if (!refCnt) { node->d_key.~KEY(); node->d_data.~DATA(); BSLS_ASSERT(0 == node->d_ptrs[0].d_next_p); PoolUtil::deallocate(d_poolManager_p, node); } else { BSLS_ASSERT_SAFE(0 < refCnt); } } template<class KEY, class DATA> template<class VECTOR> int SkipList<KEY, DATA>::removeAllMaybeUnlock(VECTOR *removed, bool unlock) { typedef typename VECTOR::value_type ValueType; BSLMF_ASSERT((IsVector<VECTOR, ValueType>::value)); BSLMF_ASSERT((bsl::is_same<ValueType, Pair *>::value || bsl::is_same<ValueType, PairHandle>::value)); typedef typename bsl::conditional<bsl::is_same<ValueType, Pair *>::value, PairFactory, PairHandleFactory>::type FactoryType; BSLMT_MUTEXASSERT_IS_LOCKED(&d_lock); Node * const begin = d_head_p; Node * const end = d_tail_p->d_ptrs[0].d_prev_p; int numRemoved = d_length; for (int ii = 0; ii <= d_listLevel; ++ii) { d_head_p->d_ptrs[ii].d_next_p = d_tail_p; d_tail_p->d_ptrs[ii].d_prev_p = d_head_p; } d_length = 0; for (Node *q = end; begin != q; q = q->d_ptrs[0].d_prev_p) { q->d_ptrs[0].d_next_p = 0; // Marks node as removed from list, // must be done before mutex unlock. } if (unlock) { d_lock.unlock(); } if (removed) { const FactoryType factory(this); // 'oldSize' must be a signed type to be compared to the subtraction // in the assertion after the loop. const bsls::Types::IntPtr oldSize = removed->size(); removed->resize(oldSize + numRemoved); typename VECTOR::reverse_iterator removedIt = removed->rbegin(); for (Node *q = end; begin != q; q = q->d_ptrs[0].d_prev_p) { *removedIt++ = factory(q); } BSLS_ASSERT(oldSize == removed->rend() - removedIt); } else { for (Node *q = end; begin != q; ) { Node *condemned = q; q = q->d_ptrs[0].d_prev_p; releaseNode(condemned); } } return numRemoved; } template<class KEY, class DATA> template<class VECTOR> int SkipList<KEY, DATA>::removeAllImp(VECTOR *removed) { d_lock.lock(); return removeAllMaybeUnlock(removed, true); } template<class KEY, class DATA> int SkipList<KEY, DATA>::removeNode(Node *node) { BSLS_ASSERT(node); LockGuard guard(&d_lock); if (0 == node->d_ptrs[0].d_next_p) { return e_NOT_FOUND; // RETURN } int level = node->d_level; for (int k = level; k >= 0; --k) { Node *p = node->d_ptrs[k].d_prev_p; Node *q = node->d_ptrs[k].d_next_p; q->d_ptrs[k].d_prev_p = p; p->d_ptrs[k].d_next_p = q; } node->d_ptrs[0].d_next_p = 0; --d_length; return 0; } template<class KEY, class DATA> int SkipList<KEY, DATA>::updateNode(bool *newFrontFlag, Node *node, const KEY& newKey, bool allowDuplicates) { BSLS_ASSERT(node); LockGuard guard(&d_lock); if (0 == node->d_ptrs[0].d_next_p) { return e_NOT_FOUND; // RETURN } Node *location[k_MAX_NUM_LEVELS]; lookupImpLowerBound(location, newKey); if (!allowDuplicates) { Node *q = location[0]; if (q != d_tail_p && q != node && q->d_key == newKey) { return e_DUPLICATE; // RETURN } } node->d_key = newKey; // may throw // now we are committed: change the list! moveImp(newFrontFlag, location, node); return 0; } template<class KEY, class DATA> int SkipList<KEY, DATA>::updateNodeR(bool *newFrontFlag, Node *node, const KEY& newKey, bool allowDuplicates) { BSLS_ASSERT(node); LockGuard guard(&d_lock); if (0 == node->d_ptrs[0].d_next_p) { return e_NOT_FOUND; // RETURN } Node *location[k_MAX_NUM_LEVELS]; if (!allowDuplicates) { lookupImpLowerBoundR(location, newKey); Node *p = location[0]; if (p != d_tail_p && p != node && p->d_key == newKey) { return e_DUPLICATE; // RETURN } } else { lookupImpUpperBoundR(location, newKey); } node->d_key = newKey; // may throw // now we are committed: change the list! moveImp(newFrontFlag, location, node); return 0; } // PRIVATE ACCESSORS template<class KEY, class DATA> SkipList_Node<KEY, DATA> * SkipList<KEY, DATA>::backNode() const { LockGuard guard(&d_lock); Node *node = d_tail_p->d_ptrs[0].d_prev_p; if (node == d_head_p) { return 0; // RETURN } ++node->d_refCount; return node; } template<class KEY, class DATA> void SkipList<KEY, DATA>::checkInvariants() const { for (int ii = 0; ii <= d_listLevel; ++ii) { int numNodes = 0; Node *prev = d_head_p; for (Node *q = d_head_p->d_ptrs[ii].d_next_p; d_tail_p != q; prev = q, q = q->d_ptrs[ii].d_next_p) { ++numNodes; BSLS_ASSERT(q->d_ptrs[ii].d_prev_p == prev); BSLS_ASSERT(0 < q->d_refCount); BSLS_ASSERT(q->d_level >= ii); BSLS_ASSERT(q->d_level <= d_listLevel); for (int jj = ii - 1; 0 <= jj; --jj) { BSLS_ASSERT(q->d_ptrs[jj].d_next_p); BSLS_ASSERT(q->d_ptrs[jj].d_prev_p); } } BSLS_ASSERT(numNodes <= d_length); BSLS_ASSERT(0 != ii || numNodes == d_length); BSLS_ASSERT(0 == d_head_p->d_ptrs[ii].d_prev_p); BSLS_ASSERT(0 == d_tail_p->d_ptrs[ii].d_next_p); } } template<class KEY, class DATA> SkipList_Node<KEY, DATA> *SkipList<KEY, DATA>::findNode(const KEY& key) const { Node *locator[k_MAX_NUM_LEVELS]; LockGuard guard(&d_lock); lookupImpLowerBound(locator, key); Node *q = locator[0]; if (q != d_tail_p && q->d_key == key) { ++q->d_refCount; return q; // RETURN } return 0; } template<class KEY, class DATA> SkipList_Node<KEY, DATA> *SkipList<KEY, DATA>::findNodeR(const KEY& key) const { Node *locator[k_MAX_NUM_LEVELS]; LockGuard guard(&d_lock); lookupImpUpperBoundR(locator, key); Node *p = locator[0]; if (d_head_p != p) { p = p->d_ptrs[0].d_prev_p; if (d_head_p != p && key == p->d_key) { ++p->d_refCount; return p; // RETURN } } return 0; } template<class KEY, class DATA> SkipList_Node<KEY, DATA> *SkipList<KEY, DATA>::findNodeLowerBound( const KEY& key) const { Node *locator[k_MAX_NUM_LEVELS]; LockGuard guard(&d_lock); lookupImpLowerBound(locator, key); Node *q = locator[0]; if (q != d_tail_p) { ++q->d_refCount; return q; // RETURN } return 0; } template<class KEY, class DATA> SkipList_Node<KEY, DATA> *SkipList<KEY, DATA>::findNodeUpperBound( const KEY& key) const { Node *locator[k_MAX_NUM_LEVELS]; LockGuard guard(&d_lock); lookupImpUpperBound(locator, key); Node *q = locator[0]; if (q != d_tail_p) { ++q->d_refCount; return q; // RETURN } return 0; } template<class KEY, class DATA> SkipList_Node<KEY, DATA> *SkipList<KEY, DATA>::findNodeLowerBoundR( const KEY& key) const { Node *locator[k_MAX_NUM_LEVELS]; LockGuard guard(&d_lock); lookupImpLowerBoundR(locator, key); Node *q = locator[0]; if (q != d_tail_p) { ++q->d_refCount; return q; // RETURN } return 0; } template<class KEY, class DATA> SkipList_Node<KEY, DATA> *SkipList<KEY, DATA>::findNodeUpperBoundR( const KEY& key) const { Node *locator[k_MAX_NUM_LEVELS]; LockGuard guard(&d_lock); lookupImpUpperBoundR(locator, key); Node *q = locator[0]; if (q != d_tail_p) { ++q->d_refCount; return q; // RETURN } return 0; } template<class KEY, class DATA> SkipList_Node<KEY, DATA> *SkipList<KEY, DATA>::frontNode() const { LockGuard guard(&d_lock); Node *node = d_head_p->d_ptrs[0].d_next_p; if (node == d_tail_p) { return 0; // RETURN } ++node->d_refCount; return node; } template<class KEY, class DATA> void SkipList<KEY, DATA>::lookupImpLowerBound(Node *location[], const KEY& key) const { Node *p = d_head_p; for (int k = d_listLevel; k >= 0; --k) { Node *q = p->d_ptrs[k].d_next_p; while (q != d_tail_p && q->d_key < key) { p = q; q = p->d_ptrs[k].d_next_p; } location[k] = q; } BSLS_ASSERT_SAFE(d_head_p != location[0]); } template<class KEY, class DATA> void SkipList<KEY, DATA>::lookupImpLowerBoundR(Node *location[], const KEY& key) const { Node *q = d_tail_p; for (int k = d_listLevel; k >= 0; --k) { Node *p = q->d_ptrs[k].d_prev_p; while (p != d_head_p && !(p->d_key < key)) { q = p; p = p->d_ptrs[k].d_prev_p; } location[k] = q; } BSLS_ASSERT_SAFE(d_head_p != location[0]); } template<class KEY, class DATA> void SkipList<KEY, DATA>::lookupImpUpperBound(Node *location[], const KEY& key) const { Node *p = d_head_p; for (int k = d_listLevel; k >= 0; --k) { Node *q = p->d_ptrs[k].d_next_p; while (q != d_tail_p && !(key < q->d_key)) { p = q; q = p->d_ptrs[k].d_next_p; } location[k] = q; } BSLS_ASSERT_SAFE(d_head_p != location[0]); } template<class KEY, class DATA> void SkipList<KEY, DATA>::lookupImpUpperBoundR(Node *location[], const KEY& key) const { Node *q = d_tail_p; for (int k = d_listLevel; k >= 0; --k) { Node *p = q->d_ptrs[k].d_prev_p; while (p != d_head_p && key < p->d_key) { q = p; p = q->d_ptrs[k].d_prev_p; } location[k] = q; } BSLS_ASSERT_SAFE(d_head_p != location[0]); } template<class KEY, class DATA> SkipList_Node<KEY, DATA> * SkipList<KEY, DATA>::nextNode(Node *node) const { BSLS_ASSERT(node != d_head_p); BSLS_ASSERT(node != d_tail_p); LockGuard guard(&d_lock); Node *next = node->d_ptrs[0].d_next_p; if (0 == next || d_tail_p == next) { return 0; // RETURN } ++next->d_refCount; return next; } template<class KEY, class DATA> SkipList_Node<KEY, DATA> * SkipList<KEY, DATA>::prevNode(Node *node) const { BSLS_ASSERT(node != d_head_p); BSLS_ASSERT(node != d_tail_p); LockGuard guard(&d_lock); if (0 == node->d_ptrs[0].d_next_p) { return 0; // RETURN } Node *prev = node->d_ptrs[0].d_prev_p; if (d_head_p == prev) { return 0; // RETURN } ++prev->d_refCount; return prev; } template<class KEY, class DATA> int SkipList<KEY, DATA>::skipBackward(Node **node) const { BSLS_ASSERT(node); Node *current = *node; BSLS_ASSERT(current); BSLS_ASSERT(current != d_head_p); BSLS_ASSERT(current != d_tail_p); LockGuard guard(&d_lock); if (0 == current->d_ptrs[0].d_next_p) { // The node is no longer on the list. return e_NOT_FOUND; // RETURN } const int count = --current->d_refCount; BSLS_ASSERT(0 < count); (void) count; // suppress 'unused variable' warnings Node *prev = current->d_ptrs[0].d_prev_p; if (d_head_p == prev) { *node = 0; return 0; // RETURN } ++prev->d_refCount; *node = prev; return 0; } template<class KEY, class DATA> int SkipList<KEY, DATA>::skipForward(Node **node) const { BSLS_ASSERT(node); Node *current = *node; BSLS_ASSERT(current); BSLS_ASSERT(current != d_head_p); BSLS_ASSERT(current != d_tail_p); LockGuard guard(&d_lock); if (0 == current->d_ptrs[0].d_next_p) { // The node is no longer on the list. return e_NOT_FOUND; // RETURN } const int count = --current->d_refCount; BSLS_ASSERT(0 < count); (void) count; // suppress 'unused variable' warnings Node *next = current->d_ptrs[0].d_next_p; if (d_tail_p == next) { *node = 0; return 0; // RETURN } ++next->d_refCount; *node = next; return 0; } // CLASS METHODS template<class KEY, class DATA> inline int SkipList<KEY, DATA>::level(const Pair *reference) { BSLS_ASSERT(reference); Node *node = pairToNode(reference); return node->d_level; } // CREATORS template<class KEY, class DATA> SkipList<KEY, DATA>::SkipList(bslma::Allocator *basicAllocator) : d_listLevel(0) , d_length(0) , d_poolManager_p(0) , d_allocator_p(bslma::Default::allocator(basicAllocator)) { initialize(); } template<class KEY, class DATA> SkipList<KEY, DATA>::SkipList(const SkipList& original, bslma::Allocator *basicAllocator) : d_listLevel(0) , d_length(0) , d_poolManager_p(0) , d_allocator_p(bslma::Default::allocator(basicAllocator)) { initialize(); *this = original; } template<class KEY, class DATA> SkipList<KEY, DATA>::~SkipList() { #if defined(BSLS_ASSERT_SAFE_IS_ACTIVE) checkInvariants(); #endif for (Node *p = d_tail_p->d_ptrs[0].d_prev_p; d_head_p != p; ) { BSLS_ASSERT(1 == p->d_refCount); Node *condemned = p; p = p->d_ptrs[0].d_prev_p; condemned->d_ptrs[0].d_next_p = 0; releaseNode(condemned); } PoolUtil::deallocate(d_poolManager_p, d_head_p); PoolUtil::deallocate(d_poolManager_p, d_tail_p); PoolUtil::deletePoolManager(d_allocator_p, d_poolManager_p); } // MANIPULATORS template<class KEY, class DATA> SkipList<KEY, DATA>& SkipList<KEY, DATA>::operator=(const SkipList& rhs) { if (&rhs == this) { return *this; // RETURN } DoubleLockGuard guard(&d_lock, &rhs.d_lock); // first empty this list removeAllMaybeUnlock(static_cast<bsl::vector<Pair *> *>(0), false); // Now get handles to all of 'rhs's elements. Since rhs.d_lock is locked, // we need to do all operations manually because the important functions of // 'rhs' (like frontNode and nextNode) will lock the mutex. bsl::vector<PairHandle> rhsElements; rhsElements.reserve(rhs.d_length); for (Node *node = rhs.d_head_p->d_ptrs[0].d_next_p; node && node != rhs.d_tail_p; node = node->d_ptrs[0].d_next_p) { ++node->d_refCount; rhsElements.insert(rhsElements.end(), PairHandle())->reset( &rhs, reinterpret_cast<Pair *>(node)); } // Note that unlocking 'rhs.d_lock' here causes a data race if another // thread calls 'update' or 'updateR' on a node in 'rhs'. for (typename bsl::vector<PairHandle>::iterator it = rhsElements.begin(); it != rhsElements.end(); ++it) { Node *node = allocateNode(d_rand.randomLevel(), it->key(), it->data()); addNodeImpR(0, node, false); // false -> do not lock (already locked) } return *this; } template<class KEY, class DATA> inline void SkipList<KEY, DATA>::releaseReferenceRaw(const Pair *reference) { Node *node = pairToNode(reference); releaseNode(node); } // Insertion Methods template<class KEY, class DATA> inline void SkipList<KEY, DATA>::add(PairHandle *result, const KEY& key, const DATA& data, bool *newFrontFlag) { Pair *handle; addRaw(&handle, key, data, newFrontFlag); result->reset(this, handle); } template<class KEY, class DATA> inline void SkipList<KEY, DATA>::add(const KEY& key, const DATA& data, bool *newFrontFlag) { Pair **zeroPair = 0; addRaw(zeroPair, key, data, newFrontFlag); } template<class KEY, class DATA> inline void SkipList<KEY, DATA>::addAtLevelRaw(Pair **result, int level, const KEY& key, const DATA& data, bool *newFrontFlag) { Node *node = allocateNode(level, key, data); if (result) { ++node->d_refCount; *result = reinterpret_cast<Pair *>(node); } addNode(newFrontFlag, node); } template<class KEY, class DATA> int SkipList<KEY, DATA>::addAtLevelUniqueRaw(Pair **result, int level, const KEY& key, const DATA& data, bool *newFrontFlag) { Node *node = allocateNode(level, key, data); if (result) { ++node->d_refCount; *result = reinterpret_cast<Pair *>(node); } int ret = addNodeUnique(newFrontFlag, node); if (ret) { if (result) { --node->d_refCount; *result = 0; } releaseNode(node); return ret; // RETURN } return 0; } template<class KEY, class DATA> inline void SkipList<KEY, DATA>::addRaw(Pair **result, const KEY& key, const DATA& data, bool *newFrontFlag) { addAtLevelRaw(result, d_rand.randomLevel(), key, data, newFrontFlag); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::addUnique(PairHandle *result, const KEY& key, const DATA& data, bool *newFrontFlag) { Pair *handle; int rc = addUniqueRaw(&handle, key, data, newFrontFlag); if (0 != rc) { return rc; // RETURN } result->reset(this, handle); return 0; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::addUnique(const KEY& key, const DATA& data, bool *newFrontFlag) { Pair **zeroPair = 0; return addUniqueRaw(zeroPair, key, data, newFrontFlag); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::addUniqueRaw(Pair **result, const KEY& key, const DATA& data, bool *newFrontFlag) { return addAtLevelUniqueRaw(result, d_rand.randomLevel(), key, data, newFrontFlag); } // Insertion Methods (Reverse Search) template<class KEY, class DATA> inline void SkipList<KEY, DATA>::addAtLevelRawR(Pair **result, int level, const KEY& key, const DATA& data, bool *newFrontFlag) { Node *node = allocateNode(level, key, data); if (result) { ++node->d_refCount; *result = reinterpret_cast<Pair *>(node); } addNodeR(newFrontFlag, node); } template<class KEY, class DATA> int SkipList<KEY, DATA>::addAtLevelUniqueRawR(Pair **result, int level, const KEY& key, const DATA& data, bool *newFrontFlag) { Node *node = allocateNode(level, key, data); if (result) { ++node->d_refCount; *result = reinterpret_cast<Pair *>(node); } int ret = addNodeUniqueR(newFrontFlag, node); if (ret) { if (result) { --node->d_refCount; *result = 0; } releaseNode(node); return ret; // RETURN } return 0; } template<class KEY, class DATA> inline void SkipList<KEY, DATA>::addR(PairHandle *result, const KEY& key, const DATA& data, bool *newFrontFlag) { Pair *handle; addRawR(&handle, key, data, newFrontFlag); result->reset(this, handle); } template<class KEY, class DATA> inline void SkipList<KEY, DATA>::addR(const KEY& key, const DATA& data, bool *newFrontFlag) { Pair **zeroPair = 0; addRawR(zeroPair, key, data, newFrontFlag); } template<class KEY, class DATA> inline void SkipList<KEY, DATA>::addRawR(Pair **result, const KEY& key, const DATA& data, bool *newFrontFlag) { addAtLevelRawR(result, d_rand.randomLevel(), key, data, newFrontFlag); } template<class KEY, class DATA> int SkipList<KEY, DATA>::addUniqueR(PairHandle *result, const KEY& key, const DATA& data, bool *newFrontFlag) { Pair *handle; int rc = addUniqueRawR(&handle, key, data, newFrontFlag); if (0 != rc) { return rc; // RETURN } result->reset(this, handle); return 0; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::addUniqueR(const KEY& key, const DATA& data, bool *newFrontFlag) { Pair **zeroPair = 0; return addUniqueRawR(zeroPair, key, data, newFrontFlag); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::addUniqueRawR(Pair **result, const KEY& key, const DATA& data, bool *newFrontFlag) { return addAtLevelUniqueRawR(result, d_rand.randomLevel(), key, data, newFrontFlag); } // Removal Methods template<class KEY, class DATA> inline int SkipList<KEY, DATA>::popFront(PairHandle *item) { Node *node = popFrontImp(); if (!node) { return e_NOT_FOUND; // RETURN } if (item) { item->reset(this, reinterpret_cast<Pair *>(node)); } else { releaseNode(node); } return 0; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::popFrontRaw(Pair **item) { Node *node = popFrontImp(); if (!node) { return e_NOT_FOUND; // RETURN } if (item) { *item = reinterpret_cast<Pair *>(node); } else { releaseNode(node); } return 0; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::remove(const Pair *reference) { if (0 == reference) { return e_INVALID; // RETURN } Node *node = pairToNode(reference); int ret = removeNode(node); if (ret) { return ret; // RETURN } releaseNode(node); return 0; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::removeAll() { return removeAllImp(static_cast<bsl::vector<PairHandle> *>(0)); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::removeAll(bsl::vector<PairHandle> *removed) { return removeAllImp(removed); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::removeAll(std::vector<PairHandle> *removed) { return removeAllImp(removed); } #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR template<class KEY, class DATA> inline int SkipList<KEY, DATA>::removeAll(std::pmr::vector<PairHandle> *removed) { return removeAllImp(removed); } #endif template<class KEY, class DATA> inline int SkipList<KEY, DATA>::removeAllRaw(bsl::vector<Pair *> *removed) { return removeAllImp(removed); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::removeAllRaw(std::vector<Pair *> *removed) { return removeAllImp(removed); } #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR template<class KEY, class DATA> inline int SkipList<KEY, DATA>::removeAllRaw(std::pmr::vector<Pair *> *removed) { return removeAllImp(removed); } #endif // Update Methods template<class KEY, class DATA> inline int SkipList<KEY, DATA>::update(const Pair *reference, const KEY& newKey, bool *newFrontFlag, bool allowDuplicates) { if (0 == reference) { return e_INVALID; // RETURN } Node *node = pairToNode(reference); return updateNode(newFrontFlag, node, newKey, allowDuplicates); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::updateR(const Pair *reference, const KEY& newKey, bool *newFrontFlag, bool allowDuplicates) { if (0 == reference) { return e_INVALID; // RETURN } Node *node = pairToNode(reference); return updateNodeR(newFrontFlag, node, newKey, allowDuplicates); } // ACCESSORS template<class KEY, class DATA> inline SkipListPair<KEY, DATA> * SkipList<KEY, DATA>::addPairReferenceRaw(const Pair *reference) const { Node *node = pairToNode(reference); ++node->d_refCount; return const_cast<Pair *>(reference); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::back(PairHandle *back) const { Pair *backPtr = reinterpret_cast<Pair *>(backNode()); if (backPtr) { back->reset(this, backPtr); return 0; // RETURN } return -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::backRaw(Pair **back) const { *back = reinterpret_cast<Pair *>(backNode()); return *back ? 0 : -1; } template<class KEY, class DATA> bool SkipList<KEY, DATA>::exists(const KEY& key) const { Node *locator[k_MAX_NUM_LEVELS]; LockGuard guard(&d_lock); lookupImpLowerBound(locator, key); Node *q = locator[0]; if (q != d_tail_p && q->d_key == key) { return true; // RETURN } return false; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::front(PairHandle *front) const { BSLS_ASSERT(front); Pair *frontPtr = reinterpret_cast<Pair *>(frontNode()); if (frontPtr) { front->reset(this, frontPtr); return 0; // RETURN } return -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::frontRaw(Pair **front) const { BSLS_ASSERT(front); *front = reinterpret_cast<Pair *>(frontNode()); return *front ? 0 : -1; } template<class KEY, class DATA> inline bool SkipList<KEY, DATA>::isEmpty() const { LockGuard guard(&d_lock); return d_tail_p == d_head_p->d_ptrs[0].d_next_p; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::length() const { LockGuard guard(&d_lock); return d_length; } template<class KEY, class DATA> bsl::ostream& SkipList<KEY, DATA>::print(bsl::ostream& stream, int level, int spacesPerLevel) const { if (stream.bad()) { return stream; // RETURN } bdlb::Print::indent(stream, level, spacesPerLevel); LockGuard guard(&d_lock); // Now we must do all operations manually, since all important functions // like frontNode() and nextNode will lock the mutex if (0 <= spacesPerLevel) { // Multi-line output. if (level < 0) { level = -level; } stream << "[\n"; const int levelPlus1 = level + 1; for (Node *node = d_head_p->d_ptrs[0].d_next_p; node && node != d_tail_p; node = node->d_ptrs[0].d_next_p) { bdlb::Print::indent(stream, levelPlus1, spacesPerLevel); stream << "[\n"; const int levelPlus2 = level + 2; bdlb::Print::indent(stream, levelPlus2, spacesPerLevel); stream << "level = " << node->d_level << "\n"; bdlb::PrintMethods::print(stream, node->d_key, levelPlus2, spacesPerLevel); bdlb::Print::indent(stream, levelPlus2, spacesPerLevel); stream << "=>\n"; bdlb::PrintMethods::print(stream, node->d_data, levelPlus2, spacesPerLevel); bdlb::Print::indent(stream, levelPlus1, spacesPerLevel); stream << "]\n"; } bdlb::Print::indent(stream, level, spacesPerLevel); stream << "]\n"; } else { // Output on a single line and suppress any further indentation. stream << "["; for (Node *node = d_head_p->d_ptrs[0].d_next_p; node && node != d_tail_p; node = node->d_ptrs[0].d_next_p) { stream << "[ (level = " << node->d_level << ") "; bdlb::PrintMethods::print(stream, node->d_key, 0, -1); stream << " => "; bdlb::PrintMethods::print(stream, node->d_data, 0, -1); stream << " ]"; } stream << "]"; } return stream << bsl::flush; } // simple forward finds template<class KEY, class DATA> inline int SkipList<KEY, DATA>::find(PairHandle *item, const KEY& key) const { BSLS_ASSERT(item); Pair *itemPtr = reinterpret_cast<Pair *>(findNode(key)); if (itemPtr) { item->reset(this, itemPtr); return 0; // RETURN } return -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findRaw(Pair **item, const KEY& key) const { BSLS_ASSERT(item); *item = reinterpret_cast<Pair *>(findNode(key)); return *item ? 0 : -1; } // simple reverse finds template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findR(PairHandle *item, const KEY& key) const { BSLS_ASSERT(item); Pair *itemPtr = reinterpret_cast<Pair *>(findNodeR(key)); if (itemPtr) { item->reset(this, itemPtr); return 0; // RETURN } return -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findRRaw(Pair **item, const KEY& key) const { BSLS_ASSERT(item); *item = reinterpret_cast<Pair *>(findNodeR(key)); return *item ? 0 : -1; } // find lower bound template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findLowerBound(PairHandle *item, const KEY& key) const { BSLS_ASSERT(item); Pair *itemPtr = reinterpret_cast<Pair *>(findNodeLowerBound(key)); if (itemPtr) { item->reset(this, itemPtr); return 0; // RETURN } return -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findLowerBoundRaw(Pair **item, const KEY& key) const { BSLS_ASSERT(item); *item = reinterpret_cast<Pair *>(findNodeLowerBound(key)); return *item ? 0 : -1; } // find lower bound reverse template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findLowerBoundR(PairHandle *item, const KEY& key) const { BSLS_ASSERT(item); Pair *itemPtr = reinterpret_cast<Pair *>(findNodeLowerBoundR(key)); if (itemPtr) { item->reset(this, itemPtr); return 0; // RETURN } return -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findLowerBoundRRaw(Pair **item, const KEY& key) const { BSLS_ASSERT(item); *item = reinterpret_cast<Pair *>(findNodeLowerBoundR(key)); return *item ? 0 : -1; } // find upper bound template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findUpperBound(PairHandle *item, const KEY& key) const { BSLS_ASSERT(item); Pair *itemPtr = reinterpret_cast<Pair *>(findNodeUpperBound(key)); if (itemPtr) { item->reset(this, itemPtr); return 0; // RETURN } return -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findUpperBoundRaw(Pair **item, const KEY& key) const { BSLS_ASSERT(item); *item = reinterpret_cast<Pair *>(findNodeUpperBound(key)); return *item ? 0 : -1; } // find upper bound reverse template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findUpperBoundR(PairHandle *item, const KEY& key) const { BSLS_ASSERT(item); Pair *itemPtr = reinterpret_cast<Pair *>(findNodeUpperBoundR(key)); if (itemPtr) { item->reset(this, itemPtr); return 0; // RETURN } return -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::findUpperBoundRRaw(Pair **item, const KEY& key) const { BSLS_ASSERT(item); *item = reinterpret_cast<Pair *>(findNodeUpperBoundR(key)); return *item ? 0 : -1; } // next, previous, & skip* template<class KEY, class DATA> inline int SkipList<KEY, DATA>::next(PairHandle *next, const Pair *reference) const { if (0 == reference) { return e_INVALID; // RETURN } Node *node = pairToNode(reference); Node *nNode = nextNode(node); if (nNode) { next->reset(this, reinterpret_cast<Pair *>(nNode)); return 0; // RETURN } return -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::nextRaw(Pair **next, const Pair *reference) const { BSLS_ASSERT(next); BSLS_ASSERT(reference); Node *node = pairToNode(reference); *next = reinterpret_cast<Pair *>(nextNode(node)); return *next ? 0 : -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::previous(PairHandle *prevPair, const Pair *reference) const { if (0 == reference) { return e_INVALID; // RETURN } Node *node = pairToNode(reference); Node *pNode = prevNode(node); if (pNode) { prevPair->reset(this, reinterpret_cast<Pair *>(pNode)); return 0; // RETURN } return -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::previousRaw(Pair **prevPair, const Pair *reference) const { BSLS_ASSERT(prevPair); BSLS_ASSERT(reference); Node *node = pairToNode(reference); *prevPair = reinterpret_cast<Pair *>(prevNode(node)); return *prevPair ? 0 : -1; } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::skipBackward(PairHandle *item) const { BSLS_ASSERT(item->isValid()); Node **node_p = reinterpret_cast<Node **>(&item->d_node_p); return skipBackward(node_p); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::skipBackwardRaw(Pair **item) const { BSLS_ASSERT(item); Node **node_p = reinterpret_cast<Node **>(item); return skipBackward(node_p); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::skipForward(PairHandle *item) const { BSLS_ASSERT(item->isValid()); Node **node_p = reinterpret_cast<Node **>(&item->d_node_p); return skipForward(node_p); } template<class KEY, class DATA> inline int SkipList<KEY, DATA>::skipForwardRaw(Pair **item) const { BSLS_ASSERT(item); Node **node_p = reinterpret_cast<Node **>(item); return skipForward(node_p); } // Aspects template<class KEY, class DATA> inline bslma::Allocator *SkipList<KEY, DATA>::allocator() const { return d_allocator_p; } } // close package namespace // FREE OPERATORS template<class KEY, class DATA> bool bdlcc::operator==(const SkipList<KEY, DATA>& lhs, const SkipList<KEY, DATA>& rhs) { if (&lhs == &rhs) { return true; // RETURN } bdlcc::SkipList_DoubleLockGuard guard(&lhs.d_lock, &rhs.d_lock); // Once we have locked the lists, we need to do all operations manually // because the important functions of the lists (like frontNode and // nextNode) will lock the mutex. for (SkipList_Node<KEY, DATA> *lhsNode = lhs.d_head_p->d_ptrs[0].d_next_p, *rhsNode = rhs.d_head_p->d_ptrs[0].d_next_p; ; lhsNode = lhsNode->d_ptrs[0].d_next_p, rhsNode = rhsNode->d_ptrs[0].d_next_p) { if ((!lhsNode && !rhsNode) || (lhsNode == lhs.d_tail_p && rhsNode == rhs.d_tail_p)) { // we reached the end of both lists at the same time return true; // RETURN } if (!lhsNode || !rhsNode || lhsNode == lhs.d_tail_p || rhsNode == rhs.d_tail_p) { // We reached the end of one list before the other return false; // RETURN } if (!(lhsNode->d_key == rhsNode->d_key && lhsNode->d_data == rhsNode->d_data)) { return false; // RETURN } } BSLS_ASSERT(!"unreachable"); return false; } template<class KEY, class DATA> bool bdlcc::operator!=(const SkipList<KEY, DATA>& lhs, const SkipList<KEY, DATA>& rhs) { if (&lhs == &rhs) { return false; // RETURN } bdlcc::SkipList_DoubleLockGuard guard(&lhs.d_lock, &rhs.d_lock); // Once we have locked the lists, we need to do all operations manually // because the important functions of the lists (like frontNode and // nextNode) will lock the mutex. for (SkipList_Node<KEY, DATA> *lhsNode = lhs.d_head_p->d_ptrs[0].d_next_p, *rhsNode = rhs.d_head_p->d_ptrs[0].d_next_p; ; lhsNode = lhsNode->d_ptrs[0].d_next_p, rhsNode = rhsNode->d_ptrs[0].d_next_p) { if ((!lhsNode && !rhsNode) || (lhsNode == lhs.d_tail_p && rhsNode == rhs.d_tail_p)) { // we reached the end of both lists at the same time return false; // RETURN } if (!lhsNode || !rhsNode || lhsNode == lhs.d_tail_p || rhsNode == rhs.d_tail_p) { // We reached the end of one list before the other return true; // RETURN } if (!(lhsNode->d_key == rhsNode->d_key) || !(lhsNode->d_data == rhsNode->d_data)) { return true; // RETURN } } BSLS_ASSERT(!"unreachable"); return false; } template<class KEY, class DATA> inline bsl::ostream& bdlcc::operator<<(bsl::ostream& stream, const SkipList<KEY, DATA>& list) { return list.print(stream, 0, -1); } } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2015 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------