more examples

main
Brett 2025-03-13 21:35:58 -04:00
parent d36b1e809a
commit 87274c560e
2 changed files with 78 additions and 2 deletions

View File

@ -51,7 +51,7 @@ macro(blt_add_project name source type)
project(tower-defense) project(tower-defense)
endmacro() endmacro()
project(tower-defense VERSION 0.0.5) project(tower-defense VERSION 0.0.6)
option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF)
option(ENABLE_UBSAN "Enable the ub sanitizer" OFF) option(ENABLE_UBSAN "Enable the ub sanitizer" OFF)

View File

@ -36,6 +36,14 @@
#include <blt/std/assert.h> #include <blt/std/assert.h>
/*
* Naming Conventions
*/
// most classes, functions, variable names, etc should use lower_snake_case
// enum / enum class should use lower_snake_case for the type name, then UPPER_SNAKE_CASE for elements
// class member (private) variables should be prefixed with m_
// prefer enum class defs over enum defs, they are namespaced.
void michael_examples() void michael_examples()
{ {
/* /*
@ -79,7 +87,7 @@ void michael_examples()
// the initializer is run before any other code, thus doing it inline guarantees the class is in a valid state. // the initializer is run before any other code, thus doing it inline guarantees the class is in a valid state.
explicit rule_of_5_t(const blt::i32 id): m_id(id) explicit rule_of_5_t(const blt::i32 id): m_id(id)
{ {
BLT_ASSERT(m_id > 0 && "id must be greater than 0 (zero is used to represent empty state)"); // BLT_ASSERT(m_id > 0 && "id must be greater than 0 (zero is used to represent empty state)");
BLT_TRACE("I ({}) was constructed", m_id); BLT_TRACE("I ({}) was constructed", m_id);
} }
@ -131,7 +139,11 @@ void michael_examples()
blt::i32 m_id = 0; blt::i32 m_id = 0;
}; };
BLT_TRACE("--{}--", "{Rule of 5}");
// look at the output from this, you will understand what is going on. // look at the output from this, you will understand what is going on.
// we should talk about R and L value references right?
// soon! (keep this concept in mind, basically R value references are temporary objects, L values point to real objects)
{ {
// scoped block means lifetimes of objects only exist for the lifetime of the block // scoped block means lifetimes of objects only exist for the lifetime of the block
rule_of_5_t test1(1); // constructed rule_of_5_t test1(1); // constructed
@ -152,6 +164,9 @@ void michael_examples()
// you'll notice that destruction is in reverse order of declaration // you'll notice that destruction is in reverse order of declaration
} }
BLT_TRACE("");
BLT_TRACE("--{}--", "{Rule of 0}");
/* /*
* You know from 2P95 that we can use new/delete to allocate memory. * You know from 2P95 that we can use new/delete to allocate memory.
* Don't do that. Fun fact: you basically never need to use new/delete anymore * Don't do that. Fun fact: you basically never need to use new/delete anymore
@ -161,4 +176,65 @@ void michael_examples()
* What this means is, using standard headers, you never need to worry about manual memory management. * What this means is, using standard headers, you never need to worry about manual memory management.
* This is called the rule of 0. You don't need to define copy/move/destructors when the underlying types handle it for you. * This is called the rule of 0. You don't need to define copy/move/destructors when the underlying types handle it for you.
*/ */
struct rule_of_0_t
{
// this is a vector of rule_of_5_t, use it to show that copy and move are already made for us
std::vector<rule_of_5_t> m_rules;
};
{
rule_of_0_t test3;
// emplace_back is used to construct an object in place, you pass this function arguments. If you want to pass an already existing object,
// you can use .push_back(object) instead.
test3.m_rules.emplace_back(1);
test3.m_rules.emplace_back(2);
test3.m_rules.emplace_back(3);
// this will make a copy
rule_of_0_t copy_of_3 = test3;
// this will make a move
const rule_of_0_t move_of_3 = std::move(test3);
// all of which is implicitly defined, because std::vector has respective move and copy constructors defined.
// this function acts as a black box to the compiler. Can prevent optimizations in cases like this.
blt::black_box(move_of_3);
}
BLT_TRACE("");
BLT_TRACE("--{}--", "{std::vector reserve()}");
// now remember the problem of vectors allocating, then moving objects around internally as a result?
// well you can manually allocate a number of elements in a vector.
{
std::vector<rule_of_5_t> vector_of_rules;
vector_of_rules.reserve(100); // vector is empty, but can store 100 elements before reallocating
BLT_ASSERT(vector_of_rules.empty());
BLT_ASSERT(vector_of_rules.capacity() == 100);
for (int i = 0; i < 10; ++i)
vector_of_rules.emplace_back(i + 1);
// you'll notice in the console there's no sign of reallocation - objects are only constructed - in place
}
// Ok. Cool. If you know the number of elements you can reserve and not have to reallocate. But you said std::vector can replace basically
// any dynamic array. How about when I know the number of elements, do I always have to use .emplace_back()/.push_back()????
// the answer is of course not!
BLT_TRACE("");
BLT_TRACE("--{}--", "{std::vector resize()}");
{
// you can directly ask a size in the constructor (as long as you are not using an integer type as T)
// (I don't tend to use this and prefer calling the function directly)
// If the type is not default constructible you must provide the type to fill the requested elements with.
// in which case the type must be copy constructible
std::vector<rule_of_5_t> vector_of_rules{10, rule_of_5_t{0}};
// or manually call resize
vector_of_rules.resize(100, rule_of_5_t{0}); // vector has 100 elements of rule_of_5_t{0}
// welcome to our first use of a BLT iterator
// this is called a structured binding. You can use it to unpack structs. enumerate returns a tuple of <size_t, T>
for (const auto [i, v] : blt::enumerate(vector_of_rules))
v = rule_of_5_t{static_cast<int>(i)};
}
} }