Gamgee
You miserable little maggot. I'll stove your head in!
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
short_value_optimized_storage.h
Go to the documentation of this file.
1 #ifndef gamgee__short_value_optimized_storage__guard
2 #define gamgee__short_value_optimized_storage__guard
3 
4 #include "utils.h"
5 
6 #include <vector>
7 #include <utility>
8 #include <cstring>
9 #include <stdexcept>
10 
11 namespace gamgee {
12 namespace utils {
13 
23 template<class ELEMENT_TYPE>
25  public:
26  ShortValueOptimizedStorage(const uint32_t capacity, const uint32_t short_value_upper_bound) :
27  m_num_values { 0 },
28  m_max_value_length { 0 },
29  m_short_value_upper_bound { short_value_upper_bound },
30  m_values(capacity),
31  m_contiguous_storage(capacity * short_value_upper_bound)
32  {
33  if ( short_value_upper_bound == 0 ) {
34  throw std::invalid_argument{"short value upper bound must be > 0"};
35  }
36 
37  for ( auto i = 0u; i < m_values.size(); ++i ) {
38  m_values[i].first = nullptr;
39  m_values[i].second = 0u;
40  }
41  }
42 
44  // Custom destructor because we have conditional deletion of values in the container
45  clear();
46  }
47 
48  // Moveable but not copyable
53 
57  uint32_t num_values() const { return m_num_values; }
58 
62  uint32_t capacity() const { return m_values.size(); }
63 
67  uint32_t max_value_length() const { return m_max_value_length; }
68 
72  uint32_t value_length(const uint32_t index) const {
73  check_max_boundary(index, capacity());
74  return m_values[index].second;
75  }
76 
80  bool is_set(const uint32_t index) const {
81  return index < capacity() && m_values[index].first != nullptr;
82  }
83 
87  std::pair<ELEMENT_TYPE*,uint32_t> get(const uint32_t index) const {
88  check_max_boundary(index, capacity());
89  return m_values[index];
90  }
91 
95  void set(const uint32_t index, const std::vector<ELEMENT_TYPE>& values) {
96  if ( values.empty() ) return;
97  set(index, &(values[0]), values.size());
98  }
99 
103  void set(const uint32_t index, const ELEMENT_TYPE* values, const uint32_t num_values) {
104  // Do nothing for empty values
105  if ( num_values == 0 || values == nullptr ) return;
106 
107  // Make sure index is in bounds
108  check_max_boundary(index, capacity());
109 
110  // If this is the first time we've used this slot, we have one more value in the container
111  if ( m_values[index].first == nullptr ) { ++m_num_values; }
112 
113  // Determine how the value will be stored based on its length, and get storage ready
114  prepare_memory(index, num_values);
115 
116  // Copy values into the prepared storage
117  memcpy(m_values[index].first, values, num_values * sizeof(ELEMENT_TYPE));
118 
119  // Keep track of the length of the longest set of values in the container
120  if ( num_values > m_max_value_length) m_max_value_length = num_values;
121  }
122 
128  void clear(const uint32_t index) {
129  if ( ! is_set(index) ) return;
130 
131  const auto previous_length = m_values[index].second;
132  release_memory_if_necessary(index);
133  --m_num_values;
134 
135  // Special case: clearing the longest item requires us to recalculate m_max_value_length
136  if ( previous_length == m_max_value_length ) {
137  recalculate_max_value_length();
138  }
139  }
140 
144  void clear() {
145  for ( auto i = 0u; i < m_values.size(); ++i ) {
146  release_memory_if_necessary(i);
147  }
148  m_num_values = 0;
149  m_max_value_length = 0;
150  }
151 
152  private:
153  uint32_t m_num_values;
154  uint32_t m_max_value_length;
155  uint32_t m_short_value_upper_bound;
156  std::vector<std::pair<ELEMENT_TYPE*,uint32_t>> m_values;
157  std::vector<ELEMENT_TYPE> m_contiguous_storage;
158 
163  void prepare_memory(const uint32_t index, const uint32_t num_values) {
164  // Clean up previous dynamic allocation at this index if there is one
165  release_memory_if_necessary(index);
166 
167  // Use contiguous pre-allocated storage for short values
168  if ( num_values <= m_short_value_upper_bound ) {
169  m_values[index].first = &(m_contiguous_storage[index * m_short_value_upper_bound]);
170  }
171  else {
172  // Use dynamic allocation for values that don't fit (note: can't use smart pointers to wrap
173  // this allocation since we will often be pointing into memory that must not be freed -- otherwise
174  // we risk undefined behavior)
175  m_values[index].first = new ELEMENT_TYPE[num_values];
176  }
177 
178  // Record the length of the value as well
179  m_values[index].second = num_values;
180  }
181 
186  void release_memory_if_necessary(const uint32_t index) {
187  // Only free pointers that don't point into the contiguous storage
188  if ( m_values[index].first != nullptr &&
189  m_values[index].first != &(m_contiguous_storage[index * m_short_value_upper_bound]) ) {
190 
191  // No complaining about this delete! If you're the author of a low-level container like vector,
192  // or this class, delete is perfectly ok to use internally. Can't use smart pointers because we
193  // have pointers into contiguous storage mixed in with pointers to individually-allocated memory
194  // blocks.
195  delete[] m_values[index].first;
196  }
197 
198  m_values[index].first = nullptr;
199  m_values[index].second = 0;
200  }
201 
206  void recalculate_max_value_length() {
207  m_max_value_length = 0u;
208 
209  for ( auto& value : m_values ) {
210  if ( value.first != nullptr && value.second > m_max_value_length ) {
211  m_max_value_length = value.second;
212  }
213  }
214  }
215 };
216 
217 }
218 }
219 
220 #endif /* gamgee__short_value_optimized_storage__guard */
void clear(const uint32_t index)
Clear the value at a specific index.
Definition: short_value_optimized_storage.h:128
void check_max_boundary(const uint32_t index, const uint32_t size, const std::string &prefix_msg)
checks that an index is greater than or equal to size
Definition: utils.h:63
uint32_t capacity() const
Returns the maximum number of values the container can hold.
Definition: short_value_optimized_storage.h:62
bool is_set(const uint32_t index) const
Determines whether there is a value at the specified position.
Definition: short_value_optimized_storage.h:80
uint32_t value_length(const uint32_t index) const
Returns the length of the value at the specified position.
Definition: short_value_optimized_storage.h:72
void clear()
Reset storage to a pristine state with no values.
Definition: short_value_optimized_storage.h:144
ShortValueOptimizedStorage & operator=(ShortValueOptimizedStorage &&other)=default
uint32_t max_value_length() const
Returns the length of the longest value in the container.
Definition: short_value_optimized_storage.h:67
ShortValueOptimizedStorage(const uint32_t capacity, const uint32_t short_value_upper_bound)
Definition: short_value_optimized_storage.h:26
~ShortValueOptimizedStorage()
Definition: short_value_optimized_storage.h:43
Definition: exceptions.h:9
void set(const uint32_t index, const std::vector< ELEMENT_TYPE > &values)
Set the value at the specified index by vector.
Definition: short_value_optimized_storage.h:95
Definition: short_value_optimized_storage.h:24
uint32_t num_values() const
Returns the number of values in the container.
Definition: short_value_optimized_storage.h:57
void set(const uint32_t index, const ELEMENT_TYPE *values, const uint32_t num_values)
Set the value at the specified index by pointer.
Definition: short_value_optimized_storage.h:103