671 lines
24 KiB
C++
671 lines
24 KiB
C++
//--------------------------------------------------------------------------
|
|
//
|
|
// Copyright (C) 1992, Microsoft Corporation.
|
|
//
|
|
// File: skiplist.hxx
|
|
//
|
|
// Contents: Skip list classes.
|
|
//
|
|
// CSkipList -- A class that implements skip lists.
|
|
// The skip list manages the efficient lookup
|
|
// of "Entry"s which are inserted into the skip list
|
|
// by the client. "Entry"s are represented by
|
|
// a pointer passed into the CSkipList::Insert()
|
|
// method by the client.
|
|
// These pointers can be retrieved by the Search()
|
|
// method which takes a pointer to a "Base", (which
|
|
// acts as a key.)
|
|
//
|
|
// "Entry"s are always derived from "Base"s. That is,
|
|
// the CSkipList::Search() method assumes that a
|
|
// simple pointer manipulation can turn a pointer to
|
|
// an "Entry" that it has in the list into a pointer
|
|
// to "Base" that it needs for comparison purposes.
|
|
// Once the pointer is converted, the user-supplied
|
|
// comparison function is called to facilitate the
|
|
// search.
|
|
//
|
|
// An example
|
|
// ----------
|
|
//
|
|
// We have a "Base" class which is a key. Pointers
|
|
// to CKeys will be passed to CSkipList::Search()
|
|
// and to the constructor of CSkipList (the max key)
|
|
//
|
|
// class CKey
|
|
// {
|
|
// int keyvalue; // a simple integer key
|
|
// public:
|
|
// static int Compare(void *pkey1, void *pkey2)
|
|
// {
|
|
// return ((CKey*)pkey1)->keyvalue <
|
|
// ((CKey*)pkey2)->keyvalue;
|
|
// }
|
|
//
|
|
// static int Delete(const *pentry)
|
|
// {
|
|
// delete pentry;
|
|
// }
|
|
// };
|
|
//
|
|
// We have an "Entry" class which is-a key and also
|
|
// is-a CExtra (whatever the client needs/wants.)
|
|
// Pointers to CDataEntrys are passed to
|
|
// CSkipList::Insert. Also, pointers to CDataEntrys
|
|
// are converted to pointers to CKeys by the addition
|
|
// of an offset which is set once for all skip list
|
|
// entries when passed to CSkipList constructor.
|
|
//
|
|
// class CDataEntry : public CExtra, private CKey
|
|
// {
|
|
// char *pszData;
|
|
// };
|
|
//
|
|
// we can easily make a skip list out of the
|
|
// CDataEntries as follows:
|
|
//
|
|
// static int maxkey=32677;
|
|
//
|
|
// CSkipList mysl((LPFNCOMPARE)(CKey::Compare),
|
|
// (LPFNDELETE)(CKey::Delete),
|
|
// OFFSETBETWEEN(CDataEntry, CKey),
|
|
// SKIPLIST_PRIVATE,
|
|
// &maxkey,
|
|
// 10,
|
|
// hr);
|
|
//
|
|
// Notes on parameters:
|
|
// 1. Pass in the address of the comparison function.
|
|
// This comparison function is called by
|
|
// CSkipList::Search(). The first paramter to
|
|
// the compare function is a pointer to the "Base"
|
|
// part of an "Entry" in the skip list (computed by
|
|
// adding offset computed by OFFSETBETWEEN macro).
|
|
// Search() routine makes the determination as to which
|
|
// "Entry"s to compare based on internal details of
|
|
// the skip list mechanism.
|
|
// The second parameter to the Compare function is the
|
|
// "Base" pointer parameter passed to Search.
|
|
//
|
|
// 2. Pass in the address of the deletion function.
|
|
// This function is called only as part of the
|
|
// destructor of CSkipList and allows the deletion
|
|
// of any entries left in the skip list. If
|
|
// the skip list is used in such a way as to not
|
|
// "own" the pointers within, then this function
|
|
// will simple return after doing nothing.
|
|
// If the skip list is used in such a way as to
|
|
// actually "own" the objects inserted, then
|
|
// the user-supplied delete function should be used
|
|
// to cleanup correctly.
|
|
//
|
|
// 3. Since all entries in the list must be used as both
|
|
// "key" and "data", the EntryToKeyOffset gives
|
|
// the offset in bytes between the "Entry" part and
|
|
// the "Base" part. In the example, the offset would
|
|
// be the size of CExtra.
|
|
//
|
|
// See method description for details on other parameters.
|
|
//
|
|
// Insert an entry:
|
|
// ----------------
|
|
// mysl.Insert(new CDataEntry);
|
|
//
|
|
// Lookup an entry:
|
|
// ----------------
|
|
// CKey key(some param to ctr);
|
|
// mysl.Search(&key); // returns a pointer to respective
|
|
// // CDataEntry *
|
|
//
|
|
// CSkipListEntry -- class used privately by CSkipList.
|
|
//
|
|
// History: 06-May-92 Ricksa Created
|
|
// 18-May-94 BruceMa Fixed scm memory leak in SKIP_LIST_ENTRY
|
|
// 28-Jun-94 BruceMa Memory sift fixes
|
|
// 04-Oct-94 BillMo Changed from macro to class implementation.
|
|
// Added comments above and to skip list methods
|
|
// after demacroization.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#ifndef __SKIPLIST_HXX__
|
|
#define __SKIPLIST_HXX__
|
|
|
|
#include <scmmem.hxx>
|
|
|
|
// Used by insert/delete algorithms to preallocate array on stack.
|
|
// This is valid for skip lists with <= 64K elements.
|
|
#define SKIP_LIST_MAX 16
|
|
|
|
// Set up the generator for skip list levels
|
|
void InitSkLevelGenerator(void);
|
|
|
|
// Get a level from the generator
|
|
int GetSkLevel(int cMax);
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Macro: OFFSETBETWEEN
|
|
//
|
|
// Parameters: [Entry] -- class name of the class which forms entries
|
|
// in the skip list. see example below.
|
|
// MUST BE DERIVED from class named in parameter
|
|
// 'Base'
|
|
//
|
|
// [Base] -- class name of the class which is the 'key'
|
|
// for the skip list entries
|
|
//
|
|
// Purpose: Encapsulate calculation of offset that the skip list uses
|
|
// to convert pointers to "Entrys" to pointers to "Bases"
|
|
//
|
|
// History: 04-Nov-94 Ricksa Created
|
|
//
|
|
// Notes: VERY IMPORTANT NOTE: "Entry" must be derived from "Base"
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#define OFFSETBETWEEN(Entry, Base) \
|
|
((char*)( (const Base*)((const Entry*)0x1000 ))) - \
|
|
((char*)( (const Base*)0x1000))
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// The following defines control how allocation is done by the skip list.
|
|
// The fSharedAlloc parameter of CSkipList::CSkipList can either be:
|
|
//
|
|
// SKIPLIST_SHARED -- in x86 Windows builds will cause the shared allocator
|
|
// to be used for CSkipListEntrys.
|
|
// (In NT builds will currently be privately allocated.)
|
|
//
|
|
// SKIPLIST_PRIVATE -- in all build environments use PrivMemAlloc.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#define SKIPLIST_SHARED TRUE
|
|
#define SKIPLIST_PRIVATE FALSE
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Documentation aid:
|
|
//
|
|
// Since CSkipLists do not know the actual classes to be inserted/deleted
|
|
// there will always be a cast involved in using the comparison function.
|
|
// For this reason the typedefs below act as a documentation aid and
|
|
// reminder that the pointers are expected to point to particular
|
|
// meta-types of objects.
|
|
//
|
|
// "Base" is used where a pointer to the "key" is expected. (e.g. the
|
|
// Search method.)
|
|
//
|
|
// "Entry" is used where a pointer to the derived class (data+key) is
|
|
// expected (e.g. the Insert method.)
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
typedef void Base;
|
|
typedef void Entry;
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Some type definitions.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
class CSkipListEntry;
|
|
|
|
// note: the name CSkipListEnum is used so that the details of the
|
|
// enumerator are hidden and can be expanded in the future without
|
|
// have to modify the source of clients.
|
|
|
|
typedef CSkipListEntry * CSkipListEnum;
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function type: LPFNCOMPARE
|
|
//
|
|
// Purpose: Provide parameter signature for comparison function used
|
|
// by skip lists. The user implementation should
|
|
// compare the two keys and return <0, >0 or ==0.
|
|
//
|
|
// Arguments: [pkey1] -- pointer to the "Base" part of an "Entry" in
|
|
// the skip list as calculated by adding the
|
|
// "EntryToKeyOffset" value (passed to CSkipList
|
|
// constructor) to the address of an "Entry" in the
|
|
// list.
|
|
// [pkey2] -- the address passed to CSkipList::Search which is
|
|
// the address of a "Base" key being used to locate
|
|
// the respective entry in skip list.
|
|
//
|
|
// History: 04-Nov-94 BillMo Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
typedef int (*LPFNCOMPARE)(Base *pkey1, Base *pkey2);
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function type: LPFNDELETE
|
|
//
|
|
// Purpose: Provide parameter signature for deletion function used
|
|
// by skip list's destructor. The user implementation should
|
|
// delete the entry as appropriate (i.e. should delete it
|
|
// if the destruction of the skip list without deleting
|
|
// would cause leaks.)
|
|
//
|
|
// i.e. if the skip list owns the pointers, delete them in this
|
|
// function, otherwise don't.
|
|
//
|
|
// Arguments: [pentry] -- pointer to an "Entry" part of an "Entry"
|
|
// originally passed to CSkipList::Insert.
|
|
//
|
|
// History: 04-Nov-94 BillMo Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
typedef void (*LPFNDELETE)(Entry *);
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Class: CSkipList
|
|
//
|
|
// Purpose: Implements head of a skip list.
|
|
//
|
|
// Interface: CSkipList -- constructor
|
|
// ~CSkipList -- destructor (calls user-supplied deletion
|
|
// routine)
|
|
// Search -- find item in list
|
|
// Insert -- add new item to the list (insert the pointer)
|
|
// Remove -- remove item from the list (return the pointer)
|
|
// (this doesn't delete the pointer or call
|
|
// lpfnDelete.)
|
|
// Replace -- replace the entry (MUST be same key value.)
|
|
// First -- get first entry in skip list.
|
|
// Next -- get next entry given by CSkipListEnum param.
|
|
// GetList -- get the list
|
|
//
|
|
// History: 06-May-92 Ricksa Created
|
|
// 04-Nov-94 BillMo Update comments.
|
|
//
|
|
// Notes: CSkipList has a nested class: CSkipListEntry
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
class CSkipList
|
|
{
|
|
public:
|
|
|
|
CSkipList(
|
|
LPFNCOMPARE lpfnCompare,
|
|
LPFNDELETE lpfnDelete,
|
|
const int EntryToKeyOffset,
|
|
BOOL fSharedAlloc,
|
|
Base * pvMaxKey,
|
|
const int cMaxLevel,
|
|
HRESULT & hr);
|
|
|
|
~CSkipList(void);
|
|
|
|
Entry * Search(const Base * skey);
|
|
|
|
Entry * Insert(Entry *pEntryNew);
|
|
|
|
Entry * Remove(Base * BaseKey);
|
|
|
|
Entry * Replace(Base * BaseKey, Entry * pEntryNew);
|
|
|
|
Entry * First(CSkipListEnum *psle);
|
|
|
|
Entry * Next(CSkipListEnum *psle);
|
|
|
|
CSkipListEntry * GetList(void);
|
|
|
|
private:
|
|
|
|
void * Allocate(ULONG cb);
|
|
|
|
void Deallocate(void *pv);
|
|
|
|
CSkipListEntry * FillUpdateArray(const Base *BaseKey,
|
|
CSkipListEntry **ppEntryUpdate);
|
|
|
|
Entry * _Search(const Base * BaseKey,
|
|
CSkipListEntry **ppPrivEntry);
|
|
|
|
int _cCurrentMaxLevel;
|
|
|
|
int _cMaxLevel;
|
|
|
|
CSkipListEntry * _pEntryHead;
|
|
|
|
BOOL _fSharedAlloc;
|
|
|
|
const int _EntryToKeyOffset;
|
|
|
|
LPFNCOMPARE _lpfnCompare;
|
|
|
|
LPFNDELETE _lpfnDelete;
|
|
|
|
|
|
};
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CSkipList::Allocate
|
|
//
|
|
// Synopsis: Allocate the requested number of bytes from the
|
|
// shared heap, or private heap, depending on the
|
|
// state of CSkipList::_fSharedAlloc
|
|
// If _fSharedAlloc is TRUE, then ScmMemAlloc is called,
|
|
// otherwise PrivMemAlloc is used.
|
|
//
|
|
// Arguments: [cb] -- Number of bytes requsted.
|
|
//
|
|
// Returns: Pointer to allocated memory, or NULL on failure.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
inline void *
|
|
CSkipList::Allocate(ULONG cb)
|
|
{
|
|
if (_fSharedAlloc)
|
|
return ScmMemAlloc(cb);
|
|
else
|
|
return PrivMemAlloc(cb);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CSkipList::Deallocate
|
|
//
|
|
// Synopsis: Dellocate the requested block from the
|
|
// shared heap, or private heap, depending on the
|
|
// state of CSkipList::_fSharedAlloc
|
|
// If _fSharedAlloc is TRUE, then ScmMemFree is called,
|
|
// otherwise PrivMemFree is used.
|
|
//
|
|
// Arguments: [pv] -- Pointer to block to free.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
inline void
|
|
CSkipList::Deallocate(void *pv)
|
|
{
|
|
if (_fSharedAlloc)
|
|
ScmMemFree(pv);
|
|
else
|
|
PrivMemFree(pv);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Class: CSkipListEntry
|
|
//
|
|
// Purpose: Implements an entry in a skip list.
|
|
//
|
|
// Interface: Initialize -- initialize object
|
|
// GetSize -- calculate size to allocate for object
|
|
// cLevel -- returns number of forward pointers
|
|
// GetForward -- returns the ith forward pointer
|
|
// SetForward -- sets ith forward pointer
|
|
// GetBase -- return base address of array
|
|
// GetEntry -- get user-supplied object pointer
|
|
// GetKey -- get key of user-supplied object
|
|
//
|
|
// History: 06-May-92 Ricksa Created
|
|
// 04-Nov-94 BillMo Demacroize and update comments.
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
class CSkipListEntry
|
|
{
|
|
private:
|
|
friend class CSkipList;
|
|
|
|
CSkipListEntry(); // declared but not defined
|
|
|
|
void Initialize(Entry * pvEntry,
|
|
const int cEntries,
|
|
BOOL fHead);
|
|
|
|
static int GetSize(const int cEntries);
|
|
|
|
int cLevel(void);
|
|
|
|
CSkipListEntry* GetForward(int iCur);
|
|
|
|
void SetForward(int iCur, CSkipListEntry* pnew);
|
|
|
|
CSkipListEntry ** GetBase(void);
|
|
|
|
Entry * GetEntry(void);
|
|
|
|
Base * GetKey(const int EntryToKeyOffset);
|
|
|
|
|
|
int _cEntries:24;
|
|
int _fHead:8;
|
|
Entry * _pvEntry;
|
|
CSkipListEntry * _apBaseForward[1];
|
|
};
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CSkipListEntry::Initialize
|
|
//
|
|
// Synopsis: Set up member variables of skip list entry
|
|
//
|
|
// Arguments: [pvEntry] -- client-supplied "entry"/"base" pointer
|
|
// [cEntries] -- count of entries requested.
|
|
// [fHead] -- TRUE if head of skip list
|
|
//
|
|
// Returns: size in bytes required for an object to contain requested
|
|
// number of entries.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
inline void
|
|
CSkipListEntry::Initialize(Entry * pvEntry,
|
|
const int cEntries,
|
|
BOOL fHead)
|
|
{
|
|
_pvEntry = pvEntry;
|
|
_cEntries = cEntries;
|
|
_fHead = fHead;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CSkipListEntry::GetSize
|
|
//
|
|
// Synopsis: Calculate the size in bytes required for a CSkipListEntry
|
|
// to contain [cEntries] elements.
|
|
//
|
|
// Arguments: [cEntries] -- count of entries requested.
|
|
//
|
|
// Returns: size in bytes required for an object to contain requested
|
|
// number of entries.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
inline int
|
|
CSkipListEntry::GetSize(const int cEntries)
|
|
{
|
|
return sizeof(CSkipListEntry) +
|
|
(cEntries-1)*sizeof(CSkipListEntry *);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CSkipListEntry::cLevel
|
|
//
|
|
// Synopsis: Returns the level (entry count) of skip list entry.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
inline int CSkipListEntry::cLevel(void)
|
|
{
|
|
return _cEntries;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CSkipListEntry::GetForward
|
|
//
|
|
// Synopsis: Return the [iCur]'th forward pointer.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
inline CSkipListEntry* CSkipListEntry::GetForward(int iCur)
|
|
{
|
|
return _apBaseForward[iCur];
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CSkipListEntry::SetForward
|
|
//
|
|
// Synopsis: Set the [iCur]'th forward pointer to [pnew]
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
inline void CSkipListEntry::SetForward(int iCur, CSkipListEntry* pnew)
|
|
{
|
|
_apBaseForward[iCur] = pnew;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CSkipListEntry::GetBase
|
|
//
|
|
// Synopsis: Get the base address of the forward pointer array.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
inline CSkipListEntry **CSkipListEntry::GetBase(void)
|
|
{
|
|
return _apBaseForward;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CSkipListEntry::GetEntry
|
|
//
|
|
// Synopsis: Get pointer to the client-supplied object that was
|
|
// inserted into the skiplist using CSkipList::Insert.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
inline Entry * CSkipListEntry::GetEntry(void)
|
|
{
|
|
Win4Assert(!_fHead);
|
|
return(_pvEntry);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CSkipListEntry::GetKey
|
|
//
|
|
// Synopsis: Return a pointer to the key of the object passed into
|
|
// CSkipList::Insert or CSkipList::CSkipList.
|
|
//
|
|
// Arguments: [EntryToKeyOffset] -- amount to add to the address of
|
|
// the client-supplied entry in
|
|
// order to get the address of the
|
|
// key.
|
|
//
|
|
//
|
|
// Returns: Pointer to "Base", otherwise known as key, of client
|
|
// inserted object (or max key)
|
|
//
|
|
// Notes: If _fHead is TRUE, this indicates that the skip list
|
|
// entry is the one allocated by CSkipList::CSkipList.
|
|
// In this case, the pointer to the user-supplied entry
|
|
// is actually a pointer to the key (since we don't need
|
|
// a full entry object for use as a maximum key value.)
|
|
// If _fHead is FALSE, then the object was allocated by
|
|
// CSkipList::Insert and thus the entry pointer needs
|
|
// to be adjusted in order to get the address of the key.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
inline Base * CSkipListEntry::GetKey(const int EntryToKeyOffset)
|
|
{
|
|
if (_fHead)
|
|
return (Base*)_pvEntry;
|
|
else
|
|
return (Base*) (((char*)_pvEntry)+EntryToKeyOffset);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Macro: DEFINE_TYPE_SAFE_SKIPLIST
|
|
//
|
|
// Purpose: Generates a wrapper class to provide type safe skip lists.
|
|
//
|
|
// Arguments: [NewType] -- The name of the new class (derives privately
|
|
// from CSkipList.
|
|
// [EntryType] -- type of entry (must be derived from BaseType.
|
|
// [BaseType] -- type of key
|
|
//
|
|
// History: 04-Nov-94 BillMo Created.
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#define DEFINE_TYPE_SAFE_SKIPLIST(NewType,EntryType,BaseType)\
|
|
class NewType : private CSkipList\
|
|
{\
|
|
public:\
|
|
inline NewType(\
|
|
LPFNCOMPARE lpfnCompare,\
|
|
LPFNDELETE lpfnDelete,\
|
|
const int EntryToKeyOffset,\
|
|
BOOL fSharedAlloc,\
|
|
Base * pvMaxKey,\
|
|
const int cMaxLevel,\
|
|
HRESULT & hr) :\
|
|
CSkipList(lpfnCompare,\
|
|
lpfnDelete,\
|
|
EntryToKeyOffset,\
|
|
fSharedAlloc,\
|
|
pvMaxKey,\
|
|
cMaxLevel,\
|
|
hr) {}\
|
|
inline EntryType * Search(const BaseType * skey)\
|
|
{\
|
|
return (EntryType*) CSkipList::Search(skey);\
|
|
}\
|
|
inline EntryType * Insert(EntryType *pEntryNew)\
|
|
{\
|
|
return (EntryType*) CSkipList::Insert(pEntryNew);\
|
|
}\
|
|
inline EntryType * Remove(BaseType * BaseKey)\
|
|
{\
|
|
return (EntryType*) CSkipList::Remove(BaseKey);\
|
|
}\
|
|
inline EntryType * Replace(BaseType * BaseKey, EntryType * pEntryNew)\
|
|
{\
|
|
return (EntryType*) CSkipList::Replace(BaseKey, pEntryNew);\
|
|
}\
|
|
inline EntryType * First(CSkipListEnum *psle)\
|
|
{\
|
|
return (EntryType*) CSkipList::First(psle);\
|
|
}\
|
|
inline EntryType * Next(CSkipListEnum *psle)\
|
|
{\
|
|
return (EntryType*) CSkipList::Next(psle);\
|
|
}\
|
|
inline CSkipListEntry * GetList(void)\
|
|
{\
|
|
return CSkipList::GetList();\
|
|
}\
|
|
};
|
|
|
|
#endif // __SKIPLIST_HXX__
|
|
|