C++ Library Extensions 2022.12.09
To help learn modern C++ programming
024-dynamic_array.cpp
Go to the documentation of this file.
1#include <tpf_output.hpp>
3
4#include <cstdlib>
5#include <iterator>
6
9
10template<typename ElementType>
11class dynamic_array
12{
13public:
14 using iterator = ElementType*;
15
16 // don't do this
17 // using const_iterator = const iterator;
18 // it will define ElementType* const, it is wrong
19
20 using const_iterator = const ElementType*;
21
22private:
23 size_t m_size;
24 ElementType *m_ptr;
25
26 void free_memory()
27 {
28 if (m_ptr)
29 {
30 if constexpr(std::is_pod_v<ElementType>)
31 _aligned_free(this->m_ptr);
32 else
33 delete[] m_ptr;
34
35 this->m_ptr = nullptr;
36 this->m_size = 0;
37 }
38 }
39
40 // Before we call this function,
41 // existing memory should be released, otherwise memory leaks.
42 //
43 // the this->m_size should be greater than 0,
44 // otherwise, UNDEFINED BEHAVIOR
45 void new_alloc()
46 {
47 if constexpr( std::is_pod_v<ElementType>)
48 {
49 this->m_ptr = (ElementType*) _aligned_malloc(this->m_size * sizeof(ElementType), 16);
50
51 if(!this->m_ptr)
52 Tpf_ThrowDebugException("Allocation Failed");
53 }
54 else
55 {
56 try
57 {
58 // operator new throws std::bad_alloc if allocation fails
59 this->m_ptr = new ElementType[this->m_size];
60 }
61 catch(const std::bad_alloc&)
62 {
63 Tpf_ThrowDebugException("Allocation Failed");
64 }
65 }
66 }
67
68 void invalidate()
69 {
70 this->m_ptr = nullptr;
71 }
72
73 inline void copy_memory(ElementType* right_hand_side_m_ptr)
74 {
75 if constexpr ( std::is_pod_v<ElementType> )
76 // if ElementType is a POD, then we can use memcpy()
77 std::memcpy(this->m_ptr, right_hand_side_m_ptr, m_size * sizeof(ElementType));
78 else
79 {
80 // if ElementType is NOT a POD
81 // then we have to copy element-wise
82 for(size_t i = 0; i < this->m_size; ++i)
83 this->m_ptr[i] = right_hand_side_m_ptr[i];
84 }
85 }
86
87public:
88
89 // the pointer pointing to the first element
91 {
92 return this->m_ptr;
93 }
94
95 // the pointer pointing to the element after the last element
97 {
98 return (this->m_ptr + this->m_size);
99 }
100
102 {
103 // we actually do NOT need the cast,
104 // but I want to make the syntax clear
105 return static_cast<const_iterator>(this->m_ptr);
106 }
107
109 {
110 // we actually do NOT need the cast,
111 // but I want to make the syntax clear
112 return static_cast<const_iterator>(this->m_ptr + this->m_size);
113 }
114
115 auto rbegin()
116 {
117 return std::reverse_iterator(end());
118 }
119
120 auto rend()
121 {
122 return std::reverse_iterator(begin());
123 }
124
125 auto crbegin()
126 {
127 return std::reverse_iterator(cend());
128 }
129
130 auto crend()
131 {
132 return std::reverse_iterator(cbegin());
133 }
134
135 size_t size() const { return this->m_size; }
136
137 // we assume count is greater than zero
138 void resize(size_t count)
139 {
140 if(count == this->m_size)
141 return;
142 else
143 {
144 // free existing memory
145 this->free_memory();
146 this->m_size = count;
147
148 // if allocation fails, this function throws exception
149 new_alloc();
150 }
151 }
152
153 // this is dangerous practice
154 // if dynamic allocation fails,
155 // this constructor throws std::bad_alloc exception
156 explicit dynamic_array(size_t size = 1)
157 : m_size{size}
158 {
159 new_alloc();
160 }
161
162 // if dynamic allocation fails,
163 // it throws exception
164 dynamic_array(const dynamic_array &right_hand_side) :
165 m_size{right_hand_side.m_size}
166 {
167 new_alloc();
168 copy_memory(right_hand_side.m_ptr);
169 }
170
171 ElementType *operator&() { return this->m_ptr; }
172
173 ElementType &operator[](size_t index) { return this->m_ptr[index]; }
174
175 const ElementType &operator[](size_t index) const { return this->m_ptr[index]; }
176
177 ElementType &at(size_t index)
178 {
179 if (index < this->m_size && this->m_ptr != nullptr)
180 return this->m_ptr[index];
181 else
182 Tpf_ThrowDebugException("Index Out of Range");
183 }
184
185 const ElementType &at(size_t index) const
186 {
187 if (index < this->m_size && this->m_ptr != nullptr)
188 return this->m_ptr[index];
189 else
190 Tpf_ThrowDebugException("Index Out of Range");
191 }
192
193 // if allocation fails,
194 // it can throw std::bad_alloc exception
195 dynamic_array &operator=(const dynamic_array &right_hand_side)
196 {
197 // this code is wrong, because address of operator&()
198 // is defined for class dynamic_array,
199 // so, we cannot use the following code any longer
200 // if(this != &right_hand_side)
201
202 if (this != std::addressof(right_hand_side))
203 {
204 if (this->m_size == right_hand_side.m_size)
205 {
206 // we do not need to reallocate memory
207 // and also, we do not need to free memory
208 // this->free_memory()
209
210 copy_memory(right_hand_side.m_ptr);
211 }
212 else
213 {
214 // we have free existing memory
215 this->free_memory();
216 this->m_size = right_hand_side.m_size;
217
218 new_alloc();
219 copy_memory(right_hand_side.m_ptr);
220 }
221 }
222
223 return *this;
224 }
225
226 dynamic_array(dynamic_array &&right_hand_side) noexcept
227 : m_size{right_hand_side.m_size},
228 m_ptr{right_hand_side.m_ptr}
229 {
230 // IMPORTANT: invalidate right_hand_side
231 // after move, right_hand_side is invalide
232 // so, we should NOT access to right_hand_side after move operation
233 right_hand_side.invalidate();
234 }
235
236 dynamic_array &operator=(dynamic_array &&right_hand_side) noexcept
237 {
238 if (this != std::addressof(right_hand_side))
239 {
240 // delete existing memory
241 this->free_memory();
242 this->m_size = right_hand_side.m_size;
243 this->m_ptr = right_hand_side.m_ptr;
244
245 // IMPORTANT: invalidate right_hand_size after move assignment
246 // since after move operation, right_hand_side is invalid
247 // so, we should NOT access to right_hand_side after move operation
248 right_hand_side.invalidate();
249 }
250
251 return *this;
252 }
253
255 {
256 this->free_memory();
257 }
258
259 friend std::ostream& operator<<(std::ostream& os, const dynamic_array& da)
260 {
261 size_t size = da.size() - 1;
262 os << "{ ";
263
264 for(size_t i = 0; i < size; ++i)
265 os << da[i] << ", ";
266
267 os << da[size] << " }";
268
269 return os;
270 }
271};
272
274{
275 dynamic_array<int> a{10};
276
277 for(size_t i = 0; i < a.size(); ++i)
278 a[i] = (int)i;
279
280 stream << "a = " << a << endl;
281}
282
284{
285 size_t count = 2;
286
287 std::vector< dynamic_array<int> > jagged_array(count); // don't do this
288
289 stream << "At this point, default constructor of dynamic_array is called "
290 << count << " times.\n"<< endl;
291
292 for(size_t i = 0; i < jagged_array.size(); ++i)
293 jagged_array[i] = dynamic_array<int>{ i + 1 };
294
295 stream << "In the for loop, default constructor of dynamic_array is called "
296 << count << " times, and move constructor is called " << count << " times\n" << endl;
297
298 stream << "So, destructor is called total: " << (count + count) << " times " << endl;
299}
300
302{
303 stream << "\nthis is better, but not perfect!!\n" << endl;
304
305 size_t count = 2;
306
307 std::vector< dynamic_array<int> > jagged_array;
308 jagged_array.reserve(count);
309
310 for(size_t i = 0; i < jagged_array.capacity(); ++i)
311 jagged_array.emplace_back(dynamic_array<int>{ i + 1 });
312}
313
314// we never created temporary dynamic array in this function
316{
317 stream << "\nthis is PEREFECT WAY, YOU SHOULD ALWAYS USE THIS METHOD!!\n" << endl;
318
319 size_t count = 10;
320
321 std::vector< dynamic_array<int> > jagged_array;
322 jagged_array.reserve(count);
323
324 for(size_t i = 0; i < jagged_array.capacity(); ++i)
325 {
326 jagged_array.emplace_back(i + 1);
327 // i + 1 is argument for dynamic_array.
328
329 // now std::vector creates dynamic_array
330 // internally, so, no move... only count times of default
331 // constructor of dynamic_array is called.
332
333 for(size_t j = 0; j < jagged_array.back().size(); ++j)
334 jagged_array.back()[j] = (int)j;
335 }
336
337 // note that the constructor of dynamic_array{size_t}
338
339 for(auto& da: jagged_array)
340 {
341 stream << da << endl;
342 }
343}
344
346{
347 // we dynamically allocated 10 elements
348 dynamic_array<int> array{ 10 };
349
350 int i = 0;
351
352 for(auto& e: array)
353 e = i++;
354
355 stream << array << endl;
356
357 tpf::sstream out;
358
359 std::for_each(array.rbegin(), array.rend(),
360 [&out](auto& e)
361 {
362 out << e << ", ";
363 });
364
365 // if you don't endl with stream, you cannot see the output
366 out << endl;
367}
368
370{
371 dynamic_array<int> array{10};
372
373 int i = -1;
374
375 for(auto& e: array)
376 e = ++i;
377
378 stream << array << endl;
379
380 // what does it mean?
381 // array = 20;
382 // stream << array << endl;
383}
384
385/*
386 POD: Plain Old Data Type
387
388 POD is C style struct or C++ Primitive Data Type
389*/
390
391// if we are to use SSE, we need to align our data 16 bytes
392struct alignas(16) vector_3d_pod
393{
394 float m_x, m_y, m_z, m_color;
395
396 // POD type can have non-virtual member function
397 // so, this struct is still a POD type
398 void set(float x, float y, float z)
399 {
400 m_x = x; m_y = y; m_z = z;
401 }
402};
403
404std::ostream& operator<<(std::ostream& os, const vector_3d_pod& v)
405{
406 os << "< " << v.m_x << ", " << v.m_y << ", " << v.m_z <<" >";
407 return os;
408}
409
410struct alignas(16) vector_3d_not_pod
411{
412 float m_x, m_y, m_z, m_color;
413
414 virtual void set(float x, float y, float z)
415 {
416 m_x = x; m_y = y; m_z = z;
417 }
418
419 // if a class or struct has a custom destructor
420 // other than default destructor provided by the compiler
421 // then it is not a POD
423 { }
424};
425
426std::ostream& operator<<(std::ostream& os, const vector_3d_not_pod& v)
427{
428 os << "< " << v.m_x << ", " << v.m_y << ", " << v.m_z <<" >";
429 return os;
430}
431
433{
434 size_t m_x, m_y, m_z, m_color;
435
436 // if a class or struct has a data member
437 // that is not a POD, that the class or struct is also NOT a POD
438 std::unique_ptr<int> ptr;
439};
440
442{
443 stream << "vector_3d_pod is pod: " << std::is_pod_v<vector_3d_pod> << endl;
444 stream << "vector_3d_not_pod is pod: " << std::is_pod_v<vector_3d_not_pod> << endl;
445 stream << "it_is_still_not_pod is pod: " << std::is_pod_v<it_is_still_not_pod> << endl;
446 stream << "int* is pod: " << std::is_pod_v<int*> << endl;
447
448 /*
449 C++ primitive types such as char, int, double, double*, etc.
450 are POD type.
451
452 we can use memcpy() to copy one POD to another POD.
453 */
454}
455
456template<typename ElementType>
457void benchmarking_with_primitive_type(size_t test_count, size_t element_count)
458{
459 stream << "Benchmarking with type: "
461
462 stream << "With std::vector"<< tpf::types::type_list_t<ElementType>{}
463 << " - Test Count: " << test_count
464 << " - Element Count: " << element_count << endl;
465
466 // define stop watch
468
469 for(size_t i = 0; i < test_count; ++i)
470 {
471 // allocate memory at one sweep
472 std::vector<ElementType> v(element_count);
473
474 for(size_t j = 0; j < element_count; ++j)
475 v[j] = (ElementType)j;
476
477 if(element_count < 21)
478 stream << v << endl;
479 }
480
481 stream << "... Elasped: " << sw.elapsed_time() << endl << endl;
482
484 stream << "With dynamic_array"<< tpf::types::type_list_t<ElementType>{}
485 << " - Test Count: " << test_count
486 << " - Element Count: " << element_count << endl;
487
488 // reset stop watch
489 sw.reset();
490
491 for(size_t i = 0; i < test_count; ++i)
492 {
493 // allocate memory at one sweep
494 dynamic_array<ElementType> v(element_count);
495
496 for(size_t j = 0; j < element_count; ++j)
497 v[j] = (ElementType)j;
498
499 if(element_count < 21)
500 stream << v << endl;
501 }
502
503 stream << "... Elasped: " << sw.elapsed_time() << endl << endl;
504}
505
506template<typename ElementType>
507void benchmarking_with_class_type(size_t test_count, size_t element_count)
508{
509 stream << "Benchmarking with type: "
511
512 stream << "With std::vector"<< tpf::types::type_list_t<ElementType>{}
513 << " - Test Count: " << test_count
514 << " - Element Count: " << element_count << endl;
515
516 // define stop watch
518
519 for(size_t i = 0; i < test_count; ++i)
520 {
521 // allocate memory at one sweep
522 std::vector<ElementType> v(element_count);
523
524 for(size_t j = 0; j < element_count; ++j)
525 v[j].set((float)j, (float)j, (float)j);
526
527 if(element_count < 21)
528 stream << v << endl;
529 }
530
531 stream << "... Elasped: " << sw.elapsed_time() << endl << endl;
532
534 stream << "With dynamic_array"<< tpf::types::type_list_t<ElementType>{}
535 << " - Test Count: " << test_count
536 << " - Element Count: " << element_count << endl;
537
538 // reset stop watch
539 sw.reset();
540
541 for(size_t i = 0; i < test_count; ++i)
542 {
543 // allocate memory at one sweep
544 dynamic_array<ElementType> v(element_count);
545
546 for(size_t j = 0; j < element_count; ++j)
547 v[j].set((float)j, (float)j, (float)j);
548
549 if(element_count < 21)
550 stream << v << endl;
551 }
552
553 stream << "... Elasped: " << sw.elapsed_time() << endl << endl;
554}
555
556int main()
557{
558 // examples_for_dynamic_array();
559 // do_not_do_this();
560
561 // is_still_is_not_perfect();
562
563 // please_use_this_method();
564
565 // test_iterators_dynamic_array();
566
567 // why_explicit_to_constructor();
568
569 // test_pod_types();
570
571 benchmarking_with_primitive_type<int>(10, 10'000'000);
572
573 benchmarking_with_primitive_type<float>(20, 10'000'000);
574
575 benchmarking_with_primitive_type<double>(5, 10'000'000);
576
577 benchmarking_with_class_type<vector_3d_pod>(5, 1'000'000);
578
579 benchmarking_with_class_type<vector_3d_not_pod>(5, 1'000'000);
580}
std::atomic< int > count
Definition: 022-mutex.cpp:10
void test_iterators_dynamic_array()
void examples_for_dynamic_array()
void why_explicit_to_constructor()
tpf::sstream stream
std::ostream & operator<<(std::ostream &os, const vector_3d_pod &v)
void do_not_do_this()
auto endl
void test_pod_types()
void please_use_this_method()
void is_still_is_not_perfect()
int main()
void benchmarking_with_class_type(size_t test_count, size_t element_count)
void benchmarking_with_primitive_type(size_t test_count, size_t element_count)
ElementType * operator&()
dynamic_array(dynamic_array &&right_hand_side) noexcept
const_iterator cbegin()
ElementType & operator[](size_t index)
const ElementType * const_iterator
dynamic_array(const dynamic_array &right_hand_side)
const ElementType & at(size_t index) const
void resize(size_t count)
const_iterator cend()
dynamic_array & operator=(dynamic_array &&right_hand_side) noexcept
ElementType & at(size_t index)
const ElementType & operator[](size_t index) const
friend std::ostream & operator<<(std::ostream &os, const dynamic_array &da)
dynamic_array(size_t size=1)
size_t size() const
ElementType * iterator
dynamic_array & operator=(const dynamic_array &right_hand_side)
std::string elapsed_time(bool bReset=true, TimeUnit dummy_time=TimeUnit{}) const
Implements set operations.
Definition: tpf_set.hpp:52
constexpr auto endl
Definition: tpf_output.hpp:973
std::unique_ptr< int > ptr
This type is used to manipulate type list.
Definition: tpf_types.hpp:956
virtual void set(float x, float y, float z)
void set(float x, float y, float z)
Stream output operators << are implemented.
#define Tpf_ThrowDebugException(debug_message)
Throw a debug_exception with message as argument.
Definition: tpf_types.hpp:1416