zeek/src/List.h

321 lines
9.9 KiB
C++

// 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 <stdarg.h>
#include <cassert>
#include <initializer_list>
#include <iterator>
#include <utility>
#include "zeek/util.h"
namespace zeek {
enum class ListOrder : int { ORDERED, UNORDERED };
template<typename T, ListOrder Order = ListOrder::ORDERED>
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<T> 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<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_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<typename T, ListOrder Order = ListOrder::ORDERED>
using PList = List<T*, Order>;
// Popular type of list: list of strings.
using name_list = PList<char>;
} // 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 )