Monday, 5 December 2022

Chapter 25 // Exercise 17 - Principles & Practice Using C++

In this exercise I'm using Visual Studio 2019 and a modified version of the std_lib_facilities header found here.

Chapter 25 // Exercise 17

In section 25.4.3-4 we provided a class Array_ref claimed to make access to elements of an array simpler and safer. In particular, we claimed to handle inheritance correctly. Try a variety of ways to get a Rectangle* into a vector<Circle*> using an Array_ref<Shape*> but no casts or other operations involving undefined behaviour. This ought to be impossible.

Github: N/A

Wow it's been a hot minute since I last looked at the FLTK code. I would avoid using arrays entirely but this little class is handy. There's no code for this one as none of it compiles anyway.

And with that I'm onto the second to last chapter of the book...it feels strange. What will I do without the exercises in this book hanging over my head?


Sunday, 4 December 2022

Chapter 25 // Exercise 16 - Principles & Practice Using C++

In this exercise I am using Visual Studio 2019 and a modified version of the std_lib_facilities header found here.

Chapter 25 // Exercise 16

Formulate 20 coding style rules (don't just copy those in section 25.6). Apply them to a program of more than 300 lines that you recently wrote. Write a short (a page or two) comment on the experience of applying those rules. Did you find errors in the code? Did the code get clearer? Did some code get less clear? Now modify the set of rules based on this experience.

This was a really interesting exercise for me as my coding style has changed so much over the past 7 years. I could probably make a video talking about this. It's even changed a lot over the past 3 years I've been putting my code on github.

Currently my 20 rules would be:
  1. Dashed lines between functions (I picked this up from work and now I will put bugs on code reviews if people have forgotten dashed lines)
  2. UpperCamelCase to be used for object creation, enums to use capital snake case and everything else should use lowerCamelCase (this is in direct violation of Bjarne's R302 but he can suck it, camelCaseForever. I also use Hungarian Notation for vectors sometimes because why be completely consistent?)
  3. All member variables should be prefixed with m
  4. All global variables should be prefixed with g
  5. Function argument parameters should be prefixed with p
  6. Enums should be prefixed with e and be all uppercase afterwards. Example; ePURPLE_CRAYON
  7. Where a function argument parameter is not modified in the function, it should always be passed by const reference (unless it's cheaper to just pass by const value)
  8. References should be used over pointers where possible
  9. Prefer std::vector over raw arrays
  10. const_cast should not be used
  11. Prefer ifndef/define over pragma once
  12. All local variables should be declared as const/constexpr unless they need to be modified
  13. Commonly used (non-changing) local variables should be declared in a private namespace in the cpp of the file they are used in
  14. Avoid mixing static member variables with non-static. Either create a namespace or singleton with all static functions/members
  15. Try to order member variables in descending size order
  16. Braces {} should always be on their own line. The only exception is 1 line functions in header files (or empty constructors/destructors)
  17. Avoid post-fix increment/decrement unless the functionality of post-fix is specifically needed. Always prefer pre-fix increment/decrement
  18. Includes should be in grouped order of local (""), then global (<>). Each group should be in alphabetical order
  19. Include what you use. Prefer including in the cpp with forward declares in the header. Only include in the header if you must
  20. Avoid lambda's. Only use them if you must
When I first started programming back in January 2016, I had no one to talk to about C++ and used this book or stackoverflow to try and complete exercises. When I started my degree that September, I started picking up habits from my tutors code and youtube videos. For those first 3 years my code style heavily resembled what you see in the book. Then I started working at Rare and at first I fought against the strict coding style/rules. I used them because I had to but over the past 3 and half years I've grown to love some of those rules, and the ones I don't like don't bother me anymore. I'd say my current style is the one I'll probably stick to now as I find it clear, concise and maintainable. 

I would say rules 1-6 are just me being picky and pedantic however the rest are extremely useful in production code and can even help you avoid bugs and provide a better debugging environment.

Saturday, 3 December 2022

Chapter 25 // Exercise 15 - Principles & Practice Using C++

In this exercise I'm using Visual Studio 2019 and a modified version of the std_lib_facilities header found here.

Chapter 25 // Exercise 15

Measure the time (section 26.6.1) it takes to allocate 10,000 objects of random sizes in the [1000:0)-byte range using new; then measure the time it takes to deallocate them using delete.

Do this twice, once deallocating in the reverse order of allocation and once deallocating in random order. 

Then, do the equivalent  of allocating 10,000 objects of size 500 bytes from a pool and freeing them.

Then, do the equivalent of allocating 10,000 objects of random sizes in the [1000:0)-byte range on a stack and then free them (in reverse order). 

Compare the measurements . Do each measurement at least three times to make sure the results are consistent.

I split this up into several parts. For the object of random size I created a struct that holds a string, the string then reserves a random size between 0, 1000. A vector of that struct is created to hold the pointers to the object created.

---- Part 1 ----
(No pool or reserved allocation)
Allocate 10,000 objects: 1(19ms), 2(20ms), 3(21ms)
Deallocate in reverse order: 1(9ms), 2(10ms), 3(10ms)

---- Part 2 ----
(No pool or reserved allocation)
Allocate 10,000 objects: 1(20ms), 2(20ms), 3(21ms)
Deallocate in random order: 1(11ms), 2(11ms), 3(11ms)

For the ones with a pool I decided to not use new and delete and instead create a vector with a reserved size of 10,000 500-byte objects. That way when you pushback it won't need to allocate more space. Then "freeing" is as simple as just clearing the string.

---- Part 3 ----
(with reserved allocation of 500 bytes per object)
Allocate 10,000 objects: 1(14ms), 2(8ms), 3(8ms)
Deallocate in reverse order: 1(1ms), 2(1ms), 3(0ms)

---- Part 4 ----
(with reserved allocation of 500 bytes per object)
Allocate 10,000 objects: 1(14ms), 2(14ms), 3(14ms)
Deallocate in random order: 1(1ms), 2(1ms), 3(1ms)

---- Part 5 ----
(no pool, on std::stack)
Allocate 10,000 objects: 1(24ms), 2(24ms), 3(24ms)
Deallocate in random order: 1(15ms), 2(16ms), 3(16ms)

Allocating with a pool is always faster because the memory you need is right there. Memory pools are used a lot in games (you may hear them being called "banks" as well).

Friday, 2 December 2022

Chapter 25 // Exercise 14 - Principles & Practice Using C++

In this exercise I am using Visual Studio 2019 and a modified version of the std_lib_facilities header found here.

Chapter 25 // Exercise 14

Implement a simple vector that can hold at most N elements allocated from a pool. Test of for N === 1000 and integer elements.

I used my vector implementation from Chapter 19 - Exercise 8:

As this uses a custom allocator. I then modified the constructors to be able to take in 2 values; the number of elements required and the size of the allocator. If the size of the allocator given is more than 0 or more than the number of elements given, the constructor for MyVector will then reserve that space.

The allocator uses malloc, free and placement new. So on construction, it will allocate space based on the size of the allocator, then when we specify the number of elements, it will use placement new to "place" those elements into the already allocated memory.