// See the file "COPYING" in the main distribution directory for copyright. #pragma once // BaseList.h -- // Interface for class BaseList, current implementation is as an // array of ent's. This implementation was chosen to optimize // getting to the ent's rather than inserting and deleting. // Also pairs of append's and get's act like push's and pop's // and are very efficient. The only really expensive operations // are inserting (but not appending), which requires pushing every // element up, and resizing the list, which involves getting new space // and moving the data. Resizing occurs automatically when inserting // more elements than the list can currently hold. Automatic // resizing is done by growing by GROWTH_FACTOR at a time and // always increases the size of the list. Resizing to zero // (or to less than the current value of num_entries) // will decrease the size of the list to the current number of // elements. Resize returns the new max_entries. // // Entries must be either a pointer to the data or nonzero data with // sizeof(data) <= sizeof(void*). #include #include #include #include #include "zeek/util.h" namespace zeek { enum class ListOrder : int { ORDERED, UNORDERED }; template class List { public: constexpr static int DEFAULT_LIST_SIZE = 10; constexpr static int LIST_GROWTH_FACTOR = 2; ~List() { free(entries); } explicit List(int size = 0) { num_entries = 0; if ( size <= 0 ) { max_entries = 0; entries = nullptr; return; } max_entries = size; entries = (T*)util::safe_malloc(max_entries * sizeof(T)); } List(const List& b) { max_entries = b.max_entries; num_entries = b.num_entries; if ( max_entries ) entries = (T*)util::safe_malloc(max_entries * sizeof(T)); else entries = nullptr; for ( int i = 0; i < num_entries; ++i ) entries[i] = b.entries[i]; } List(List&& b) { entries = b.entries; num_entries = b.num_entries; max_entries = b.max_entries; b.entries = nullptr; b.num_entries = b.max_entries = 0; } List(const T* arr, int n) { num_entries = max_entries = n; entries = (T*)util::safe_malloc(max_entries * sizeof(T)); memcpy(entries, arr, n * sizeof(T)); } List(std::initializer_list il) : List(il.begin(), il.size()) {} List& operator=(const List& b) { if ( this == &b ) return *this; free(entries); max_entries = b.max_entries; num_entries = b.num_entries; if ( max_entries ) entries = (T*)util::safe_malloc(max_entries * sizeof(T)); else entries = nullptr; for ( int i = 0; i < num_entries; ++i ) entries[i] = b.entries[i]; return *this; } List& operator=(List&& b) { if ( this == &b ) return *this; free(entries); entries = b.entries; num_entries = b.num_entries; max_entries = b.max_entries; b.entries = nullptr; b.num_entries = b.max_entries = 0; return *this; } // Return nth ent of list (do not remove). T& operator[](int i) const { return entries[i]; } void clear() // remove all entries { free(entries); entries = nullptr; num_entries = max_entries = 0; } bool empty() const noexcept { return num_entries == 0; } size_t size() const noexcept { return num_entries; } int length() const { return num_entries; } int max() const { return max_entries; } int resize(int new_size = 0) // 0 => size to fit current number of entries { if ( new_size < num_entries ) new_size = num_entries; // do not lose any entries if ( new_size != max_entries ) { entries = (T*)util::safe_realloc((void*)entries, sizeof(T) * new_size); if ( entries ) max_entries = new_size; else max_entries = 0; } return max_entries; } void push_front(const T& a) { if ( num_entries == max_entries ) resize(max_entries ? max_entries * LIST_GROWTH_FACTOR : DEFAULT_LIST_SIZE); for ( int i = num_entries; i > 0; --i ) entries[i] = entries[i - 1]; // move all pointers up one ++num_entries; entries[0] = a; } void push_back(const T& a) { if ( num_entries == max_entries ) resize(max_entries ? max_entries * LIST_GROWTH_FACTOR : DEFAULT_LIST_SIZE); entries[num_entries++] = a; } void pop_front() { remove_nth(0); } void pop_back() { remove_nth(num_entries - 1); } T& front() { return entries[0]; } T& back() { return entries[num_entries - 1]; } // The append method is maintained for historical/compatibility reasons. // (It's commonly used in the event generation API) void append(const T& a) // add to end of list { push_back(a); } bool remove(const T& a) // delete entry from list { int pos = member_pos(a); if ( pos != -1 ) { remove_nth(pos); return true; } return false; } T remove_nth(int n) // delete nth entry from list { assert(n >= 0 && n < num_entries); T old_ent = entries[n]; // For data where we don't care about ordering, we don't care about keeping // the list in the same order when removing an element. Just swap the last // element with the element being removed. if constexpr ( Order == ListOrder::ORDERED ) { --num_entries; for ( ; n < num_entries; ++n ) entries[n] = entries[n + 1]; } else { entries[n] = entries[num_entries - 1]; --num_entries; } return old_ent; } // Return 0 if ent is not in the list, ent otherwise. bool is_member(const T& a) const { int pos = member_pos(a); return pos != -1; } // Returns -1 if ent is not in the list, otherwise its position. int member_pos(const T& e) const { int i; for ( i = 0; i < length() && e != entries[i]; ++i ) ; return (i == length()) ? -1 : i; } T replace(int ent_index, const T& new_ent) // replace entry #i with a new value { if ( ent_index < 0 ) return T{}; T old_ent{}; if ( ent_index > num_entries - 1 ) { // replacement beyond the end of the list resize(ent_index + 1); for ( int i = num_entries; i < max_entries; ++i ) entries[i] = T{}; num_entries = max_entries; } else old_ent = entries[ent_index]; entries[ent_index] = new_ent; return old_ent; } // Type traits needed for some of the std algorithms to work using value_type = T; using pointer = T*; using const_pointer = const T*; // Iterator support using iterator = pointer; using const_iterator = const_pointer; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; iterator begin() { return entries; } iterator end() { return entries + num_entries; } const_iterator begin() const { return entries; } const_iterator end() const { return entries + num_entries; } const_iterator cbegin() const { return entries; } const_iterator cend() const { return entries + num_entries; } reverse_iterator rbegin() { return reverse_iterator{end()}; } reverse_iterator rend() { return reverse_iterator{begin()}; } const_reverse_iterator rbegin() const { return const_reverse_iterator{end()}; } const_reverse_iterator rend() const { return const_reverse_iterator{begin()}; } const_reverse_iterator crbegin() const { return rbegin(); } const_reverse_iterator crend() const { return rend(); } protected: // This could essentially be an std::vector if we wanted. Some // reasons to maybe not refactor to use std::vector ? // // - Harder to use a custom growth factor. Also, the growth // factor would be implementation-specific, taking some control over // performance out of our hands. // // - It won't ever take advantage of realloc's occasional ability to // grow in-place. // // - Combine above point this with lack of control of growth // factor means the common choice of 2x growth factor causes // a growth pattern that crawls forward in memory with no possible // re-use of previous chunks (the new capacity is always larger than // all previously allocated chunks combined). This point and // whether 2x is empirically an issue still seems debated (at least // GCC seems to stand by 2x as empirically better). // // - Sketchy shrinking behavior: standard says that requests to // shrink are non-binding (it's expected implementations heed, but // still not great to have no guarantee). Also, it would not take // advantage of realloc's ability to contract in-place, it would // allocate-and-copy. T* entries; int max_entries; int num_entries; }; // Specialization of the List class to store pointers of a type. template using PList = List; // Popular type of list: list of strings. using name_list = PList; } // namespace zeek // Macro to visit each list element in turn. #define loop_over_list(list, iterator) \ int iterator; \ for ( iterator = 0; iterator < (list).length(); ++iterator )