diff --git a/.gitignore b/.gitignore index d2d49df..e84cd09 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ coverage_gcovr coverage_lcov !.vscode/launch.json !.vscode/tasks.json -.cache \ No newline at end of file +.cache +__pycache__/ \ No newline at end of file diff --git a/docs/image/future_promis.png b/docs/image/future_promis.png deleted file mode 100644 index 73da3f7..0000000 Binary files a/docs/image/future_promis.png and /dev/null differ diff --git a/docs/image/future_promis_flow.png b/docs/image/future_promis_flow.png deleted file mode 100644 index 1942fba..0000000 Binary files a/docs/image/future_promis_flow.png and /dev/null differ diff --git a/scripts/run.sh b/scripts/run.sh index a15e535..6383704 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -45,7 +45,8 @@ cppcheck \ --inline-suppr \ --quiet \ --error-exitcode=1 \ - ./src ./include + ./src ./include \ + -isrc/embedded/ echo "[OK] Static analysis passed" diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 57ca20c..0da196b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -7,7 +7,7 @@ set(CORE_SOURCES # Basic C++ language features - ${CMAKE_CURRENT_SOURCE_DIR}/basics/InitializeVariable.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/basics/Initialization.cpp ${CMAKE_CURRENT_SOURCE_DIR}/basics/Operations.cpp ${CMAKE_CURRENT_SOURCE_DIR}/basics/TypeQualifier.cpp ${CMAKE_CURRENT_SOURCE_DIR}/basics/ControlFlow.cpp @@ -67,26 +67,27 @@ set(CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/container/sequence/Deque.cpp ${CMAKE_CURRENT_SOURCE_DIR}/container/associative/Set.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/expression/Lambda.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/expression/FunctionPointer.cpp - # Smart pointers ${CMAKE_CURRENT_SOURCE_DIR}/smart_pointer/Unique.cpp ${CMAKE_CURRENT_SOURCE_DIR}/smart_pointer/Shared.cpp ${CMAKE_CURRENT_SOURCE_DIR}/smart_pointer/Weak.cpp - # Operator overloading - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/ArithmeticOperator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/IOOperator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/UnaryOperator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/ComparisonOperator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/InDecOperator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/SubscriptOperator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/ParenthesisOperator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/TypeCast.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/AssignmentOperator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/ClassMemberAccessOperator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/overloading/AllocationOperator.cpp + # Function + ${CMAKE_CURRENT_SOURCE_DIR}/function/Lambda.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/FunctionPointer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/Functional.cpp + ## Operator overloading + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/ArithmeticOperator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/StreamOperator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/UnaryOperator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/ComparisonOperator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/InDecOperator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/SubscriptOperator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/FunctionCallOperator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/TypeCast.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/AssignmentOperator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/ClassMemberAccessOperator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function/operator_overloading/AllocationOperator.cpp # Date and time ${CMAKE_CURRENT_SOURCE_DIR}/datetime/Time.cpp @@ -97,6 +98,7 @@ set(CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/concurrency/RaceCondition.cpp ${CMAKE_CURRENT_SOURCE_DIR}/concurrency/ConditionVariable.cpp ${CMAKE_CURRENT_SOURCE_DIR}/concurrency/FuturePromise.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/concurrency/Timing.cpp # Utils ${CMAKE_CURRENT_SOURCE_DIR}/utils/Algorithm.cpp diff --git a/src/core/basics/ControlFlow.cpp b/src/core/basics/ControlFlow.cpp index fd381ef..5fca0c7 100644 --- a/src/core/basics/ControlFlow.cpp +++ b/src/core/basics/ControlFlow.cpp @@ -1,47 +1,47 @@ -#include #include "ExampleRegistry.h" +#include "Logger.h" namespace { void conditionals() { - std::cout << "\n--- Conditional Examples ---\n"; + LOG("Conditional Examples"); // if-else int x = rand(); - std::cout << "x = " << x << "\n"; + LOG_S("x = " << x); if (x > 0) { - std::cout << "x is positive\n"; + LOG("x is positive"); } else if (x < 0) { - std::cout << "x is negative\n"; + LOG("x is negative"); } else { - std::cout << "x is zero\n"; + LOG("x is zero"); } // switch int choice = rand() % 2 + 1; - std::cout << "choice = " << x << "\n"; + LOG_S("Choice = " << std::to_string(x)); switch (choice) { case 1: - std::cout << "Choice is 1\n"; + LOG("Choice is 1"); break; case 2: - std::cout << "Choice is 2\n"; + LOG("Choice is 2"); break; default: - std::cout << "Choice is something else\n"; + LOG("Choice is something else"); break; } } void jumps() { - std::cout << "\n--- Jump Statement Examples ---\n"; + LOG("Jump Statement Examples"); // goto int num = rand(); if (num == 3) goto jumpLabel; - std::cout << "This line will be skipped.\n"; + LOG("This line will be skipped."); jumpLabel: - std::cout << "Jumped here using goto!\n"; + LOG("Jumped here using goto!"); // break / continue for (int i = 0; i < 5; ++i) { @@ -49,7 +49,7 @@ void jumps() { continue; // skip 2 if (i == 4) break; // stop loop at 4 - std::cout << "i = " << i << "\n"; + LOG_S("i = " << i); } } @@ -59,43 +59,43 @@ int square(int n) { } void functionCalls() { - std::cout << "\n--- Function Call Examples ---\n"; + LOG("Function Call Examples"); // function call int result = square(5); - std::cout << "square(5) = " << result << "\n"; + LOG_S("square(5) = " << result); } void loops() { - std::cout << "\n--- Loop Examples ---\n"; + LOG("Loop Examples"); // while int i = 0; while (i < 3) { - std::cout << "while loop i = " << i << "\n"; + LOG_S("while loop i = " << i); ++i; } // do-while int j = 0; do { - std::cout << "do-while loop j = " << j << "\n"; + LOG_S("do-while loop j = " << j); ++j; } while (j < 2); // for(initialization; condition; update) for (int k = 0; k < 3; ++k) { - std::cout << "for loop k = " << k << "\n"; + LOG_S("for loop k = " << k); } // ranged-for const int arr[] = {10, 20, 30}; for (int value : arr) { - std::cout << "ranged-for value = " << value << "\n"; + LOG_S("ranged-for value = " << value); } } void halts() { - std::cout << "\n--- Halt Examples ---\n"; + LOG("Halt Examples"); // std::exit() — terminates the program normally // std::abort() — terminates abnormally (no cleanup) @@ -106,20 +106,20 @@ void halts() { } void exceptions() { - std::cout << "\n--- Exception Handling Examples ---\n"; + LOG("Exception Handling Examples"); // try - catch - throw try { throw std::runtime_error("Something went wrong!"); } catch (const std::exception& e) { - std::cout << "Caught exception: " << e.what() << "\n"; + LOG_S("Caught exception: " << e.what()); } } } // namespace class ControlFlow : public IExample { public: - std::string group() const override { return "core"; } + std::string group() const override { return "core/basics"; } std::string name() const override { return "ControlFlow"; } std::string description() const override { return "ControlFlow"; } void execute() override { diff --git a/src/core/basics/Initialization.cpp b/src/core/basics/Initialization.cpp new file mode 100644 index 0000000..853af7e --- /dev/null +++ b/src/core/basics/Initialization.cpp @@ -0,0 +1,114 @@ +// cppcheck-suppress-file [unreadVariable, unusedVariable, uninitdata, uninitvar, unassignedVariable] + +#include "ExampleRegistry.h" +#include "Logger.h" + +/// @brief Dummy class +struct Dummy { + Dummy() { LOG("Default ctor"); } + explicit Dummy(int value) { LOG_S("Int ctor: " << value); } + Dummy(const Dummy&) { LOG("Copy ctor"); } +}; + +/// @brief Aggregate type +struct Aggregate { + int x; + int y; +}; + +/// @brief Default Initialization +void default_ini() { + int a; + LOG_S("a = " << a); + + Dummy object; + + auto* p1 = new int; + LOG_S("*p1 = " << *p1); + auto* p2 = new Dummy; + + delete p1; + delete p2; +} + +/// @brief Value Initialization +void value_ini() { + // int x();Dummy obj(); // Most Vexing Parse:function declaration + + int a = int(); + int b{}; + + LOG_S("a = " << a); + LOG_S("b = " << b); + + Dummy object1 = Dummy(); // value initialization x copy-initialization + Dummy object2{}; + + auto* p1 = new int(); + LOG_S("*p1 = " << *p1); + + auto* p2 = new Dummy(); + + delete p1; + delete p2; +} + +/// @brief Direct Initialization +void direct_ini() { + int a(42); + + Dummy object1(1); + Dummy object2(static_cast(2)); + + LOG_S("a = " << a); +} + +/// @brief Copy Initialization +void copy_ini() { + Dummy source{1}; + + Dummy object1 = source; + Dummy object2 = Dummy{2}; + + int value = 42; + + LOG_S("value = " << value); +} + +/// @brief List Initialization (since C++11) +void list_ini() { + Dummy object1{1}; // direct-list-init + Dummy object2 = {static_cast(2)}; // copy-list-init + + int values1[]{1, 2, 3}; + int values2[] = {4, 5, 6}; +} + +/// @brief Aggregate Initialization +void aggregate_ini() { + Aggregate a1 = {1, 2}; + Aggregate a2{3, 4}; + Aggregate a3{.x = 3, .y = 4}; + + LOG_S("a1 = {" << a1.x << ", " << a1.y << "}"); + LOG_S("a2 = {" << a2.x << ", " << a2.y << "}"); + LOG_S("a3 = {" << a3.x << ", " << a3.y << "}"); +} + +class Initialization : public IExample { + public: + std::string group() const override { return "core/basics"; } + std::string name() const override { return "Initialization"; } + std::string description() const override { return "Initialization Examples"; } + + void execute() override { + default_ini(); + value_ini(); + direct_ini(); + copy_ini(); + list_ini(); + aggregate_ini(); + } +}; + +REGISTER_EXAMPLE(Initialization); \ No newline at end of file diff --git a/src/core/basics/InitializeVariable.cpp b/src/core/basics/InitializeVariable.cpp deleted file mode 100644 index 33df35a..0000000 --- a/src/core/basics/InitializeVariable.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// cppcheck-suppress-file [unreadVariable, unusedVariable, functionStatic] -#include - -#include "ExampleRegistry.h" - -void initialize_variable(); - -class InitializeVariable : public IExample { - public: - std::string group() const override { return "core"; } - std::string name() const override { return "InitializeVariable"; } - std::string description() const override { return "InitializeVariable"; } - void execute() override { initialize_variable(); } -}; - -REGISTER_EXAMPLE(InitializeVariable); - -struct Foo { - Foo() { std::cout << "Default constructor/ default init\n"; } - - // Foo(int) - explicit Foo(int) { - std::cout << "Constructor called with int / copy init\n"; - } - - Foo(const Foo& other) { - other.dump(); - std::cout << "Copy constructor called\n"; - } - - void dump() const {} -}; - -void initialize_variable() { - std::cout << "\n--- Variable Initialization Examples ---\n"; - // * 1) Default-initialization - int init_default_var; - Foo init_default_obj; - - auto* c = new unsigned char; - *c = 0xF; // actual init at this point for c - delete c; - - // * 2) Value-initialization - int init_value_var1(); - int init_value_var2{}; - - // * 3) direct-init: Type var(value); - Foo direct_init_obj(4); // call Foo(int) - Foo direct_init_obj2(4.3); // look for constructor -> implicit 4.3(float) -> 4(int) -> call Foo(int) - - // * 4) Copy-init: Type var = other; - // 1. Compiler tries to convert the value to a temporary Foo. - // 2. If the constructor is explicit, implicit conversion is blocked -> compilation error. - // 3. Otherwise, a temporary Foo is created and then copied/moved into the variable. - Foo copy_init_obj = (Foo)2.3; - // explicit cast: double 2.3 -> int 2 (implicit narrowing) -> Foo(int) - // -> temporary Foo -> copied/moved into copyInitObj - // Foo copyInitObjError = 2.3; // ERROR: implicit conversion blocked by explicit constructor - // We can explicitly prevent certain conversions using = delete or using {} - - // * 5) List-initialization (Brace init) - // calls the constructor directly without allowing implicit conversions. - Foo brace_init{3}; - // Foo braceInit2{3.3}; // ERORR => Prefer this way - int ar[] = {1, 2, 3}; // aggregate init - int ar2[]{1, 2, 3}; -} \ No newline at end of file diff --git a/src/core/basics/Operations.cpp b/src/core/basics/Operations.cpp index 1eea0c3..5287455 100644 --- a/src/core/basics/Operations.cpp +++ b/src/core/basics/Operations.cpp @@ -1,7 +1,7 @@ -#include #include #include "ExampleRegistry.h" +#include "Logger.h" void arithmeticOperator(); void logicalOperator(); @@ -9,7 +9,7 @@ void bitWiseOperator(); class Operations : public IExample { public: - std::string group() const override { return "core"; } + std::string group() const override { return "core/basics"; } std::string name() const override { return "Operations"; } std::string description() const override { return "Operation"; } void execute() override { @@ -22,107 +22,105 @@ class Operations : public IExample { REGISTER_EXAMPLE(Operations); void arithmeticOperator() { - std::cout << "\n--- ArithmeticOperator Examples ---\n"; + LOG_S("\n--- ArithmeticOperator Examples ---\n"); int a{100}; int b{200}; // Addition - std::cout << "a = " << a << ", b = " << b << "\n"; + LOG_S("a = " << a << ", b = " << b); int sum = a + b; - std::cout << "sum = " << sum << "\n"; + LOG_S("sum = " << sum); // Subtraction - std::cout << "a = " << a << ", b = " << b << "\n"; - int different = a - b; - std::cout << "different = " << different << "\n"; + LOG_S("a = " << a << ", b = " << b); + uint32_t different = a - b; + LOG_S("different = " << different); // Multiplication - std::cout << "a = " << a << ", b = " << b << "\n"; + LOG_S("a = " << a << ", b = " << b); int product = a * b; - std::cout << "product = " << product << "\n"; + LOG_S("product = " << product); // Division - std::cout << "a = " << a << ", b = " << b << "\n"; + LOG_S("a = " << a << ", b = " << b); int quotient = a / b; - std::cout << "quotient = " << quotient << "\n"; + LOG_S("quotient = " << quotient); // Modulus - std::cout << "a = " << a << ", b = " << b << "\n"; + LOG_S("a = " << a << ", b = " << b); int remainder = a % b; - std::cout << "remainder = " << remainder << "\n"; + LOG_S("remainder = " << remainder); // Increment - std::cout << "a = " << a << "\n"; + LOG_S("a = " << a); int pre_in = ++a; // increase a, return a - std::cout << "preIn = " << pre_in << "\n"; + LOG_S("preIn = " << pre_in); - std::cout << "a = " << a << "\n"; + LOG_S("a = " << a); int post_in = a++; // copy a to a copy, then increase a, return copy - std::cout << "postIn = " << post_in << "\n"; + LOG_S("postIn = " << post_in); // Decrement - std::cout << "b = " << b << "\n"; + LOG_S("b = " << b); int pre_de = --b; - std::cout << "preDe = " << pre_de << "\n"; + LOG_S("preDe = " << pre_de); - std::cout << "b = " << b << "\n"; + LOG_S("b = " << b); int post_de = b--; - std::cout << "postDe = " << post_de << "\n"; + LOG_S("postDe = " << post_de); // Comma: int value = (a++, b); // a is incremented, then b is returned - std::cout << "a = " << a << ", b = " << b << "\n"; - std::cout << "comma(a++, b) = " << value << "\n"; + LOG_S("a = " << a << ", b = " << b); + LOG_S("comma(a++, b) = " << value); } void logicalOperator() { - std::cout << "\n--- LogicalOperator Examples ---\n"; + LOG("\n--- LOG_SicalOperator Examples ---"); bool a = true; bool b = false; bool c = true; - std::cout << std::boolalpha; // show true/false instead of 1/0 - std::cout << "a = " << a << ", b = " << b << ", c = " << c << "\n\n"; + // show true/false instead of 1/0 + LOG_S(std::boolalpha << "a = " << a << ", b = " << b << ", c = " << c); // AND (&&) - std::cout << "[AND] a && b = " << (a && b) << "\n"; + LOG_S("[AND] a && b = " << (a && b)); // OR (||) - std::cout << "[OR ] a || b = " << (a || b) << "\n"; + LOG_S("[OR ] a || b = " << (a || b)); // NOT (!) - std::cout << "[NOT] !c = " << (!c) << "\n"; + LOG_S("[NOT] !c = " << (!c)); } void bitWiseOperator() { - std::cout << "\n--- BitWiseOperator Examples ---\n"; - std::bitset<8> bits_a {0b1111'1111}; - std::bitset<8> bits_b {0b1111'0000}; + LOG("\n--- BitWiseOperator Examples ---"); + std::bitset<8> bits_a = 0b1111'1111; std::bitset<8> bits_b = 0b1111'0000; - std::cout - << "bitA = " << bits_a << ", bitB = " << bits_b << "\n"; + LOG_S("bitA = " << bits_a << ", bitB = " << bits_b); // AND std::bitset<8> result = bits_a & bits_b; - std::cout << "bitA && bitB= " << result << "\n"; + LOG_S("bitA && bitB= " << result); // OR result = bits_a | bits_b; - std::cout << "bitA | bitB= " << result << "\n"; + LOG_S("bitA | bitB= " << result); // XOR result = bits_a ^ bits_b; - std::cout << "bitA ^ bitB= " << result << "\n"; + LOG_S("bitA ^ bitB= " << result); // NOT result = ~bits_a; - std::cout << "~bitA = " << result << "\n"; + LOG_S("~bitA = " << result); // LEFT SHIFT result = bits_a << 1; - std::cout << "bitA << 1 = " << result << "\n"; + LOG_S("bitA << 1 = " << result); // RIGHT SHIFT result = bits_a >> 1; - std::cout << "bitA >> 1 = " << result << "\n"; + LOG_S("bitA >> 1 = " << result); } \ No newline at end of file diff --git a/src/core/basics/README.md b/src/core/basics/README.md deleted file mode 100644 index e6c1d1c..0000000 --- a/src/core/basics/README.md +++ /dev/null @@ -1,130 +0,0 @@ -# Basic C++ - -## 1. Initialization in C++ -C++ provides several ways to initialize objects. Each form has different semantics and use cases. - -### 1.1 Default Initialization -Performed when an object is created **without any initializer**. - -```cpp -T object; -new T; -``` - -- Built-in types: uninitialized -- Class types: default constructor is called - ---- - -### 1.2 Value Initialization -Performed when an object is created with an **empty initializer**. - -```cpp -T(); -new T(); -T object{}; -T{}; -new T{}; -``` - -Also used in constructors: - -```cpp -Class::Class() : member() {} -Class::Class() : member{} {} -``` - -- Built-in types: zero-initialized -- Class types: default constructor is called - ---- - -### 1.3 Direct Initialization - -Initializes an object using **explicit constructor arguments**. - -```cpp -T object(arg); -T object(arg1, arg2); - -T object{arg}; // since C++11 -new T(args...); -static_cast(other); -``` - -Used in constructors and lambdas: - -```cpp -Class::Class() : member(args...) {} -[arg]() {}; -``` - ---- - -### 1.4 Copy Initialization - -Initializes an object **from another object or expression**. - -```cpp -T object = other; -f(other); -return other; -throw object; -catch (T object); -``` - -Also used for arrays: - -```cpp -T array[N] = { /* values */ }; -``` - ---- - -### 1.5 List Initialization (since C++11) - -Initializes objects using **brace-enclosed initializer lists**. - -#### Direct List Initialization - -```cpp -T object{arg1, arg2}; -new T{arg1, arg2}; - -Class::Class() : member{arg1, arg2} {} -``` - -#### Copy List Initialization - -```cpp -T object = {arg1, arg2}; -return {arg1, arg2}; -function({arg1, arg2}); -``` - -#### Designated Initializers (since C++20) - -```cpp -T object{ .des1 = arg1, .des2 = arg2 }; -T object = { .des1 = arg1, .des2 = arg2 }; -``` - ---- - -### 1.6 Aggregate Initialization - -Initializes **aggregate types** (no user-defined constructors). - -```cpp -T object = {arg1, arg2}; -T object{arg1, arg2}; // since C++11 -``` - -Designated initializers for aggregates (since C++20): - -```cpp -T object = { .des1 = arg1, .des2 = arg2 }; -T object{ .des1 = arg1, .des2 = arg2 }; -``` - -- A specialized form of list-initialization \ No newline at end of file diff --git a/src/core/basics/TypeQualifier.cpp b/src/core/basics/TypeQualifier.cpp index c456f75..1044f29 100644 --- a/src/core/basics/TypeQualifier.cpp +++ b/src/core/basics/TypeQualifier.cpp @@ -1,32 +1,56 @@ // cppcheck-suppress-file [unreadVariable] -// A function declared constexpr is implicitly an inline function. -// A static member variable (but not a namespace-scope variable) declared constexpr is implicitly an inline variable. -#include -using namespace std; +// A constexpr function is implicitly inline. +// A constexpr static data member is implicitly an inline variable (since C++17). + +#include "Logger.h" namespace { -namespace Constant { +constexpr int square(int x) { + return x * x; +} + +consteval int cube(int x) { + return x * x * x; +} + void run() { - // 1. Name constants + // 1. Named constants const double const_var_g{9.8}; + #define NAME "Phong" - // 2. Literals constants - bool myNameIsAlex{true}; // true is a boolean literal -> type: bool - double d{3.4}; // 3.4 is a double literal -> type: double - std::cout - << "Hello, world!"; // "Hello, world!" is a C-style string literal -> type: const char[14] + // 2. Literal constants + bool my_name_is_alex{true}; ///< true is a boolean literal -> type: bool + double d{3.4}; ///< 3.4 is a floating-point literal -> type: double + + // "Hello, world!" is a string literal -> type: const char[14] + LOG("Hello, world!"); - // 3. Constexpr variables - constexpr double constexpr_var_g{9.8}; // compile-time constant + // 3. constexpr variables + constexpr double kConstexprVarG{9.8}; // compile-time constant - // 4. Constant expression - constexpr double something{ - constexpr_var_g}; // ok: sum is a constant expression - // constexpr double something2{const_var_g}; // error because const_var_g is not the constexprs + // 4. Constant expressions + constexpr double kSomething{kConstexprVarG}; + + // Error: + // constexpr double something2{const_var_g}; + // const_var_g is const, but not guaranteed to be a constant expression + + if constexpr (kConstexprVarG != 9.8) { + LOG("kConstexprVarG != 9.8"); + } else { + LOG("kConstexprVarG == 9.8"); + } + + // 5. Const expression function + + constexpr int a = square(5); // compile-time + int b = square(5); // runtime allowed + + constexpr int c = cube(5); // OK + // int d = cube(5); // Error if not compile-time } -} // namespace Constant } // namespace @@ -34,10 +58,11 @@ void run() { class TypeQualifier : public IExample { public: - std::string group() const override { return "core"; } + std::string group() const override { return "core/basics"; } std::string name() const override { return "TypeQualifier"; } std::string description() const override { return "TypeQualifier"; } - void execute() override { Constant::run(); } + + void execute() override { run(); } }; -REGISTER_EXAMPLE(TypeQualifier); +REGISTER_EXAMPLE(TypeQualifier); \ No newline at end of file diff --git a/src/core/class/RoleOfThreeFiveZero.cpp b/src/core/class/RoleOfThreeFiveZero.cpp index c7602c9..e055742 100644 --- a/src/core/class/RoleOfThreeFiveZero.cpp +++ b/src/core/class/RoleOfThreeFiveZero.cpp @@ -276,7 +276,7 @@ void run() { class RoleOfThreeFiveZero : public IExample { public: - std::string group() const override { return "core"; } + std::string group() const override { return "core/class"; } std::string name() const override { return "RoleOfThreeFiveZero"; } std::string description() const override { return ""; } void execute() override { diff --git a/src/core/concurrency/ConditionVariable.cpp b/src/core/concurrency/ConditionVariable.cpp index 80bbf2f..05cfe3d 100644 --- a/src/core/concurrency/ConditionVariable.cpp +++ b/src/core/concurrency/ConditionVariable.cpp @@ -25,7 +25,7 @@ void worker_thread() { // The thread continues only when 'ready' becomes true. cv.wait(lock, []() { return ready; }); - LOG("Proccessing data"); + LOG("Processing data"); data += " after processing"; finish = true; diff --git a/src/core/concurrency/README.md b/src/core/concurrency/README.md deleted file mode 100644 index 537416f..0000000 --- a/src/core/concurrency/README.md +++ /dev/null @@ -1,230 +0,0 @@ -## 1. Concurrency -- **Concurrency** `refers to the ability` `to proccess` `multiple tasks` `at the same time.` -`On a single core`, it uses `context-switching`, `on multi-core systems`, it can run in parallel. -- It's `used to improve` the `program performance` and `response time`. -- In C/C++, we can archive concurrency by using `threads`. - -(`` is a C++ header that provide `a collection of types and functions` to work with time.) - ---- -## 2. Thread -- Threads are `the basic unit of` multitasking. -- There are many errors and risks associated with concurrency, including: - - `Deadlocks`: `refers to the situation where` two or more threads are blocked, `waiting for each other indefinitely`. - - `Race condition`: `refers to the situation where` two or more threads access `shared data` concurrently, leading to the `undefined behavior`. - - `Starvation`: `refer to the situation where` a thread `is unable to gain` regular access to the shared resources. -=> We can avoid these problems by `proper synchronization` between the threads. -- Use threads if we need to run long-lived and complex tasks. - -### 2.2. Thead Synchronization -- The synchronization can be done by using the following components: - - `Mutex/Lock`: `` they are used to protect the shared resouces, ensure that only one thread can access `the critical sections` at a time. - - `Semaphore`: - - `Futures and Promises`: ``, `` are used for the asynchronous task execution. - - `Condition variable`: `` -### 2.3. Thread Management -- `thread`: an `OS thread` `managed by` the kernel. -- Each `thread` has it own `call stack`, but all `threads` share the **heap**. -- `thread object`: refers to a C++ instance `that associated with` an `active thread` of execution in hardware level. -- `std::thread(callable)`: request the kernel OS to create a thread. -- `std::this_thread`: refer to the current thread -- `join`: blocks the current thread until the thread that identified by *this (a.ka. object thread) finished its execution. Note that if the exception -is throw before `join`, `std::terminate` might be called, and will kill the entire program process, not an invidual thread. -- `detach`: separates the `thread of the execution` from the `thread object`, allowing the execution to continue running. -- `yield`: give priority to other threads, pause its execution -- Use `return` to kill a thread. -### 2.4. Sharing Data -- `Global/Static Variable`: can be accessed by all threads. -- `Pass By Reference`: we need to explicitly wrap the args in `std::ref` to pass by reference and is the only way to properly get data out of a thread -- `thread_local` to create a static variable per thread. - -### 2.5. Atomic -- An atomic type is a type that implements atomic operations. It's used to guarantee no race conditions will occur. -- e.g. -`std::atomic` - `std::atomic_bool` -- e.g. -```cpp -#include -#include -#include -#include - -std::atomic_int acnt; -int cnt; - -void f() -{ - for (auto n{10000}; n; --n) - { - ++acnt; - ++cnt; - // Note: for this example, relaxed memory order is sufficient, - // e.g. acnt.fetch_add(1, std::memory_order_relaxed); - } -} - -int main() -{ - { - std::vector pool; - for (int n = 0; n < 10; ++n) - pool.emplace_back(f); - } - - std::cout << "The atomic counter is " << acnt << '\n' - << "The non-atomic counter is " << cnt << '\n'; -} -``` - -### 2.6. Mutex/Locks -- Mutexs are mutual exclusion objects, are owned by the thread that takes it. -- e.g. -```cpp -#include - -// Create your mutex here -std::mutex my_mutex; - -thread_function() -{ - my_mutex.lock(); // Acquire lock - // Do some non-thread safe stuff... - my_mutex.unlock(); // Release lock -} -``` - -- There are serveral types of mutex, including: - - `mutex` - - `timed_mutex` - - `recursive_mutex` - - `recursive_timed_mutex` - - `shared_timed_mutex` - -- **Lock Guard Type** is a wrapper mutex that provides a convinient RAII-style mechanism. -- They are several `lock guard types`, including: - - `std::lock_guard`: basic RAII lock for a single mutex. Locks immediately on construction, unlocks on destruction. - - `std::scoped_lock`: RAII lock for multiple mutexes - - `std::unique_lock`: Flexible RAII lock for a single mutex. Supports deferred/manual locking, unlocking, and use with condition variables, has unlock() / lock() - - `shared_lock`: RAII lock for shared access (readers) on a std::shared_mutex. Allows multiple concurrent readers. - -### 2.7. Condition Variable for event handling - -- `std::condition_variable` is a synchronization primitive used with a `std::mutex` to block one or more threads until another thread both modifies a `shared variable` (the condition) and `notifies` the `std::condition_variable`. -- `wait` will releases the lock and blocks the thread until the condition is fullfilled. -- e.g. -```cpp -#include -#include -#include -#include -#include - -std::mutex m; -std::condition_variable cv; -std::string data; -bool ready = false; -bool processed = false; - -void worker_thread() -{ - // 1. wait until main() sends data - std::unique_lock lk(m); - cv.wait(lk, []{ return ready; }); - - // after the wait, we own the lock - std::cout << "Worker thread is processing data\n"; - data += " after processing"; - - // send data back to main() - processed = true; - std::cout << "Worker thread signals data processing completed\n"; - - // manual unlocking is done before notifying, to avoid waking up - // the waiting thread only to block again (see notify_one for details) - lk.unlock(); - cv.notify_one(); -} - -int main() -{ - std::thread worker(worker_thread); - - data = "Example data"; - // send data to the worker thread - { - std::lock_guard lk(m); - ready = true; - std::cout << "main() signals data ready for processing\n"; - } - cv.notify_one(); - - // wait for the worker - { - std::unique_lock lk(m); - cv.wait(lk, []{ return processed; }); - } - std::cout << "Back in main(), data = " << data << '\n'; - - worker.join(); -} - -Output: -main() signals data ready for processing -Worker thread is processing data -Worker thread signals data processing completed -Back in main(), data = Example data after processing -``` - ---- -## 3. Task -- A `task` is a unit of asynchronous work that can be scheduled for execution. -- It is a higher-level abstraction than a thread. -- `It's generally considered` faster to work with tasks `as opposed to` threads, because the runtime can reuse threads and manage scheduling more efficiently. -- We use `task` if we want fairly simple code and don't care for managing threads, and are running shorts tasks. - -### 3.1. Promises and Futures -- `std::future` is a class template that stores a value that will be assigned in the future, and provide a way to access that value. - - It will also block if its value is accessed before the value is assigned. - - `Futures` are the objects that are returned by async operations (`std::async, std::promise, std::packaged_task`) -- `std::shared_future`: works the same way as `std::future`, except it is copytable, so multiple threads are allowed to wait for the same shared state. -- `std::promise` provides a way to store a value or an exception that will be retrieved asynchronously via a `std::future`. - - It is commonly used to pass results between threads. - - It creates a `std::future` using `get_future()`. - - The `std::promise` and the `std::future` share a common shared state. - - The `std::promise` sets the value of the shared state using `set_value()`. - - The associated `std::future` retrieves the value using `get()`. - - -### 3.2. Async -- `Async` is a function template allows we to spawn threads to do work async, then collect the results from them via the `future` mechanism. -- A call to `std::async` returns a `std::future` object. -- The future can later be used to retrieve the result of the asynchronous computation. - ```cpp - auto future = std::async(some_function, arg_1, arg_2); - ``` -- `std::async` may run the function in a `new thread` or `defer execution` until the result is requested. - - `std::launch::async`: launch in a separated thread - - `std::launch::deferred`: only be called on `get()` - - default: defer to system - ```cpp - auto future = std::async(std::launch::async,some_function, arg_1, arg_2); - ``` -![future](../../../docs/image/future_promis.png) -![future_flow](../../../docs/image/future_promis_flow.png) -- e.g. -```cpp -// Pass in function pointer -auto future = std::async(std::launch::async, some_function, arg_1, arg_2); - -// Pass in function reference -auto future = std::async(std::launch::async, &some_function, arg_1, arg_2); - -// Pass in function object -struct SomeFunctionObject -{ - void operator() (int arg_1){} -}; -auto future = std::async(std::launch::async, SomeFunctionObject(), arg_1); - -// Lambda function -auto future = std::async(std::launch::async, [](){}); -``` \ No newline at end of file diff --git a/src/core/concurrency/Timing.cpp b/src/core/concurrency/Timing.cpp new file mode 100644 index 0000000..f8b6582 --- /dev/null +++ b/src/core/concurrency/Timing.cpp @@ -0,0 +1,28 @@ +#include +#include "ExampleRegistry.h" +#include "Logger.h" + +#include + +void run() { + const auto start = std::chrono::steady_clock::now(); + std::this_thread::sleep_for(std::chrono::seconds(5)); + const auto finished = std::chrono::steady_clock::now(); + + const std::chrono::duration elapsed = + finished - start; // default unit is second + LOG_S("Elapsed time: " << elapsed); +} + +class Timing : public IExample { + public: + std::string group() const override { return "core/concurrency"; } + std::string name() const override { return "Timing"; } + std::string description() const override { + return "The examples for code timing"; + } + + void execute() override { run(); } +}; + +REGISTER_EXAMPLE(Timing); \ No newline at end of file diff --git a/src/core/exception/README.md b/src/core/exception/README.md deleted file mode 100644 index 353894a..0000000 --- a/src/core/exception/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Exception handling -**- We prefer using exceptions with the following reasons:** - - Forces the caller to recognize an error condition and handle it rather than stopping program. - - Let exceptions propagate in the call stack where they can be handled probably (e.g destroys all the objects in scope error ) - - TBD ---- -The exception mechanism has a minimal performance cost if no exception is thrown. If an exception is thrown, the cost of the stack traversal and unwinding is roughly comparable to the cost of a function call. - -**- Design for exception safe:** - - Low/ middle layers: catch and rethrow an exception if they do not have enough context to handle. This way, the exceptions will propagate up the call stack. - - Highest layers: let an unhandled exception terminate a program. (`exit(-1)`) - ---- -- *Resource Acquisition Is Initialization (RAII) (resource lifetime = object lifetime)* \ No newline at end of file diff --git a/src/core/expression/README.md b/src/core/expression/README.md deleted file mode 100644 index b31d604..0000000 --- a/src/core/expression/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# Theory - -## 1. Lambda Expressions -- In C++11 and later, lambda is a convenient way of defining an anonymous function object right at the location where it's involked or passed as an argument to a function. - -- Syntax: -```cpp -[=] () mutable throw() -> int -{ - int n = x + y; - return n; -} - -``` -- `[=]`: capture clause a.k.a lambda introducer -- `()`: (O) pararam list a.k.a lambda declarator -- `mutable`: (O) -- `throw()`: (O) -- `-> int`: (O) trailing-return-type -- body -- `[](){ ... }`defines a lambda -- `[](){ ... }()` defines and immediately CALLS it - -### 1.1 Capture Clause -- It uses to introduce new variables in its body, specifics which vars are captured, and whether the capture is `by value[=]` or `by reference [&]`. -- An empty capture clause `[]` indicates that the body accesses no vars in the enclosing scope. -- An identifier or `this` cannot appear more than once in a capture scope. -- Since C++14, we can introduce and initialize new vars in the capture scope. -- E.g. -```cpp -int a{}; -int b{}; - -auto f = []{ // no capture - return 1; -} - -auto f0 = [a]{ // capture by value - return a+1; -} - -auto f1 = [&a]{ - return a+=1; // capture by reference (a = 1) -} - -auto f2 = [=]{ - return a + b; // all capture by value -} - -auto f3 = [&]{ - a+=1; - b+=1; - return a + b; // all capture by reference -} - -auto f4 = [int a{}]{ // no capture - return a; -} -``` - -## 2. Function Pointers -- A function pointer is a pointer variable that stores the address of a function with a specific return type and parameter list. -- Syntax: -```cpp -// Declare -return_type (*FuncPtr) (parameter type, ....); - -// Referencing: Assigning a function’s address to the function pointer. -FuncPtr = function_name; - -// 3) Dereferencing: Invoking the function using the pointer. The dereference operator * is optional during function calls. -FuncPtr(10, 20); // Preferred -(*FuncPtr)(10, 20); // Also valid -``` - -- e.g. -```cpp -void print(int a){ - std::cout << a; -} - -void (*FuncPtr)(int a); -FuncPtr = print; -FuncPtr(1); -``` - diff --git a/src/core/expression/FunctionPointer.cpp b/src/core/function/FunctionPointer.cpp similarity index 64% rename from src/core/expression/FunctionPointer.cpp rename to src/core/function/FunctionPointer.cpp index bbc2e09..c50b6b6 100644 --- a/src/core/expression/FunctionPointer.cpp +++ b/src/core/function/FunctionPointer.cpp @@ -1,14 +1,16 @@ #include -#include #include +#include "Logger.h" namespace { int increase(int a, int b) { + LOG(""); return static_cast(a > b); } int decrease(int a, int b) { + LOG(""); return static_cast(a < b); } @@ -16,37 +18,39 @@ int decrease(int a, int b) { // // Declaring // return_type (*FuncPtr) (parameter type, ....); // typedef int (*SortFcn)(int a, int b); -using SortFcn = int (*)(int, int); -void run() { - std::vector vect{1, 6, 4, 22, 0, 6, 33, 39, -5}; +/// @brief Function Pointer +using sort_function_ptr = int (*)(int, int); +void run() { + std::vector vect{-1, -6, 4, 2, 0, 6, 3, 9, -5}; auto f_print = [](const std::vector& vec) { + std::ostringstream oss; for (const auto& e : vec) { - std::cout << e << " "; + oss << e << " "; } - std::cout << "\n"; + LOG(oss.str()); }; - std::cout << "Before sorting : \n"; + LOG("Before sorting : "); f_print(vect); - std::cout << "Sorting in descending " << "order \n"; - + LOG("Sorting in descending order"); // Use auto auto sort_type_auto = increase; std::sort(vect.begin(), vect.end(), sort_type_auto); // Use pointer - SortFcn sort_type_ptr = decrease; + sort_function_ptr sort_type_ptr = decrease; f_print(vect); - std::cout << "Sorting with absolute " << "value as parameter\n "; + LOG("Sorting with absolute value as parameter"); std::sort(vect.begin(), vect.end(), sort_type_ptr); + std::ostringstream oss; for (auto i : vect) - std::cout << i << " "; - std::cout << "\n"; + oss << i << " "; + LOG(oss.str()); } } // namespace @@ -54,7 +58,7 @@ void run() { class FunctionPointer : public IExample { public: - std::string group() const override { return "core/expression"; } + std::string group() const override { return "core/function"; } std::string name() const override { return "FunctionPointer"; } std::string description() const override { return "Function Pointer Example"; diff --git a/src/core/function/Functional.cpp b/src/core/function/Functional.cpp new file mode 100644 index 0000000..1c59db8 --- /dev/null +++ b/src/core/function/Functional.cpp @@ -0,0 +1,52 @@ +#include +#include "Logger.h" + +namespace { +// Functions for simple math operations +int add(int a, int b) { + LOG(""); + return a + b; +} +int sub(int a, int b) { + LOG(""); + return a - b; +} +int mul(int a, int b) { + LOG(""); + return a * b; +} +int divs(int a, int b) { + LOG(""); + return a / b; +} + +void func(int a, int b, const std::function& calc) { + if (!calc) { + LOG("No function provided"); + return; + } + + int result = calc(a, b); + LOG_S(result); +} + +void run() { + func(8, 2, add); + func(8, 2, sub); + func(8, 2, mul); + func(8, 2, divs); + func(8, 2, [](int a, int b) { return a % b; }); // 0 +} +} // namespace + +#include "ExampleRegistry.h" + +class Functional : public IExample { + public: + std::string group() const override { return "core/function"; } + std::string name() const override { return "Functional"; } + std::string description() const override { return "Functional Example"; } + void execute() override { run(); } +}; + +REGISTER_EXAMPLE(Functional); diff --git a/src/core/expression/Lambda.cpp b/src/core/function/Lambda.cpp similarity index 74% rename from src/core/expression/Lambda.cpp rename to src/core/function/Lambda.cpp index 47e5064..63aee7e 100644 --- a/src/core/expression/Lambda.cpp +++ b/src/core/function/Lambda.cpp @@ -5,34 +5,36 @@ // custom (2) // template void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp); -#include +#include #include +#include "Logger.h" namespace { void run() { std::vector vect{-1, -6, 4, 2, 0, 6, 3, 9, -5}; - auto f_print = [](const std::vector& vec) { + std::ostringstream oss; for (const auto& e : vec) { - std::cout << e << " "; + oss << e << " "; } - std::cout << "\n"; + LOG(oss.str()); }; - std::cout << "Before sorting : \n"; + LOG("Before sorting : "); f_print(vect); - std::cout << "Sorting in descending " << "order \n"; + LOG("Sorting in descending order"); std::sort(vect.begin(), vect.end(), [](int a, int b) { return a > b; }); f_print(vect); - std::cout << "Sorting with absolute " << "value as parameter\n "; + LOG("Sorting with absolute value as parameter"); std::sort(vect.begin(), vect.end(), [](int a, int b) { return a < b; }); + std::ostringstream oss; for (auto i : vect) - std::cout << i << " "; - std::cout << "\n"; + oss << i << " "; + LOG(oss.str()); } } // namespace @@ -40,7 +42,7 @@ void run() { class Lambda : public IExample { public: - std::string group() const override { return "core/expression"; } + std::string group() const override { return "core/function"; } std::string name() const override { return "Lambda"; } std::string description() const override { return "Lambda Expression Example"; diff --git a/src/core/overloading/AllocationOperator.cpp b/src/core/function/operator_overloading/AllocationOperator.cpp similarity index 96% rename from src/core/overloading/AllocationOperator.cpp rename to src/core/function/operator_overloading/AllocationOperator.cpp index c599e9f..d2c44e3 100644 --- a/src/core/overloading/AllocationOperator.cpp +++ b/src/core/function/operator_overloading/AllocationOperator.cpp @@ -89,7 +89,7 @@ void run() { class AllocationOperator : public IExample { public: - std::string group() const override { return "core/overloading"; } + std::string group() const override { return "core/overloading_operator"; } std::string name() const override { return "AllocationOperator"; } std::string description() const override { return ""; } diff --git a/src/core/overloading/ArithmeticOperator.cpp b/src/core/function/operator_overloading/ArithmeticOperator.cpp similarity index 96% rename from src/core/overloading/ArithmeticOperator.cpp rename to src/core/function/operator_overloading/ArithmeticOperator.cpp index 9997921..dd5080d 100644 --- a/src/core/overloading/ArithmeticOperator.cpp +++ b/src/core/function/operator_overloading/ArithmeticOperator.cpp @@ -72,7 +72,7 @@ void run() { class ArithmeticOperator : public IExample { public: - std::string group() const override { return "core/overloading"; } + std::string group() const override { return "core/overloading_operator"; } std::string name() const override { return "ArithmeticOperator"; } std::string description() const override { return ""; } diff --git a/src/core/function/operator_overloading/AssignmentOperator.cpp b/src/core/function/operator_overloading/AssignmentOperator.cpp new file mode 100644 index 0000000..65f85f5 --- /dev/null +++ b/src/core/function/operator_overloading/AssignmentOperator.cpp @@ -0,0 +1,52 @@ +// cppcheck-suppress-file [unreadVariable] + +// Overloading operator= + +#include "ExampleRegistry.h" +#include "Logger.h" + +namespace { +class Cents { + public: + explicit Cents(int cents = 0) : cent_{cents} {} + + /// @brief Copy constructor + Cents(const Cents& other) { + LOG(""); + cent_ = other.cent_; + } + + /// @brief Copy assignment + Cents& operator=(const Cents& cents) { + // do the copy + LOG(""); + cent_ = cents.cent_; + return *this; + } + + private: + int cent_; +}; + +void run() { + Cents c1(100); + Cents c2; + + LOG("c2 = c1;"); + c2 = c1; // c2.operator=(c1) + + LOG("Cents c3 = c1;"); + Cents c3 = c1; // c3(c1) +} +} // namespace + +class AssignmentOperator : public IExample { + public: + std::string group() const override { return "core/overloading_operator"; } + std::string name() const override { return "Assignment Operator"; } + std::string description() const override { return ""; } + + void execute() override { run(); } +}; + +REGISTER_EXAMPLE(AssignmentOperator); \ No newline at end of file diff --git a/src/core/overloading/ClassMemberAccessOperator.cpp b/src/core/function/operator_overloading/ClassMemberAccessOperator.cpp similarity index 94% rename from src/core/overloading/ClassMemberAccessOperator.cpp rename to src/core/function/operator_overloading/ClassMemberAccessOperator.cpp index 70c7fa1..6147732 100644 --- a/src/core/overloading/ClassMemberAccessOperator.cpp +++ b/src/core/function/operator_overloading/ClassMemberAccessOperator.cpp @@ -54,7 +54,7 @@ void run() { class ClassMemberAccessOperator : public IExample { public: - std::string group() const override { return "core/overloading"; } + std::string group() const override { return "core/overloading_operator"; } std::string name() const override { return "ClassMemberAccessOperator"; } std::string description() const override { return ""; } diff --git a/src/core/overloading/ComparisonOperator.cpp b/src/core/function/operator_overloading/ComparisonOperator.cpp similarity index 95% rename from src/core/overloading/ComparisonOperator.cpp rename to src/core/function/operator_overloading/ComparisonOperator.cpp index f69d79d..ac77598 100644 --- a/src/core/overloading/ComparisonOperator.cpp +++ b/src/core/function/operator_overloading/ComparisonOperator.cpp @@ -37,7 +37,7 @@ void run() { class ComparisonOperator : public IExample { public: - std::string group() const override { return "core/overloading"; } + std::string group() const override { return "core/overloading_operator"; } std::string name() const override { return "ComparisonOperator"; } std::string description() const override { return ""; } diff --git a/src/core/function/operator_overloading/FunctionCallOperator.cpp b/src/core/function/operator_overloading/FunctionCallOperator.cpp new file mode 100644 index 0000000..fe3b4d0 --- /dev/null +++ b/src/core/function/operator_overloading/FunctionCallOperator.cpp @@ -0,0 +1,37 @@ +// cppcheck-suppress-file [] + +// operator() + +#include +#include "ExampleRegistry.h" +#include "Logger.h" + +namespace { +#include // for assert() + +struct Linear { + double a, b; + + double operator()(double x) const { + LOG(""); + return a * x + b; + } +}; + +void run() { + Linear f{2, 1}; // Represents function 2x + 1. + double f_0 = f(0); + LOG_S(f_0); +} +} // namespace + +class FunctionCallOperator : public IExample { + public: + std::string group() const override { return "core/overloading_operator"; } + std::string name() const override { return "FunctionCallOperator"; } + std::string description() const override { return ""; } + + void execute() override { run(); } +}; + +REGISTER_EXAMPLE(FunctionCallOperator); \ No newline at end of file diff --git a/src/core/overloading/InDecOperator.cpp b/src/core/function/operator_overloading/InDecOperator.cpp similarity index 71% rename from src/core/overloading/InDecOperator.cpp rename to src/core/function/operator_overloading/InDecOperator.cpp index f093340..f5f284e 100644 --- a/src/core/overloading/InDecOperator.cpp +++ b/src/core/function/operator_overloading/InDecOperator.cpp @@ -1,17 +1,14 @@ // cppcheck-suppress-file [postfixOperator] // prefix/posfix -#include #include "ExampleRegistry.h" +#include "Logger.h" namespace { class Cents { - private: - int m_cents_{}; - public: - explicit Cents(int cents) : m_cents_{cents} {} - int getCents() const { return m_cents_; } + explicit Cents(int cents) : cents_{cents} {} + int getCents() const { return cents_; } // pre: inc -> return new Cents& operator++(); @@ -20,25 +17,36 @@ class Cents { // return old -> inc Cents operator++(int); Cents operator--(int); + + private: + int cents_{}; }; -// pre ++x/--x +/// @brief pre ++x Cents& Cents::operator++() { - ++m_cents_; + LOG(""); + ++cents_; return *this; } + +/// @brief pre ++x Cents& Cents::operator--() { - --m_cents_; + LOG(""); + --cents_; return *this; } -// pos x++/x-- +/// @brief pos x++ Cents Cents::operator++(int) { + LOG(""); Cents temp{*this}; // create copy ++(*this); // increase origin return temp; // return old } + +/// @brief pos x-- Cents Cents::operator--(int) { + LOG(""); Cents temp{*this}; --(*this); return temp; @@ -50,13 +58,13 @@ void run() { ++cent; cent--; --cent; - std::cout << cent.getCents() << "\n"; + LOG_S(cent.getCents()); } } // namespace class InDecOperator : public IExample { public: - std::string group() const override { return "core/overloading"; } + std::string group() const override { return "core/overloading_operator"; } std::string name() const override { return "InDecOperator"; } std::string description() const override { return ""; } diff --git a/src/core/overloading/IOOperator.cpp b/src/core/function/operator_overloading/StreamOperator.cpp similarity index 65% rename from src/core/overloading/IOOperator.cpp rename to src/core/function/operator_overloading/StreamOperator.cpp index 3ef19f2..905dd31 100644 --- a/src/core/overloading/IOOperator.cpp +++ b/src/core/function/operator_overloading/StreamOperator.cpp @@ -1,17 +1,14 @@ // cppcheck-suppress-file[] -// >> << -#include +// operator>> and operator<< #include "ExampleRegistry.h" +#include "Logger.h" namespace { class Cents { - private: - int m_cents_{}; - public: - explicit Cents(int cents) : m_cents_{cents} {} - int getCents() const { return m_cents_; } + explicit Cents(int cents) : cents_{cents} {} + int getCents() const { return cents_; } // // We won’t be able to use a member overload if the left operand is either not a class (e.g. int), // // or it is a class that we can’t modify (e.g. std::ostream). @@ -21,14 +18,21 @@ class Cents { // out << m_cents; // return out; // } + + private: + int cents_{}; }; +/// @brief Write obj to stream std::ostream& operator<<(std::ostream& out, const Cents& c) { + LOG(""); out << c.getCents(); return out; } +/// @brief Read obj from stream std::istream& operator>>(std::istream& in, Cents& c) { + LOG(""); int cents{}; in >> cents; c = in ? Cents{cents} : Cents{0}; @@ -38,12 +42,10 @@ std::istream& operator>>(std::istream& in, Cents& c) { void run() { Cents c1{25}; // out >> - std::cout - << c1 - << "\n"; // operator<<(std::cout, c1) -> operator<<(std::ostream,Cents) + std::cout << c1 << "\n"; // in >> - std::cin >> c1; // operator>>(std::cin,c1) -> operator<<(std::istream,Cents) + std::cin >> c1; std::cout << c1 << "\n"; // clearing lefover newline @@ -52,13 +54,13 @@ void run() { } } // namespace -class IOOperator : public IExample { +class StreamOperator : public IExample { public: - std::string group() const override { return "core/overloading"; } - std::string name() const override { return "IOOperator"; } + std::string group() const override { return "core/overloading_operator"; } + std::string name() const override { return "StreamOperator"; } std::string description() const override { return ""; } void execute() override { run(); } }; -REGISTER_EXAMPLE(IOOperator); \ No newline at end of file +REGISTER_EXAMPLE(StreamOperator); \ No newline at end of file diff --git a/src/core/overloading/SubscriptOperator.cpp b/src/core/function/operator_overloading/SubscriptOperator.cpp similarity index 93% rename from src/core/overloading/SubscriptOperator.cpp rename to src/core/function/operator_overloading/SubscriptOperator.cpp index e2105e2..06e74a8 100644 --- a/src/core/overloading/SubscriptOperator.cpp +++ b/src/core/function/operator_overloading/SubscriptOperator.cpp @@ -34,7 +34,7 @@ void run() { class SubscriptOperator : public IExample { public: - std::string group() const override { return "core/overloading"; } + std::string group() const override { return "core/overloading_operator"; } std::string name() const override { return "SubscriptOperator"; } std::string description() const override { return ""; } diff --git a/src/core/overloading/TypeCast.cpp b/src/core/function/operator_overloading/TypeCast.cpp similarity index 94% rename from src/core/overloading/TypeCast.cpp rename to src/core/function/operator_overloading/TypeCast.cpp index eb1d677..7ac3233 100644 --- a/src/core/overloading/TypeCast.cpp +++ b/src/core/function/operator_overloading/TypeCast.cpp @@ -51,7 +51,7 @@ void run() { class TypeCast : public IExample { public: - std::string group() const override { return "core/overloading"; } + std::string group() const override { return "core/overloading_operator"; } std::string name() const override { return "TypeCast"; } std::string description() const override { return ""; } diff --git a/src/core/overloading/UnaryOperator.cpp b/src/core/function/operator_overloading/UnaryOperator.cpp similarity index 91% rename from src/core/overloading/UnaryOperator.cpp rename to src/core/function/operator_overloading/UnaryOperator.cpp index a7322b4..020ccf4 100644 --- a/src/core/overloading/UnaryOperator.cpp +++ b/src/core/function/operator_overloading/UnaryOperator.cpp @@ -31,7 +31,7 @@ void run() { class UnaryOperator : public IExample { public: - std::string group() const override { return "core/overloading"; } + std::string group() const override { return "core/overloading_operator"; } std::string name() const override { return "UnaryOperator"; } std::string description() const override { return ""; } diff --git a/src/core/overloading/AssignmentOperator.cpp b/src/core/overloading/AssignmentOperator.cpp deleted file mode 100644 index e0c60e7..0000000 --- a/src/core/overloading/AssignmentOperator.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// cppcheck-suppress-file [unreadVariable] - -// Overloading operator= - -#include -#include "ExampleRegistry.h" - -namespace { -class Cents { - private: - int m_cents_{}; - - public: - explicit Cents(int cents = 0) : m_cents_{cents} {} - - int getCents() const { return m_cents_; } - void setCents(int cents) { m_cents_ = cents; } - - // Copy constructor - Cents(const Cents& other) { - std::cout << "Cents(const Cents& other)\n"; - m_cents_ = other.m_cents_; - } - - // Overload copy assignment - Cents& operator=(const Cents& cents) { - // do the copy - std::cout << "Cents& operator=(const Cents& cents)\n"; - m_cents_ = cents.m_cents_; - return *this; - } -}; - -void run() { - Cents c1{100}; - Cents c2; - c2 = c1; // calls overloaded copy assignment - Cents c3 = c1; // calls copy constructor because c3 didn't exist yet -} -} // namespace - -class AssignmentOperator : public IExample { - public: - std::string group() const override { return "core/overloading"; } - std::string name() const override { return "AssignmentOperator"; } - std::string description() const override { return ""; } - - void execute() override { run(); } -}; - -REGISTER_EXAMPLE(AssignmentOperator); \ No newline at end of file diff --git a/src/core/overloading/ParenthesisOperator.cpp b/src/core/overloading/ParenthesisOperator.cpp deleted file mode 100644 index acf133c..0000000 --- a/src/core/overloading/ParenthesisOperator.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// cppcheck-suppress-file [] - -// operator() -#include -#include "ExampleRegistry.h" - -namespace { -#include // for assert() - -class Matrix { - private: - double m_data_[4][4]{}; - - public: - double& operator()(int row, int col); - double operator()(int row, int col) const; // for const objects -}; - -double& Matrix::operator()(int row, int col) { - assert(row >= 0 && row < 4); - assert(col >= 0 && col < 4); - - return m_data_[row][col]; -} - -double Matrix::operator()(int row, int col) const { - assert(row >= 0 && row < 4); - assert(col >= 0 && col < 4); - - return m_data_[row][col]; -} - -void run() { - Matrix matrix; - matrix(1, 2) = 4.5; - std::cout << matrix(1, 2) << '\n'; -} -} // namespace - -class ParenthesisOperator : public IExample { - public: - std::string group() const override { return "core/overloading"; } - std::string name() const override { return "ParenthesisOperator"; } - std::string description() const override { return ""; } - - void execute() override { run(); } -}; - -REGISTER_EXAMPLE(ParenthesisOperator); \ No newline at end of file diff --git a/src/core/smart_pointer/README.md b/src/core/smart_pointer/README.md deleted file mode 100644 index c57d5da..0000000 --- a/src/core/smart_pointer/README.md +++ /dev/null @@ -1,164 +0,0 @@ -## 1. Smart Pointers -- The STL includes smart pointers, which are defined in the to help ensure that programs are free of memory and resource leaks. -- Raw pointers are only used in small code blocks where performance is critical and we can control the ownership stuff. -- e.g. -```cpp -void UseRawPointer() -{ - // Using a raw pointer -- not recommended. - Song* pSong = new Song(L"Nothing on You", L"Bruno Mars"); - - // Use pSong... - - // Delete - delete pSong; -} - -void UseSmartPointer() -{ - // Declare a smart pointer on stack and pass it the raw pointer. - unique_ptr song2(new Song(L"Nothing on You", L"Bruno Mars")); - - // Use song2... - wstring s = song2->duration_; - //... - -} // song2 is deleted automatically here. -``` - -- After being initialized, the smart pointer owns the raw pointer. -- Use the overloaded `->` and `*` operators to access the object. -- Use `get()` to access the raw pointer directly. -- There are some kinds of the smart pointer: - - `unique_ptr`: this is the default choice for POCO, **one owner** - - `shared_ptr`: **multiple owners**, the raw pointer deleted when all owners have gone out of scope or have otherwise given up ownership. - - `weak_ptr`: provides access to an object that is owned by one or more `shared_ptr`, but holds a **non-owning** reference to this object. - ---- -## 2. std::unique_ptr -- It can **only be moved** and cannot be passed by value, copied. -- e.g. -```cpp -unique_ptr SongFactory(const std::wstring& artist, const std::wstring& title) -{ - // Implicit move operation into the variable that stores the result. - return make_unique(artist, title); -} - -void MakeSongs() -{ - // Create a new unique_ptr with a new object. - auto song = make_unique(L"Mr. Children", L"Namonaki Uta"); - - // Use the unique_ptr. - vector titles = { song->title }; - - // Move raw pointer from one unique_ptr to another. - unique_ptr song2 = std::move(song); - - // Obtain unique_ptr from function that returns by value. - auto song3 = SongFactory(L"Michael Jackson", L"Beat It"); -} - -// Create a unique_ptr to an array of 5 integers. -auto p = make_unique(5); - -// Initialize the array. -for (int i = 0; i < 5; ++i) -{ - p[i] = i; - wcout << p[i] << endl; -} -``` - -## 3. std::shared_ptr -- It is designed for scenarios in which more than one owner needs to manage the lifetime of an object. -- It can be copied, passed by value in function arguments, and assigned to other `shared_ptr` instances. -- All the instances point to the same object, and share access to one "control block" that increments and decrements the reference count whenever a new shared_ptr is added, goes out of scope, or is reset. **When the reference count reaches zero, the control block deletes the memory resource and itself**. -- e.g. -```cpp -// Use make_shared function when possible. -auto sp1 = make_shared(L"The Beatles", L"Im Happy Just to Dance With You"); - -// Ok, but slightly less efficient. -// Note: Using new expression as constructor argument -// creates no named variable for other code to access. -shared_ptr sp2(new Song(L"Lady Gaga", L"Just Dance")); - -// When initialization must be separate from declaration, e.g. class members, -// initialize with nullptr to make your programming intent explicit. -shared_ptr sp5(nullptr); -//Equivalent to: shared_ptr sp5; -//... -sp5 = make_shared(L"Elton John", L"I'm Still Standing"); - -//Initialize with copy constructor. Increments ref count. -auto sp3(sp2); - -//Initialize via assignment. Increments ref count. -auto sp4 = sp2; - -//Initialize with nullptr. sp7 is empty. -shared_ptr sp7(nullptr); - -// Initialize with another shared_ptr. sp1 and sp2 -// swap pointers as well as ref counts. -sp1.swap(sp2); -``` - -## 4. std::weak_ptr -- It's used to to access the underlying object of a shared_ptr without causing the reference count to be incremented. -- If the memory has already been deleted, the weak_ptr's bool operator returns false. -- e.g. -> std::weak_ptr is a very good way to solve the dangling pointer problem. By just using raw pointers it is impossible to know if the referenced data has been deallocated or not. Instead, by letting a std::shared_ptr manage the data, and supplying std::weak_ptr to users of the data, the users can check validity of the data by calling expired() or lock(). -You could not do this with std::shared_ptr alone, because all std::shared_ptr instances share the ownership of the data which is not removed before all instances of std::shared_ptr are removed. Here is an example of how to check for dangling pointer using lock(): -```cpp -// Source - https://stackoverflow.com/a/21877073 -// Posted by sunefred, modified by community. See post 'Timeline' for change history -// Retrieved 2026-02-06, License - CC BY-SA 4.0 - -#include -#include - -int main() -{ - // OLD, problem with dangling pointer - // PROBLEM: ref will point to undefined data! - - int* ptr = new int(10); - int* ref = ptr; - delete ptr; - - // NEW - // SOLUTION: check expired() or lock() to determine if pointer is valid - - // empty definition - std::shared_ptr sptr; - - // takes ownership of pointer - sptr.reset(new int); - *sptr = 10; - - // get pointer to data without taking ownership - std::weak_ptr weak1 = sptr; - - // deletes managed object, acquires new pointer - sptr.reset(new int); - *sptr = 5; - - // get pointer to new data without taking ownership - std::weak_ptr weak2 = sptr; - - // weak1 is expired! - if(auto tmp = weak1.lock()) - std::cout << "weak1 value is " << *tmp << '\n'; - else - std::cout << "weak1 is expired\n"; - - // weak2 points to new data (5) - if(auto tmp = weak2.lock()) - std::cout << "weak2 value is " << *tmp << '\n'; - else - std::cout << "weak2 is expired\n"; -} -``` \ No newline at end of file diff --git a/src/core/string/CString.cpp b/src/core/string/CString.cpp index 3ec28cf..26ee4af 100644 --- a/src/core/string/CString.cpp +++ b/src/core/string/CString.cpp @@ -1,12 +1,14 @@ #include // C-String -#include +#include "Logger.h" namespace { +void log_string_info(const char* label, const char* str, size_t size) { + LOG_S(label << ": \"" << str << "\"" << " | size=" << size + << " | length=" << strlen(str)); +} -namespace initialize_string { - -// Memory layout (addresses are illustrative): -// Stack (modifiable array) +namespace create { +/// @brief Stack (modifiable array) // 0x7fffc000: str2[0] = 'H' // 0x7fffc001: str2[1] = 'e' // 0x7fffc002: str2[2] = 'l' @@ -15,7 +17,7 @@ namespace initialize_string { // 0x7fffc005: str2[5] = '\0' // (str2 starts at 0x7fffc000) -// Data / Read-only segment (string literal) +/// @brief String literal (Data / Read-only segment) // 0x00403000: 'H' // 0x00403001: 'e' // 0x00403002: 'l' @@ -27,165 +29,177 @@ namespace initialize_string { // Pointer variable // 0x7fffbff0: str1 = 0x00403000 // str1 holds address of string literal void run() { - // **1. As a character array (modifiable)** - char str_array[] = "this is a strArray literal"; - std::cout << str_array << " - size " << sizeof(str_array) << " - length " - << strlen(str_array) << "\n"; - str_array[0] ^= ' '; - std::cout << str_array << "\n"; - - // **2. As a a pointer to a string literal const (read-only)** - // Literal is const char* - char* str_ptr = "this is a strPtr literal"; - std::cout << str_ptr << " - size " << sizeof(str_ptr) << " - length " - << strlen(str_ptr) << "\n"; - // strPtr[0] ^= ' '; // ERROR - - // **3. Using sprintf / snprintf (string formatting)** + LOG("=== create ==="); + + /// @brief Create a string as a character array (modifiable) + char str_array[] = "this is a string array literal"; + log_string_info("str_array", str_array, sizeof(str_array)); + str_array[0] ^= ' '; // modify first character + log_string_info("modified str_array", str_array, sizeof(str_array)); + + /// @brief Create a pointer to a string literal const (read-only) + const char* str_ptr = "this is a strPtr literal"; + log_string_info("str_ptr", str_ptr, sizeof(str_ptr)); + // str_ptr[0] ^= ' '; // ERROR + + /// @brief Create a string using sprintf / snprintf char str_formatted[50]; int num_var = 21; + const char text[] = "example"; - // sprintf (unsafe if buffer too small) - sprintf(str_formatted, "sprintf: %d", num_var); - std::cout << str_formatted << " - size " << sizeof(str_formatted) - << " - length " << strlen(str_formatted) << "\n"; + sprintf(str_formatted, "sprintf: %d", + num_var); // sprintf (unsafe if buffer too small) + log_string_info("sprintf", str_formatted, sizeof(str_formatted)); - // snprintf (safer, limits buffer size) - snprintf(str_formatted, sizeof(str_formatted), "snprintf %s %d", str_array, - num_var); - std::cout << str_formatted << " - size " << sizeof(str_formatted) - << " - length " << strlen(str_formatted) << "\n"; + snprintf(str_formatted, sizeof(str_formatted), "snprintf %s %d", text, + num_var); // snprintf (safer, limits buffer size) + log_string_info("snprintf", str_formatted, sizeof(str_formatted)); } -} // namespace initialize_string +} // namespace create -namespace copy_string { +namespace copy { void run() { + LOG("=== copy ==="); + const char src[] = "CopyStr"; char dst[50]; - // **1. Copy full string** + /// @brief Copy full string strcpy(dst, src); + log_string_info("strcpy", dst, sizeof(dst)); - std::cout << dst << " - size " << sizeof(dst) << " - length " << strlen(dst) - << "\n"; - - // **2. Copy the number of charactor** + /// @brief Copy first N characters strncpy(dst, "Hello123", 5); - dst[5] = '\0'; // ensure null-termination - - std::cout << dst << " - size " << sizeof(dst) << " - length " << strlen(dst) - << "\n"; + dst[5] = '\0'; // strncpy may not append '\0' + log_string_info("strncpy", dst, sizeof(dst)); } -} // namespace copy_string +} // namespace copy -namespace concat_string { +namespace concat { void run() { + LOG("=== concat ==="); + const char part1[] = "Hello"; const char part2[] = "World"; char dst[50] = ""; - // **1. Append full str** + /// @brief Append full strings strcat(dst, part1); strcat(dst, part2); strcat(dst, " !!"); - std::cout << dst << " - size " << sizeof(dst) << " - length " << strlen(dst) - << "\n"; + log_string_info("strcat", dst, sizeof(dst)); - // **2. Append the number of charactors** + /// @brief Append first N characters strncat(dst, "1234", 3); - std::cout << dst << " - size " << sizeof(dst) << " - length " << strlen(dst) - << "\n"; + log_string_info("strncat", dst, sizeof(dst)); } -} // namespace concat_string +} // namespace concat -namespace compare_string { +namespace compare { void run() { + LOG("=== compare ==="); + const char str1[] = "abc"; const char str2[] = "abcde"; - // **0. Compare memory** - // int memcmp ( const void * ptr1, const void * ptr2, size_t num ); + + /// @brief Compare memory + // int memcmp(const void* ptr1, const void* ptr2, size_t num); int result0 = memcmp(str1, str2, sizeof(str1)); - std::cout << "strcmp(str1, \"abcde\") = " << result0 << "\n"; + LOG_S("memcmp(str1, str2, sizeof(str1)) = " << result0); - // **1. Compare full str** + /// @brief Compare full string int result1 = strcmp(str1, "abc"); int result2 = strcmp(str1, str2); - std::cout << "strcmp(str1, \"abc\") = " << result1 << "\n"; - std::cout << "strcmp(str1, str2) = " << result2 << "\n"; + LOG_S("strcmp(str1, \"abc\") = " << result1); + LOG_S("strcmp(str1, str2) = " << result2); - // **2. Compare first N characters** + /// @brief Compare first N characters int result3 = strncmp(str1, str2, 3); - std::cout << "strncmp(str1, str2, 3) = " << result3 << "\n"; + LOG_S("strncmp(str1, str2, 3) = " << result3); } -} // namespace compare_string +} // namespace compare -namespace parse_string { +namespace parse { void run() { - char str[] = "A,B,C,D,"; // OK - // char* str = "A,B,C,D,"; // ERROR - const + LOG("=== compare ==="); - // **1. Splitting a string by some delimiter** - // 1.1. strtok - not safe - char* delimiter = ","; + char str[] = "A,B,C,D,"; + // char* str = "A,B,C,D,"; // ERROR - string literal is read-only + + const char* delimiter = ","; + + /// @brief strtok (NOT thread-safe) + LOG_S("=== strtok ==="); const char* token = strtok(str, delimiter); while (token != nullptr) { - std::cout << "strtok - token :" << token << "\n"; + LOG_S("token = " << token); + token = strtok(nullptr, delimiter); } + + /// @brief strtok problem example { - std::cout << " === problem === \n"; + LOG_S("=== strtok problem example ==="); + char str1[] = "a,b,c"; char str2[] = "1,2,3"; // Parse str1 - const char* token1 = strtok(str1, delimiter); // token1 = "a" - std::cout << "str1 first token: " << token1 << "\n"; + const char* token1 = strtok(str1, delimiter); + LOG_S("str1 first token = " << token1); // Parse str2 - const char* token2 = strtok(str2, delimiter); // token2 = "1" - std::cout << "str2 first token: " << token2 << "\n"; + const char* token2 = strtok(str2, delimiter); + LOG_S("str2 first token = " << token2); // Continue parsing str1 - token1 = strtok(nullptr, delimiter); // Unexpected! - std::cout << "str1 second token: " << token1 << "\n"; + token1 = strtok(nullptr, delimiter); + LOG_S("str1 second token = " << token1 << " (unexpected)"); } - // Have to Reset string for the next handle because strtok + /// @brief strtok_r (thread-safe / reentrant) + LOG_S("=== strtok_r ==="); + char str2[] = "one,two,three"; - // 1.2. strtok_r - safe char* saveptr; const char* token2 = strtok_r(str2, delimiter, &saveptr); + while (token2 != nullptr) { - std::cout << "strtok - token :" << token2 << "\n"; + LOG_S("token = " << token2); token2 = strtok_r(nullptr, delimiter, &saveptr); } - // **2. strcspn: find first occurrence of any chars in reject set** - const char sample[] = "hello123world"; - size_t pos = strcspn(sample, "0123456789"); // pos = 5 + /// @brief strcspn + // Find first occurrence of any character in reject set + LOG_S("=== strcspn ==="); - std::cout << "Sample string: " << sample << "\n"; - std::cout << "First digit from \"0123456789\" found at index: " << pos - << "\n"; - std::cout << "The digit is: " << sample[pos] << "\n"; + const char sample[] = "hello123world"; + size_t pos = strcspn(sample, "0123456789"); + LOG_S("sample = " << sample); + LOG_S("first digit index = " << pos); + LOG_S("digit = " << sample[pos]); } -} // namespace parse_string +} // namespace parse namespace number_conversion { void run() { - // **1. string to integer** + LOG_S("=== number_conversion ==="); + + /// @brief String to integer const char str_num[] = "100"; int num = atoi(str_num); - std::cout << num << "\n"; + LOG_S("atoi(\"100\") = " << num); - // **2. string to double** + /// @brief String to double const char str_num_d[] = "100.1234__123"; double num_d = atof(str_num_d); - std::cout << num_d << "\n"; + LOG_S("atof(\"100.1234__123\") = " << num_d); + /// @brief strtod char* end; num_d = strtod(str_num_d, &end); - std::cout << num_d << " end part:" << end << "\n"; + LOG_S("strtod = " << num_d); + LOG_S("remaining string = " << end); } } // namespace number_conversion } // namespace @@ -198,12 +212,11 @@ class CString : public IExample { std::string name() const override { return "C-String"; } std::string description() const override { return "C-String Example"; } void execute() override { - initialize_string::run(); - copy_string::run(); - concat_string::run(); - compare_string::run(); - parse_string::run(); - + create::run(); + copy::run(); + concat::run(); + compare::run(); + parse::run(); number_conversion::run(); } }; diff --git a/src/core/string/README.md b/src/core/string/README.md deleted file mode 100644 index 45dc79a..0000000 --- a/src/core/string/README.md +++ /dev/null @@ -1,295 +0,0 @@ -# 1. String Formatting - -| Method | Standard | Pros | Cons | -|------------------|----------|--------------------------|------------------------------| -| `std::format` | C++20 | Clean, safe, Python-like | Needs newer compiler | -| `+` concatenation | C++98 | Simple | Hard to format numbers | -| `stringstream` | C++98 | Flexible streaming | Slow, verbose | -| `snprintf` | C | Very fast, classic | Unsafe if misused | -https://www.geeksforgeeks.org/cpp/strings-in-cpp/ -https://hackingcpp.com/cpp/std/string_basics - ---- - -# 2. Create Strings - -```cpp -std::string s = "hello"; -std::string s2("hello"); -std::string s3(5, 'a'); // "aaaaa" -``` ---- - -# 3. Basic Properties - -| Function | Description | -| ------------ | --------------------- | -| `s.size()` | number of characters | -| `s.length()` | same as size | -| `s.empty()` | check empty | -| `s.clear()` | remove all characters | - ---- - -# 4. Access Characters - -```cpp -s[0] // no bounds check -s.at(0) // bounds check -s.front() // first char -s.back() // last char -``` - ---- - -# 5. Modify String - -## append - -```cpp -s.append(" world"); -s += " world"; -``` - -## insert - -```cpp -s.insert(5, "XXX"); -``` - -## erase - -```cpp -s.erase(0,2); -``` - -## replace - -```cpp -s.replace(0,5,"hi"); -``` - -## remove `\n` - -```cpp - std::string s = "hello\nworld\n"; - auto new_end = std::remove(s.begin(), s.end(), '\n'); - s.erase(new_end) - // std::erase(s,'\n') - s.erase(std::remove(s.begin(), s.end(), '\n'), s.end()); -``` - - ---- - -# 6. Substring - -```cpp -std::string sub = s.substr(pos, len); -``` - -Example: - -```cpp -std::string s = "abcdef"; - -s.substr(2,3); // "cde" -``` - -Important: - -``` -substr() does NOT modify original string -``` - ---- - -# 7. Find / Search - -## find substring - -```cpp -size_t pos = s.find("abc"); - -if (pos != std::string::npos) - std::cout << "found"; -``` - -## find char - -```cpp -s.find('a'); -``` - -## find last - -```cpp -s.rfind("abc"); -``` - ---- - -# 8. Compare Strings - -```cpp -if (a == b) -if (a != b) -if (a < b) -``` - -Using compare: - -```cpp -a.compare(b) -``` - -Return: - -``` -0 -> equal -<0 -> smaller ->0 -> larger -``` - -### C++20 - -```cpp -s.starts_with("abc"); -s.ends_with("xyz"); -``` - -### Before C++20 - -#### starts_with - -```cpp -s.rfind("abc",0) == 0 -``` - -#### ends_with - -```cpp -s.size() >= 3 && -s.compare(s.size()-3,3,"xyz") == 0 -``` - ---- - -# 9. Convert String - -## string → int - -### C++11 - -```cpp -int n = std::stoi(s); -``` - -Other: - -``` -stol -stoll -stof -stod -``` - -Example: - -```cpp -double x = std::stod("3.14"); -``` - ---- - -## number → string - -```cpp -std::string s = std::to_string(123); -``` - ---- - -# 10. String Parsing - -## split using stringstream - -```cpp -#include - -std::string line = "a,b,c"; -std::stringstream ss(line); -std::string item; - -while(std::getline(ss,item,',')) { - std::cout << item << std::endl; -} -``` - -Result - -``` -a -b -c -``` - ---- - -# 11. Trim Whitespace (manual) - -Common pattern: - -```cpp -s.erase(0, s.find_first_not_of(" \t\n\r")); -s.erase(s.find_last_not_of(" \t\n\r") + 1); -``` - -Removes leading/trailing spaces. - ---- - -# 12. Convert Case - -```cpp -#include - -std::transform(s.begin(), s.end(), s.begin(), ::tolower); -std::transform(s.begin(), s.end(), s.begin(), ::toupper); -``` - ---- - -# 20. Functions Modify the String - -Modify: - -``` -append -insert -erase -replace -clear -push_back -pop_back -``` - -Do NOT modify: - -``` -substr -find -compare -size -empty -``` - -Rule: - -``` -method() const -> does NOT modify object -``` - ---- - -# 21. Common Bugs \ No newline at end of file diff --git a/src/core/string/StdString.cpp b/src/core/string/StdString.cpp index 4b42b66..3f7281b 100644 --- a/src/core/string/StdString.cpp +++ b/src/core/string/StdString.cpp @@ -1,159 +1,213 @@ #include +#include #include -#include +#include #include + #include "ExampleRegistry.h" +#include "Logger.h" + +namespace { + +void log_string_info(const char* label, const std::string& str) { + LOG_S(label << ": \"" << str << "\"" << " | size=" << str.size() + << " | empty=" << std::boolalpha << str.empty()); +} + +} // namespace namespace create { + void run() { - // 1. Direct initialization with literal + LOG("=== create ==="); + + /// @brief Direct initialization with literal std::string s1 = "string 1"; - // 2. Constructor initialization + /// @brief Constructor initialization std::string s2("string 2"); - // 3. Repeat character constructor + /// @brief Repeat character constructor std::string s3(5, 's'); // "sssss" - // 4. Concatenated string literals (compile-time) + /// @brief Concatenated string literals (compile-time) std::string s4 = "First " - "Second"; // -> "First Second" + "Second"; - // 5. Raw string literal (no escaping needed) + /// @brief Raw string literal (no escaping needed) std::string s5 = R"(C:\folder\file.txt)"; - // Print results - std::cout << "s1: " << s1 << '\n'; - std::cout << "s2: " << s2 << '\n'; - std::cout << "s3: " << s3 << '\n'; - std::cout << "s4: " << s4 << '\n'; - std::cout << "s5: " << s5 << '\n'; + log_string_info("s1", s1); + log_string_info("s2", s2); + log_string_info("s3", s3); + log_string_info("s4", s4); + log_string_info("s5", s5); } + } // namespace create namespace modify { + void run() { - std::string ss = "xPhong"; - std::cout << "init : " << ss << '\n'; - - // append - ss.append("Nguyen"); - std::cout << "append : " << ss << '\n'; - - // insert at position - ss.insert(6, "Vanxx"); - std::cout << "insert : " << ss << '\n'; - - // erase (pos, length) - ss.erase(9, 2); - std::cout << "erase : " << ss << '\n'; - - // erase char using pos - auto new_end = std::remove(ss.begin(), ss.end(), 'O'); - ss.erase(new_end, ss.end()); - std::cout << "erase 'O') : " << ss << '\n'; - // #include - // std::erase(ss, '\n'); - - // replace (pos, length, new_string) - ss.replace(0, 1, "My name is "); - std::cout << "replace : " << ss << '\n'; - - // convert case - std::transform(ss.begin(), ss.end(), ss.begin(), ::tolower); - std::cout << "tolower : " << ss << '\n'; - std::transform(ss.begin(), ss.end(), ss.begin(), ::toupper); - std::cout << "toupper : " << ss << '\n'; - - // trim white - ss.erase(0, ss.find_first_not_of(" \t\n\r")); - std::cout << "trim first : " << ss << '\n'; - ss.erase(ss.find_last_not_of(" \t\n\r") + 1); - std::cout << "trim last : " << ss << '\n'; + LOG("=== modify ==="); + + std::string str = "xPhong"; + log_string_info("init", str); + + /// @brief Append + str.append("Nguyen"); + log_string_info("append", str); + + /// @brief Insert at position + str.insert(6, "Vanxx"); + log_string_info("insert", str); + + /// @brief Erase by position and length + str.erase(9, 2); + log_string_info("erase", str); + + /// @brief Erase character using remove + erase idiom + auto new_end = std::remove(str.begin(), str.end(), 'O'); + str.erase(new_end, str.end()); + log_string_info("erase 'O'", str); + + // C++20 + // std::erase(str, 'O'); + + /// @brief Replace sub + str.replace(0, 1, "My name is "); + log_string_info("replace", str); + + /// @brief Convert to lowercase + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return std::tolower(c); }); + log_string_info("tolower", str); + + /// @brief Convert to uppercase + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return std::toupper(c); }); + log_string_info("toupper", str); + + /// @brief Trim leading whitespace + str = " Hello World "; + log_string_info("before trim", str); + + str.erase(0, str.find_first_not_of(" \t\n\r")); + log_string_info("trim first", str); + + /// @brief Trim trailing whitespace + str.erase(str.find_last_not_of(" \t\n\r") + 1); + log_string_info("trim last", str); } + } // namespace modify namespace sub { + void run() { - std::string ss = "PhongNguyen"; - std::cout << "init : " << ss << '\n'; - std::string first_name = ss.substr(0, 5); - std::cout << "sub : " << first_name << '\n'; + LOG("=== sub ==="); + + std::string str = "PhongNguyen"; + log_string_info("init", str); + + /// @brief Extract sub + std::string first_name = str.substr(0, 5); + log_string_info("substr", first_name); } + } // namespace sub namespace search { + void run() { - std::string ss = "PhongNguyen"; - std::cout << "init : " << ss << '\n'; + LOG("=== search ==="); + + std::string str = "PhongNguyen"; + log_string_info("init", str); - // find char - size_t pos = ss.find('N'); + /// @brief Find character + size_t pos = str.find('N'); if (pos != std::string::npos) { - std::string second_name = ss.substr(pos); // from pos to end (default) - std::cout << "find 'N' : at " << pos << " -> " << second_name << '\n'; + std::string last_name = str.substr(pos); + + LOG_S("find('N')" << " | pos=" << pos << " | result=\"" << last_name + << "\""); } - // find substring - pos = ss.find("ong"); + /// @brief Find sub + pos = str.find("ong"); if (pos != std::string::npos) { - std::cout << "find \"ong\" : at " << pos << '\n'; + LOG_S("find(\"ong\")" << " | pos=" << pos); } - // find last occurrence - pos = ss.rfind('n'); + /// @brief Find last occurrence + pos = str.rfind('n'); if (pos != std::string::npos) { - std::cout << "rfind 'n' : at " << pos << '\n'; + LOG_S("rfind('n')" << " | pos=" << pos); } } + } // namespace search namespace compare { + void run() { - std::string ss1 = "PhongNguyen"; - std::string ss2 = "PhongNguyen"; + LOG("=== compare ==="); - int result = ss1.compare(ss2); + std::string str1 = "PhongNguyen"; + std::string str2 = "PhongNguyen"; - std::cout << "compare result : " << result << '\n'; - std::cout << "equal ? : " << std::boolalpha << (result == 0) << '\n'; + /// @brief Compare strings + int result = str1.compare(str2); + LOG_S("compare result = " << result); + LOG_S("equal = " << std::boolalpha << (result == 0)); } } // namespace compare namespace convert { + void run() { - // string -> int - std::string sint = "3"; - int ivalue = std::stoi(sint); - std::cout << "stoi : " << ivalue << '\n'; + LOG("=== convert ==="); + + /// @brief String to integer + std::string str_int = "3"; + int ivalue = std::stoi(str_int); + LOG_S("stoi(\"3\") = " << ivalue); - // string -> double - std::string sdouble = "3.3"; - double dvalue = std::stod(sdouble); - std::cout << "stod : " << std::setprecision(4) << dvalue << '\n'; + /// @brief String to double + std::string str_double = "3.3"; + double dvalue = std::stod(str_double); + LOG_S("stod(\"3.3\") = " << std::setprecision(4) << dvalue); - // number -> string + /// @brief Number to string int value = 999; - std::string svalue = std::to_string(value); - std::cout << "to_string : " << svalue << '\n'; + std::string str_value = std::to_string(value); + log_string_info("to_string", str_value); } + } // namespace convert namespace parsing { + void run() { + LOG("=== parsing ==="); + std::string line = "a,b,c"; - std::cout << "init : " << line << '\n'; - char deli = ','; + log_string_info("init", line); + + /// @brief Parse CSV-like string + char delimiter = ','; std::stringstream ss(line); std::string item; - std::cout << "Parsing with delimiter ',': \n"; - while (std::getline(ss, item, deli)) { - std::cout << item << std::endl; + LOG_S("Parsing with delimiter ','"); + while (std::getline(ss, item, delimiter)) { + LOG_S("token = " << item); } } + } // namespace parsing class StdString : public IExample { @@ -161,6 +215,7 @@ class StdString : public IExample { std::string group() const override { return "core/string"; } std::string name() const override { return "StdString"; } std::string description() const override { return "StdString Example"; } + void execute() override { create::run(); modify::run(); @@ -172,4 +227,4 @@ class StdString : public IExample { } }; -REGISTER_EXAMPLE(StdString); +REGISTER_EXAMPLE(StdString); \ No newline at end of file diff --git a/src/core/string/StringFormatting.cpp b/src/core/string/StringFormatting.cpp index e8167b7..e82f132 100644 --- a/src/core/string/StringFormatting.cpp +++ b/src/core/string/StringFormatting.cpp @@ -1,70 +1,93 @@ #include #include -#include +#include #include #include #include "ExampleRegistry.h" +#include "Logger.h" + +namespace { + +void log_formatted(const char* method, const std::string& text) { + LOG_S(method << ": \"" << text << "\"" << " | size=" << text.size()); +} + +} // namespace namespace std_format { + void run() { + LOG("=== std_format ==="); + std::string name{"Phong"}; int score = 100; - std::string s = - std::format("User {} has {} points, Pi: {:.2f}\n", name, score, 3.14159); - - std::cout << s; + /// @brief C++20 std::format + std::string str = + std::format("User {} has {} points, Pi: {:.2f}", name, score, 3.14159); + log_formatted("std::format", str); } + } // namespace std_format namespace concatenation { + void run() { + LOG("=== concatenation ==="); + std::string name{"Phong"}; int score = 100; - std::string s = "User " + name + " has " + std::to_string(score) + - " points, Pi: " + std::to_string(3.14159) + "\n"; - - std::cout << s; + /// @brief String concatenation using operator+ + std::string str = "User " + name + " has " + std::to_string(score) + + " points, Pi: " + std::to_string(3.14159); + log_formatted("concatenation", str); } + } // namespace concatenation namespace stream { + void run() { + LOG("=== stream ==="); + std::string name{"Phong"}; int score = 100; + /// @brief String formatting using stringstream std::stringstream ss; - ss << "User " << name << " has " << score << " points, Pi: " << 3.14159 - << "\n"; - - std::cout << ss.str(); + ss << "User " << name << " has " << score << " points, Pi: " << std::fixed + << std::setprecision(2) << 3.14159; + log_formatted("stringstream", ss.str()); } + } // namespace stream namespace c_style { + void run() { + LOG("=== c_style ==="); + std::string name{"Phong"}; int score = 100; + /// @brief C-style formatting using snprintf char buffer[128]; - std::snprintf(buffer, sizeof(buffer), "User %s has %d points, Pi: %.2f\n", + std::snprintf(buffer, sizeof(buffer), "User %s has %d points, Pi: %.2f", name.c_str(), score, 3.14159); - - std::cout << buffer; + log_formatted("snprintf", buffer); } + } // namespace c_style class StringFormatting : public IExample { public: std::string group() const override { return "core/string"; } - std::string name() const override { return "StringFormatting"; } - std::string description() const override { - return "String formatting examples (format, concatenation, stream, " - "C-style)"; + return "String formatting examples " + "(std::format, concatenation, stringstream, C-style)"; } void execute() override { diff --git a/src/core/utils/Variadic.cpp b/src/core/utils/Variadic.cpp index 57a0519..3f02483 100644 --- a/src/core/utils/Variadic.cpp +++ b/src/core/utils/Variadic.cpp @@ -5,14 +5,12 @@ #include "Logger.h" namespace c_variadic_style { -/** - * @brief Simple C-style variadic function example. - * - * The format string controls how arguments are read: - * d -> int - * c -> char - * f -> double - */ + +/// @brief Simple C-style variadic function +/// The format string controls how arguments are read: +/// d -> int +/// c -> char +/// f -> double void simple_printf(const char* fmt, ...) { // Holds all unnamed arguments va_list args; @@ -53,14 +51,11 @@ void run() { } // namespace c_variadic_style namespace modern_variadic_style { -/** - * @brief Base case for recursion. - */ + +/// @brief Base case for recursion inline void simple_printf(const char* /*fmt*/) {} -/** - * @brief Print one argument based on format specifier. - */ +/// @brief Print one argument based on format specifier template void print_arg(char fmt, const T& value) { switch (fmt) { @@ -81,9 +76,7 @@ void print_arg(char fmt, const T& value) { } } -/** - * @brief Recursive variadic template implementation. - */ +/// @brief Recursive variadic template implementation template void simple_printf(const char* fmt, T value, Args... args) { if (*fmt == '\0') { @@ -107,9 +100,7 @@ void run() { class Variadic : public IExample { public: std::string group() const override { return "core/utils"; } - std::string name() const override { return "Variadic"; } - std::string description() const override { return "Examples for C-style variadic arguments"; } diff --git a/src/dp/behavioral/Command.cpp b/src/dp/behavioral/Command.cpp index f72c3f6..a6a2bfb 100644 --- a/src/dp/behavioral/Command.cpp +++ b/src/dp/behavioral/Command.cpp @@ -11,9 +11,9 @@ // UML: docs/uml/patterns_behavioral_command.drawio.svg -#include #include #include +#include "Logger.h" namespace { namespace command { @@ -37,17 +37,17 @@ class ICommand { class Receiver { public: void doCheck() { - std::cout << "Receiver checking... \n"; + LOG("Receiver checking... "); dummy_++; }; void doInit() { - std::cout << "Receiver initializing... \n"; + LOG("Receiver initializing... "); dummy_++; }; void doLaunch(const std::string& arg) { - std::cout << "Receiver launching... \n\t" << arg << "\n"; + LOG("Receiver launching... \n\t" + arg); dummy_++; }; @@ -63,7 +63,7 @@ class Receiver { */ class SimpleConcreteCommand : public ICommand { public: - void execute() const override { std::cout << "\t SimpleCommand executed \n"; } + void execute() const override { LOG("executed"); } }; class ComplexConcreteCommand : public ICommand { @@ -76,7 +76,7 @@ class ComplexConcreteCommand : public ICommand { : receiver_{receiver}, payload_{std::move(payload)} {}; void execute() const override { - std::cout << "\t ComplexCommand executed \n"; + LOG("executed"); this->receiver_->doCheck(); this->receiver_->doInit(); this->receiver_->doLaunch(payload_); @@ -109,6 +109,7 @@ class Invoker { void setOnFinish(ICommand* command) { this->on_finish_ = command; } void invoke() const { + LOG("executed"); if (on_start_ != nullptr) { on_start_->execute(); } @@ -119,20 +120,20 @@ class Invoker { } }; -namespace client { -void clientCode(const Invoker* invoker) { - invoker->invoke(); -} - -} // namespace client - void run() { + auto client_code = [](const Invoker* invoker) { + invoker->invoke(); + }; + + // Receiver: UI auto* ui = new Receiver(); + // How to execute these command when something triggered auto* invoker = new Invoker(); invoker->setOnStart(new SimpleConcreteCommand()); invoker->setOnFinish(new ComplexConcreteCommand(ui, "cmd --version")); - client::clientCode(invoker); + + client_code(invoker); delete ui; } } // namespace command diff --git a/src/dp/creational/AbstractFactory.cpp b/src/dp/creational/AbstractFactory.cpp index e2f51f0..ffb5626 100644 --- a/src/dp/creational/AbstractFactory.cpp +++ b/src/dp/creational/AbstractFactory.cpp @@ -1,16 +1,14 @@ // cppcheck-suppress-file [functionStatic] -// Abstract Factory is a creational design pattern that lets you produce -// families of related objects without specifying their concrete classes. -// Appicability: -// (*) when your code needs to work with various families of related products, -// but you don’t want it to depend on the concrete classes -// of those products—they might be unknown beforehand or you simply want to -// allow for future extensibility. -// (**) when you have a class with a set of Factory Methods that blur its -// primary responsibility. - -// UML: docs/uml/patterns_creational_abstractfactory.drawio.svg +// Abstract Factory — create families of related products without concrete types. +// +// Flow in this file: +// 1. Define product interfaces -> IGdbProduct, ICMakeProduct +// 2. Implement concrete products per OS -> Linux / Windows / MacOs variants +// 3. Define an abstract factory -> IProductAbstractFactory +// 4. Implement concrete factories -> one factory = one matching family +// 5. Client uses one factory for all products -> products stay consistent (same OS) + #include #include #include "ExampleRegistry.h" @@ -18,19 +16,16 @@ namespace { namespace abstract_factory { -/** - * The Product interface declares the operations that all concrete products must - * implement. - */ + +/// @class Product Interface +/// @brief Declares the operations that all concrete products must implement class IGdbProduct { public: virtual ~IGdbProduct() = default; virtual void launch() const = 0; }; -/** - * Concrete Products provide various implementations of the Product interface. - */ +/// @brief The concrete product class LinuxGdbProduct : public IGdbProduct { public: void launch() const override { @@ -75,60 +70,62 @@ class MacOsCMakeProduct : public ICMakeProduct { } }; -// =================================================================================== - -/* - * Abstract Factory - * provides an abstract interface for creating a family of products - */ +/// @class Abstract Factory +/// @brief Provide abstract interface for creating a family of products class IProductAbstractFactory { public: virtual ~IProductAbstractFactory() = default; - virtual IGdbProduct* create_gdb_product() = 0; - virtual ICMakeProduct* create_cmake_product() = 0; + virtual std::unique_ptr create_gdb_product() = 0; + virtual std::unique_ptr create_cmake_product() = 0; }; -/* - * Concrete Factory - * each concrete factory create a family of products and client uses - * one of these factories so it never has to instantiate a product object - */ +/// @class Concrete Factory +/// @brief concrete factory create a family of products and client uses +/// one of these factories so it never has to instantiate a product object class WindowsProductFactory : public IProductAbstractFactory { public: - IGdbProduct* create_gdb_product() override { return new WindowsGdbProduct(); } - ICMakeProduct* create_cmake_product() override { - return new WindowsCMakeProduct(); + std::unique_ptr create_gdb_product() override { + return std::make_unique(); + } + std::unique_ptr create_cmake_product() override { + return std::make_unique(); } }; class LinuxProductFactory : public IProductAbstractFactory { public: - IGdbProduct* create_gdb_product() override { return new LinuxGdbProduct(); } - ICMakeProduct* create_cmake_product() override { - return new LinuxCMakeProduct(); + std::unique_ptr create_gdb_product() override { + return std::make_unique(); + } + + std::unique_ptr create_cmake_product() override { + return std::make_unique(); } }; class MacOsProductFactory : public IProductAbstractFactory { public: - IGdbProduct* create_gdb_product() override { return new MacOsGdbProduct(); } - ICMakeProduct* create_cmake_product() override { - return new MacOsCMakeProduct(); + std::unique_ptr create_gdb_product() override { + return std::make_unique(); } -}; -// =================================================================================== + std::unique_ptr create_cmake_product() override { + return std::make_unique(); + } +}; -// static redudant inside anonymous namespace -IProductAbstractFactory* create_product_factory(const std::string& os) { +/// @brief Factory selector +/// static redudant inside anonymous namespace +std::unique_ptr create_product_factory( + const std::string& os) { if (os == "linux") { - return new LinuxProductFactory(); + return std::make_unique(); } if (os == "windows") { - return new WindowsProductFactory(); + return std::make_unique(); } if (os == "macos") { - return new MacOsProductFactory(); + return std::make_unique(); } LOG("OS not support yet - " + os); @@ -138,25 +135,16 @@ IProductAbstractFactory* create_product_factory(const std::string& os) { void run() { LOG("Abstract Factory Pattern Example"); - /** - * The client code works with factories and products only through abstract - * types: AbstractFactory and AbstractProduct. This lets you pass any factory or - * product subclass to the client code without breaking it. - */ auto client_code = [](IProductAbstractFactory* f) { - ICMakeProduct* cmake = f->create_cmake_product(); - IGdbProduct* gdb = f->create_gdb_product(); + auto cmake = f->create_cmake_product(); + auto gdb = f->create_gdb_product(); cmake->launch(); gdb->launch(); - - delete cmake; - delete gdb; }; - std::string os = "linux"; - IProductAbstractFactory* factory = create_product_factory(os); - client_code(factory); - delete factory; + const std::string os = "linux"; + auto factory = create_product_factory(os); + client_code(factory.get()); } } // namespace abstract_factory } // namespace diff --git a/src/dp/creational/Builder.cpp b/src/dp/creational/Builder.cpp index c9ee38e..ae861a2 100644 --- a/src/dp/creational/Builder.cpp +++ b/src/dp/creational/Builder.cpp @@ -1,20 +1,21 @@ // cppcheck-suppress-file [functionStatic] -// Builder is a creational design pattern that lets you construct complex -// objects step by step. The pattern allows you to produce different types and -// representations of an object using the same construction code. Appicability: -// (*) Use the Builder pattern to get rid of a “telescoping constructor”. -// (**) when you want your code to be able to create different representations -// of some product (for example, stone and wooden houses). - -// UML: docs/uml/patterns_behavioral_iterator.drawio.svg - +// Flow in this file: +// 1. Define the product being built -> Product (parts list) +// 2. Define a builder interface -> IBuilder (produce_part_N / build) +// 3. Share common builder state -> AbstractBuilder (reset / product_) +// 4. Implement concrete builders -> SimpleBuilder / ComplexBuilder +// 5. Client chains steps, then build() -> same steps, different representations + +#include #include #include #include #include #include "Logger.h" +#include "ExampleRegistry.h" + namespace { namespace builder_pattern { class Product { @@ -37,10 +38,8 @@ class Product { } }; -/** - * The Builder interface specifies methods for creating the different parts of - * the Product objects. - */ +/// @class Builder Interface +/// @brief specifics methods for creating the different parts class IBuilder { public: virtual ~IBuilder() = default; @@ -49,53 +48,30 @@ class IBuilder { virtual IBuilder& produce_part_2() = 0; virtual IBuilder& produce_part_3() = 0; - virtual Product* build() = 0; + virtual std::unique_ptr build() = 0; }; class AbstractBuilder : public IBuilder { protected: - Product* product_; + std::unique_ptr product_; public: - explicit AbstractBuilder() { product_ = new Product(); } - - ~AbstractBuilder() override { delete product_; } - - AbstractBuilder(const AbstractBuilder& other) { - - delete product_; - - product_ = new Product(); - *product_ = *other.product_; - } - - AbstractBuilder& operator=(const AbstractBuilder& other) { - if (this == &other) { - return *this; - } + explicit AbstractBuilder() : product_{std::make_unique()} {} - delete product_; - product_ = new Product(); - *product_ = *other.product_; + AbstractBuilder(const AbstractBuilder&) = delete; + AbstractBuilder& operator=(const AbstractBuilder&) = delete; - return *this; - } + AbstractBuilder(AbstractBuilder&&) = default; + AbstractBuilder& operator=(AbstractBuilder&&) = default; - // the child classes are no longer override this function + /// @brief the child classes are no longer override this function IBuilder& reset() final { - - delete product_; - product_ = new Product(); - + product_ = std::make_unique(); return *this; } }; -/** - * The Concrete Builder classes follow the Builder interface and provide - * specific implementations of the building steps. Your program may have several - * variations of Builders, implemented differently. - */ +/// @class Concrete Builder class SimpleBuilder : public AbstractBuilder { public: IBuilder& produce_part_1() override { @@ -113,7 +89,7 @@ class SimpleBuilder : public AbstractBuilder { return *this; } - Product* build() override { return product_; } + std::unique_ptr build() override { return std::move(product_); } }; class ComplexBuilder : public AbstractBuilder { @@ -133,23 +109,25 @@ class ComplexBuilder : public AbstractBuilder { return *this; } - Product* build() override { return product_; } + std::unique_ptr build() override { return std::move(product_); } }; void run() { auto client_code = [](IBuilder* const builder) { - const Product* p1 = + // product 1 + auto p1 = (*builder).produce_part_1().produce_part_2().produce_part_3().build(); p1->print(); - const Product* p2 = (*builder).reset().produce_part_1().build(); + + // product 2 + auto p2 = (*builder).reset().produce_part_1().build(); p2->print(); }; { LOG("ConcreteBuilder: Simple"); - IBuilder* builder = new SimpleBuilder(); - client_code(builder); - delete builder; + auto builder = std::make_unique(); + client_code(builder.get()); } { LOG("ConcreteBuilder: Complex"); @@ -161,8 +139,6 @@ void run() { } // namespace builder_pattern } // namespace -#include "ExampleRegistry.h" - class BuilderExample : public IExample { public: std::string group() const override { return "dp/creational"; } diff --git a/src/dp/creational/FactoryMethod.cpp b/src/dp/creational/FactoryMethod.cpp index f099ff1..5c13432 100644 --- a/src/dp/creational/FactoryMethod.cpp +++ b/src/dp/creational/FactoryMethod.cpp @@ -1,35 +1,27 @@ // cppcheck-suppress-file [functionStatic] -// Factory Method is a creational design pattern that provides an interface for -// creating objects in a superclass, but allows subclasses to alter the type of -// objects that will be created. Appicability: -// (*) when you don’t know beforehand the exact types and dependencies of the -// objects your code should work with. -// (**) when you want to provide users of your library or framework with a way -// to extend its internal components. -// (***)when you want to save system resources by reusing existing objects -// instead of rebuilding them each time. - -// UML: docs/uml/patterns_creational_factorymethod.drawio.svg - +// Flow in this file: +// 1. Define a product interface -> IGdbProduct +// 2. Implement concrete products -> Linux / Windows / MacOs Gdb +// 3. Define a creator interface -> IGdbFactory (+ AbstractGdbFactory) +// 4. Implement concrete creators -> Linux / Windows / MacOs factories +// 5. Client picks a factory, then uses it -> never constructs products directly + +#include #include +#include "ExampleRegistry.h" #include "Logger.h" namespace { namespace factory_method { -/** - * The Product interface declares the operations that all concrete products must - * implement. - */ + +/// @class Product Interface class IGdbProduct { public: virtual ~IGdbProduct() = default; virtual void launch() const = 0; }; -/** - * Concrete Products provide various implementations of the Product interface. - */ class LinuxGdbProduct : public IGdbProduct { public: void launch() const override { @@ -49,115 +41,95 @@ class MacOsGdbProduct : public IGdbProduct { void launch() const override { LOG("brew install gdb && gdb --version"); } }; -// =================================================================================== - -/** - * The Creator class declares the factory method that is supposed to return an - * object of a Product class. The Creator's subclasses usually provide the - * implementation of this method. (a.k.a IGdbFactory) - */ +/// @class Creator Interface class IGdbFactory { public: virtual ~IGdbFactory() = default; - virtual IGdbProduct* factory_method() = 0; + virtual std::unique_ptr factory_method() = 0; virtual void launch_gdb() = 0; }; class AbstractGdbFactory : public IGdbFactory { public: - // Call the factory method to create a Product object. + /// @brief call the factory method to create a Product object + /// execute operation void launch_gdb() final { - IGdbProduct* gdb = this->factory_method(); + auto gdb = this->factory_method(); gdb->launch(); - delete gdb; } }; -/** - * Concrete Creators override the factory method in order to change the - * resulting product's type. - */ class WindowsGdbFactory : public AbstractGdbFactory { public: - IGdbProduct* factory_method() override { return new WindowsGdbProduct(); } + std::unique_ptr factory_method() override { + return std::make_unique(); + } }; class LinuxGdbFactory : public AbstractGdbFactory { public: - IGdbProduct* factory_method() override { return new LinuxGdbProduct(); } + std::unique_ptr factory_method() override { + return std::make_unique(); + } }; class MacOsGdbFactory : public AbstractGdbFactory { public: - IGdbProduct* factory_method() override { return new MacOsGdbProduct(); } + std::unique_ptr factory_method() override { + return std::make_unique(); + } }; -// =================================================================================== - -IGdbFactory* create_gdb_factory(const std::string& os) { +/// @brief selector +std::unique_ptr create_gdb_factory(const std::string& os) { if (os == "linux") { - return new LinuxGdbFactory(); + return std::make_unique(); } if (os == "windows") { - return new WindowsGdbFactory(); + return std::make_unique(); } if (os == "macos") { - return new MacOsGdbFactory(); + return std::make_unique(); } LOG("OS not support yet - " + os); return nullptr; } void run() { - /** - * The client code works with an instance of a concrete creator, albeit through - * its base interface. As long as the client keeps working with the creator via - * the base interface, you can pass it any creator's subclass. - */ auto client_code = [](IGdbFactory* gdb) { - if (gdb != nullptr) + if (gdb != nullptr) { gdb->launch_gdb(); + } }; // Create factory base on the os { const std::string os = "linux"; - IGdbFactory* gdb = create_gdb_factory(os); + auto gdb = create_gdb_factory(os); - client_code(gdb); - - delete gdb; + client_code(gdb.get()); } { const std::string os = "windows"; - IGdbFactory* gdb = create_gdb_factory(os); - - client_code(gdb); + auto gdb = create_gdb_factory(os); - delete gdb; + client_code(gdb.get()); } { const std::string os = "macos"; - IGdbFactory* gdb = create_gdb_factory(os); + auto gdb = create_gdb_factory(os); - client_code(gdb); - - delete gdb; + client_code(gdb.get()); } { const std::string os = "unknown"; - IGdbFactory* gdb = create_gdb_factory(os); - - client_code(gdb); - - delete gdb; + auto gdb = create_gdb_factory(os); + client_code(gdb.get()); } } } // namespace factory_method } // namespace -#include "ExampleRegistry.h" - class FactoryMethodExample : public IExample { public: std::string group() const override { return "dp/creational"; } diff --git a/src/dp/creational/Prototype.cpp b/src/dp/creational/Prototype.cpp index fc74230..3816684 100644 --- a/src/dp/creational/Prototype.cpp +++ b/src/dp/creational/Prototype.cpp @@ -1,133 +1,131 @@ // cppcheck-suppress-file [functionStatic] -// Prototype is a creational design pattern that lets you "copy existing objects" without making your code "dependent on their classes". -// Appicability: -// (*) when your code shouldn’t depend on the concrete classes of objects that you need to copy. -// (**) when you want to reduce the number of subclasses that only differ in the way they initialize their respective objects. - -// UML: docs/uml/patterns_behavioral_prototype.drawio.svg - -#include +// Prototype = create new objects by cloning existing ones. +// +// Flow in this file: +// 1. Define a cloneable interface -> IExtensionPrototype +// 2. Implement concrete prototypes -> Logger / Analytics +// 3. (Optional) Store named presets -> ExtensionPrototypeRegistry +// 4. Client asks registry to clone by id -> never mentions concrete types + +#include +#include +#include #include #include + +#include "ExampleRegistry.h" +#include "Logger.h" + namespace { namespace prototy { -/* - * Prototype interface declares the cloning methods. - * In most cases, it’s a single `clone` method. - */ +constexpr std::string_view kLoggerExtId = "logger"; +constexpr std::string_view kAnalyzeId = "analyze"; + +/// @class Prototype interface: "I know how to copy myself." +/// @brief client code only depends on this class class IExtensionPrototype { public: virtual ~IExtensionPrototype() = default; - virtual IExtensionPrototype* clone() = 0; + + /// return a new object + virtual std::unique_ptr clone() const = 0; + virtual void execute() const = 0; }; -/* - * Concrete Prototype implement an operation for cloning itself - * In addition to copying the original object’s data to the clone, - * this method may also handle some edge cases of the cloning process related to cloning linked objects, - * untangling recursive dependencies, etc. - */ +/// @class Concrete Prototype +/// @brief each class copies itself class LoggerExtension : public IExtensionPrototype { - private: - std::string logLevel_; - public: explicit LoggerExtension(std::string level = "DEBUG") - : logLevel_{std::move(level)} {} - - IExtensionPrototype* clone() override { return new LoggerExtension(*this); } + : log_level_{std::move(level)} {} - void execute() const override { - std::cout << "[Logger] log level: " << logLevel_ << "\n"; + std::unique_ptr clone() const override { + return std::make_unique(*this); } -}; -class AnalyticsExtension : public IExtensionPrototype { + void execute() const override { LOG("log level: " + log_level_); } + private: - int sRate_; + std::string log_level_; +}; +class AnalyticsExtension : public IExtensionPrototype { public: - explicit AnalyticsExtension(int level = 1) : sRate_{level} {} + explicit AnalyticsExtension(int sampling_rate = 1) + : sampling_rate_{sampling_rate} {} - IExtensionPrototype* clone() override { - return new AnalyticsExtension(*this); + std::unique_ptr clone() const override { + return std::make_unique(*this); } - void execute() const override { - std::cout << "[Analytics] sampling rate: " << sRate_ << "\n"; - } -}; + void execute() const override { LOG_S("sampling rate: " << sampling_rate_); } -/** - * Prototype Registry provides an easy way to access frequently-used prototypes. - * It stores a set of pre-built objects that are ready to be copied. - * The simplest prototype registry is a name ^ prototype hash map. - * However, if you need better search criteria than a simple name, you can build a much more robust version of the registry. - */ -class ExtensionPrototypeRegistry { private: - std::unordered_map prototypes_; + int sampling_rate_; +}; +/// @class Prototype Registry +/// @brief a map of named presets ready to be cloned. +class ExtensionPrototypeRegistry { public: - ~ExtensionPrototypeRegistry() { - for (auto it = prototypes_.begin(); it != prototypes_.end();) { - delete it->second; // free the pointer - it = prototypes_.erase(it); // erase and move to next - } - } - void registerExtension(const std::string& id, IExtensionPrototype* proto) { - prototypes_[id] = proto; + void register_extension(std::string_view id, + std::unique_ptr proto) { + LOG(id); + prototypes_[std::string(id)] = std::move(proto); } - IExtensionPrototype* create(const std::string& id) const { - auto it = prototypes_.find(id); - if (it != prototypes_.end()) { - return it->second->clone(); + /// clone the preset for `id`. Returns nullptr if the id is unknown. + std::unique_ptr create(std::string_view id) const { + auto it = prototypes_.find(std::string(id)); + if (it == prototypes_.end()) { + return nullptr; } - return nullptr; + return it->second->clone(); // copy the template, leave the original intact } -}; - -/* - * Client creates a new object by asking a prototype to clone itself - */ -namespace client { -void clientCode(const ExtensionPrototypeRegistry* const registry) { - IExtensionPrototype* logger_etx = registry->create("logger"); - logger_etx->execute(); - IExtensionPrototype* analyx_etx = registry->create("analyze"); - analyx_etx->execute(); - - delete logger_etx; - delete analyx_etx; -} -} // namespace client + private: + std::unordered_map> + prototypes_; +}; void run() { - auto* registry = new ExtensionPrototypeRegistry(); - registry->registerExtension("logger", new LoggerExtension("DEBUG")); - registry->registerExtension("analyze", new AnalyticsExtension(1200)); - client::clientCode(registry); + // build presets once, register by name + ExtensionPrototypeRegistry registry; + registry.register_extension(kLoggerExtId, + std::make_unique("DEBUG")); + registry.register_extension(kAnalyzeId, + std::make_unique(1200)); + + // create-by-clone + auto client_code = [](const ExtensionPrototypeRegistry& reg) { + auto logger_ext = reg.create(kLoggerExtId); + auto analytics_ext = reg.create(kAnalyzeId); + + if (logger_ext) { + logger_ext->execute(); + } + if (analytics_ext) { + analytics_ext->execute(); + } + }; - delete registry; + client_code(registry); } + } // namespace prototy } // namespace -#include "ExampleRegistry.h" - class PrototypeExample : public IExample { public: std::string group() const override { return "dp/creational"; } std::string name() const override { return "Prototype"; } std::string description() const override { - return "Prototype Pattern Example"; + return "Clone configured objects via a prototype interface + registry"; } void execute() override { prototy::run(); } }; -REGISTER_EXAMPLE(PrototypeExample); \ No newline at end of file +REGISTER_EXAMPLE(PrototypeExample); diff --git a/src/dp/creational/Singleton.cpp b/src/dp/creational/Singleton.cpp index 1165215..b3bc6ac 100644 --- a/src/dp/creational/Singleton.cpp +++ b/src/dp/creational/Singleton.cpp @@ -1,69 +1,60 @@ // cppcheck-suppress-file [functionStatic] -// Singleton is a creational design pattern that lets you ensure that a class -// has only one instance, while providing a global access point to this -// instance. Appicability: -// (*) when a class in your program should have just a single instance -// available to all clients; for example, a single database object shared by -// different parts of the program. -// (**) when you need stricter control over global variables. - -// UML: docs/uml/patterns_creational_singleton.drawio.svg +// Singleton — ensure a class has only one instance, accessed globally. +// +// Flow in this file: +// 1. Hide the constructor -> private SingletonConfig() +// 2. Ban copy / assign -> deleted special members +// 3. Expose a single access point -> get_instance() (Meyers' singleton) +// 4. Client always uses get_instance() -> same object everywhere #include +#include "ExampleRegistry.h" #include "Logger.h" namespace { namespace singleton_pattern { -/** - * The Singleton class defines the `GetInstance` method that serves as an - * alternative to constructor and lets clients access the same instance of this - * class over and over. - */ -class Singleton { - private: - static inline Singleton* instance_ = nullptr; - static inline int id_ = 0; - int dummy_{}; - /** - * The Singleton's constructor should always be private to prevent direct - * construction calls with the `new` operator. - */ - Singleton() = default; +/// @class Singleton class +/// @brief defines the `GetInstance` method that serves as an alternative to constructor +class SingletonConfig { public: // 1. Should not be cloneable. - Singleton(const Singleton& other) = delete; + SingletonConfig(const SingletonConfig& other) = delete; // 2. Should not be assignable - Singleton& operator=(const Singleton& other) = delete; - - static Singleton* get_instance() { - if (instance_ == nullptr) { - instance_ = new Singleton(); - id_++; - } - LOG("id: " + std::to_string(id_)); - return instance_; + SingletonConfig& operator=(const SingletonConfig& other) = delete; + + static SingletonConfig& get_instance() { + static SingletonConfig instance; + return instance; } - void operation() { - LOG("id: " + std::to_string(id_)); - dummy_++; + void init(const std::string& input) { + LOG(input); + value_ = input; } + + const std::string& get_value() const { + LOG(""); + return value_; + }; + + private: + /// @brief Default constructor should always be private + SingletonConfig() = default; + std::string value_; }; void run() { - auto client_code = [](Singleton* s) { - s->operation(); + auto client_code = []() { + LOG(SingletonConfig::get_instance().get_value()); }; - Singleton* s1 = Singleton::get_instance(); - client_code(s1); - - Singleton* s2 = Singleton::get_instance(); - client_code(s2); + SingletonConfig& s1 = SingletonConfig::get_instance(); + s1.init("0x001"); + client_code(); // Singleton* s3 = new Singleton(); // ERROR } @@ -71,8 +62,6 @@ void run() { } // namespace singleton_pattern } // namespace -#include "ExampleRegistry.h" - class SingletonExample : public IExample { public: std::string group() const override { return "dp/creational"; } diff --git a/src/dp/structural/Adapter.cpp b/src/dp/structural/Adapter.cpp index d4619f7..6ea3755 100644 --- a/src/dp/structural/Adapter.cpp +++ b/src/dp/structural/Adapter.cpp @@ -7,7 +7,7 @@ // UML: docs/uml/patterns_structural_adapter.drawio.svg -#include +#include "Logger.h" namespace adapter_pattern { /** @@ -17,9 +17,9 @@ namespace adapter_pattern { */ class Adaptee { public: - std::string specificRequest() { + void specific_request() { dummy_++; - return "Adaptee: The adaptee's behavior."; + LOG("executed"); } private: @@ -31,7 +31,7 @@ class Adaptee { */ class Target { public: - virtual std::string request() { return " Target: The target's behavior."; } + virtual void request() { LOG("executed"); } }; // ============================================================================================================ @@ -49,40 +49,31 @@ class Adapter : public Target { Adaptee* adaptee_; public: - explicit Adapter(Adaptee* adaptee) : adaptee_{adaptee} { - std::cout << "Adapter constructer.\n"; - } + explicit Adapter(Adaptee* adaptee) : adaptee_{adaptee} { LOG("constructed"); } - std::string request() override { return adaptee_->specificRequest(); } + void request() override { return adaptee_->specific_request(); } }; -/** - * The client code supports all classes that follow the Target interface. - */ +void run() { + LOG("Adapter Example"); -namespace client { -void clientCode(Target* const target) { - if (target != nullptr) - std::cout << "Output: " << target->request() << "\n"; -} -} // namespace client + // The client code supports all classes that follow the Target interface. + auto client_code = [](Target* target) { + LOG("executed"); + target->request(); + }; -void run() { - std::cout << "Client: Can work just fine with the Target objects:\n"; + LOG("Client: Can work just fine with the Target objects:"); Target target = Target(); - std::cout << "Target: " << target.request() << "\n"; - client::clientCode(&target); - std::cout << "\n\n"; + client_code(&target); - std::cout << "Client: Cannot work with the Adaptee objects:\n"; + LOG("Client: Cannot work with the Adaptee objects:"); Adaptee adaptee = Adaptee(); - std::cout << "Adaptee: " << adaptee.specificRequest() << "\n"; // Client::clientCode(&adaptee); // error - std::cout << "Client: But can work with it via the Adapter:\n"; + LOG("Client: But can work with it via the Adapter:"); auto adapter = Adapter(&adaptee); - client::clientCode(&adapter); - std::cout << "\n"; + client_code(&adapter); } } // namespace adapter_pattern @@ -90,8 +81,8 @@ namespace case_study { // Target interface expected by the existing system class PaymentSystem { public: - virtual void payWithCard(const std::string& cardNumber) { - std::cout << "Payment using card: " << cardNumber << "\n"; + virtual void pay_with_card(const std::string& card_number) { + LOG_S("Payment using card: " << card_number); } virtual ~PaymentSystem() = default; @@ -100,8 +91,8 @@ class PaymentSystem { // Adaptee: a new payment API with an incompatible interface class PayPalAPI { public: - void sendPayment(const std::string& email) { - std::cout << "Payment sent via PayPal to " << email << "\n"; + void send_payment(const std::string& email) { + LOG_S("Payment sent via PayPal to " << email); dummy_++; } @@ -115,14 +106,15 @@ class PayPalAdapter : public PaymentSystem { PayPalAPI paypal_; public: - void payWithCard(const std::string& cardNumber) override { + void pay_with_card(const std::string& cardNumber) override { // Treat the cardNumber parameter as a PayPal email - paypal_.sendPayment(cardNumber); + paypal_.send_payment(cardNumber); } }; // Client code: uses the old interface without modification void run() { + LOG("Case Study Example"); std::string method; std::string input; method = std::string("card") + std::string(""); @@ -130,18 +122,18 @@ void run() { // method = std::string("paypal") + std::string("");input = // "user@example.com"; - std::cout << "Choose payment method (card/paypal): " << method << "\n"; + LOG_S("Choose payment method (card/paypal): " << method); PaymentSystem* payment_system = nullptr; if (method == "card") { payment_system = new PaymentSystem(); - payment_system->payWithCard(input); + payment_system->pay_with_card(input); } else if (method == "paypal") { payment_system = new PayPalAdapter(); - payment_system->payWithCard(input); + payment_system->pay_with_card(input); } else { - std::cout << "Unsupported payment method!\n"; + LOG("Unsupported payment method!"); } delete payment_system; diff --git a/src/dp/structural/Bridge.cpp b/src/dp/structural/Bridge.cpp index 7998e47..bceb942 100644 --- a/src/dp/structural/Bridge.cpp +++ b/src/dp/structural/Bridge.cpp @@ -11,70 +11,77 @@ // UML: docs/uml/patterns_structural_bridge.drawio.svg -#include - +#include +#include #include "ExampleRegistry.h" +#include "Logger.h" namespace { namespace problem { class Widget { public: virtual ~Widget() = default; - virtual std::string clickOn() const = 0; + virtual void click_on() const = 0; }; /* Concrete variations for Button */ class Button : public Widget { public: - std::string clickOn() const override { return "Click on: Button\n"; } + void click_on() const override { LOG("executed"); } }; class ButtonWindows : public Button { public: - std::string clickOn() const override { return "[Linux]" + Button::clickOn(); } + void click_on() const override { + LOG("executed"); + Button::click_on(); + } }; class ButtonLinux : public Button { public: - std::string clickOn() const override { - return "[Windows]" + Button::clickOn(); + void click_on() const override { + LOG("executed"); + Button::click_on(); } }; /* Concrete variations for Label */ class Label : public Widget { public: - std::string clickOn() const override { return "Click on: Label\n"; } + void click_on() const override { LOG("executed"); } }; class LabelWindows : public Label { public: - std::string clickOn() const override { - return "[Windows]" + Label::clickOn(); + void click_on() const override { + LOG("executed"); + Label::click_on(); } }; class LabelLinux : public Label { public: - std::string clickOn() const override { return "[Linux]" + Label::clickOn(); } + void click_on() const override { + LOG("executed"); + Label::click_on(); + } }; -/* Concrete variations for others widgets like Text,CCombo or new platform +void run() { + LOG("Problem"); + /* Concrete variations for others widgets like Text,CCombo or new platform * macOS etc*/ -// [Problem 1] We have to write the Text/TextLinux ... + // [Problem 1] We have to write the Text/TextLinux ... + auto client_code = [](const Widget* widget) { + if (widget != nullptr) + widget->click_on(); + }; -namespace client { -void clientCode(const Widget* widget) { - if (widget != nullptr) - std::cout << widget->clickOn(); -} -} // namespace client - -void run() { // [Problem 2] : Use the Bridge if you need to be able to switch // implementations at runtime. how to exmaple for this still don't know Widget* button = new ButtonWindows(); - client::clientCode(button); + client_code(button); delete button; } } // namespace problem @@ -89,18 +96,18 @@ namespace bridge_pattern { */ class OsImplemetation { public: - virtual std::string clickOnImplement() const = 0; + virtual void click_on_ipl() const = 0; virtual ~OsImplemetation() = default; }; class WindowsImplemetation : public OsImplemetation { public: - std::string clickOnImplement() const override { return "[Windows]"; } + void click_on_ipl() const override { LOG("[Windows]"); } }; class LinuxImplemetation : public OsImplemetation { public: - std::string clickOnImplement() const override { return "[Linux]"; } + void click_on_ipl() const override { LOG("[Linux]"); } }; /** @@ -110,14 +117,14 @@ class LinuxImplemetation : public OsImplemetation { */ class WidgetAbstraction { protected: - OsImplemetation* implementation_; + std::shared_ptr implementation_; public: - explicit WidgetAbstraction(OsImplemetation* implemetation) - : implementation_{implemetation} {} + explicit WidgetAbstraction(std::shared_ptr implemetation) + : implementation_{std::move(implemetation)} {} virtual ~WidgetAbstraction() = default; - virtual std::string clickOn() const = 0; + virtual void click_on() const = 0; }; /** @@ -125,40 +132,40 @@ class WidgetAbstraction { */ class ButtonAbstraction : public WidgetAbstraction { public: - explicit ButtonAbstraction(OsImplemetation* implemetation) - : WidgetAbstraction{implemetation} {} - std::string clickOn() const override { - return this->implementation_->clickOnImplement() + "Click on: Button\n"; + explicit ButtonAbstraction(std::shared_ptr implemetation) + : WidgetAbstraction{std::move(implemetation)} {} + void click_on() const override { + LOG("executed"); + this->implementation_->click_on_ipl(); } }; class LabelAbstraction : public WidgetAbstraction { public: - explicit LabelAbstraction(OsImplemetation* implemetation) - : WidgetAbstraction{implemetation} {} - std::string clickOn() const override { - return this->implementation_->clickOnImplement() + "Click on: Label\n"; + explicit LabelAbstraction(std::shared_ptr implemetation) + : WidgetAbstraction{std::move(implemetation)} {} + void click_on() const override { + LOG("executed"); + this->implementation_->click_on_ipl(); } }; -namespace client { -void clientCode(const WidgetAbstraction* widget) { - if (widget != nullptr) - std::cout << widget->clickOn(); -} -} // namespace client - void run() { - // TODO(phong-nguyen): check memory leak here - OsImplemetation* os = new WindowsImplemetation(); + LOG("Bridge Example"); + auto client_code = [](const WidgetAbstraction* widget) { + if (widget != nullptr) + widget->click_on(); + }; + + std::shared_ptr os = + std::make_shared(); WidgetAbstraction* widget = new ButtonAbstraction(os); - client::clientCode(widget); + client_code(widget); - os = new LinuxImplemetation(); + os = std::make_shared(); widget = new LabelAbstraction(os); - client::clientCode(widget); + client_code(widget); - delete os; delete widget; } } // namespace bridge_pattern diff --git a/src/embedded/README.md b/src/embedded/README.md new file mode 100644 index 0000000..917b832 --- /dev/null +++ b/src/embedded/README.md @@ -0,0 +1,261 @@ +# Embedded Bare-Metal: Build, Link, Boot, and Debug +#### Env +```bash +$ sudo apt install gcc-arm-none-eabi +$ apt-get install qemu-system +``` + +## 1. Building Process +```bash +#Sources files #Toolchain #Object files ++-----------+ +------------------+ +-----------+ +| startup.c | ----> | | ----> | startup.o | ++-----------+ | arm-none-eabi-gcc| +-----------+ + | | ++-----------+ ----> | | ----> +-----------+ +| main.c | +------------------+ | main.o | ++-----------+ +-----------+ + | +#Linker script | ++------------------+ | +| linker_script.ld | ---------------------------------+ ++------------------+ | + v + +----------------+ + |arm-none-eabi-ld| #Linker + +----------------+ + | + v + +----------------+ + | blink.elf | #Executable + +----------------+ +``` +### 1.1. Compiler +A compiler is a software program that translates source code written in a high-level programming language into a lower-level form, such as assembly code, object code, or machine code, while preserving the program's functionality. +```bash +$ gcc # native +$ g++ +$ clang +$ arm-none-eabi-gcc # cross +$ aarch64-none-elf-gcc +``` + +### 1.2. Cross Compiler +A cross compiler is a compiler that runs on one platform (host) but generates executable code for a different platform (target). +An ARM cross compiler can run on a Linux PC and generate binaries that execute on an ARM-based embedded system. +```bash +$ arm-none-eabi-gcc +$ aarch64-none-elf-gcc +``` + +### 1.3. Toolchain +A toolchain is a collection of software development tools used to build, debug, and analyze software. +A typical embedded ARM toolchain includes: +```bash +$ arm-none-eabi-gcc # Compiler +$ arm-none-eabi-as # Assembler +$ arm-none-eabi-ld # Linker +$ arm-none-eabi-objcopy # ELF to BIN converter +$ arm-none-eabi-gdb # Debugger +``` + +### 1.4. Linker Script +**Linker Script** is a configuration file that tells the linker how to place the program into the mcu memory. +- Define the memory layout: Flash, RAM, Stack, Heap +- Specify available memory regions +- Place the code/data in the correct memory location +- Set the program entry point +- Optimize memory usage + +Reference: https://sourceware.org/binutils/docs/ld/Scripts.html + +#### Concept +The linker combines multiple object files into a single executable file. +Object files are stored in a special format called an `object file format` (such as ELF). Each object file contains several `sections`, and each section has a name and a size (e.g., .text, .data, .bss). +For each loadable output section, the linker defines two addresses: +- VMA (Virtual Memory Address): The address where the section will reside when the program is running. +- LMA (Load Memory Address): The address from which the section is loaded at startup. + +#### Format +- Linker scripts are text files. e.g. `stm32_flash.ld` +- Whitespace is generally ignored. +- Include comments in linker scripts just as in C + +#### Example +The simplest possible linker script has just one command: `SECTION` to describe the memory layout of the output file. + +```ld +/*stm32_flash.ld*/ +SECTIONS +{ + . = 0x10000; # the code should be loaded at address 0x10000 + .text : { *(.text)} + . = 0x8000000; # that the data should start at address 0x8000000 + .data : { *{.data}} + .bss : { *(.bss) } +} +``` +- Symbols: + - `.`: location counter + - `.text`, `.data`, and `.bss` are the sections of the output file + - `*(.text)` means all `.text` input sections in all input files. + +### 1.5. Startup Code +A microcontroller does not start executing from `main()` after power-up or reset. Instead, it begins execution at the reset vector, which points to the startup code (typically `Reset_Handler`). +- Setting initial data values in SRAM, for global variables for example. +- Zero initialization of data memory for variables that are uninitialized at load time. +- Initializing the data variables controlling heap memory, for malloc() for example. +- Performing any required system initialization. +- Calling main(). + +--- +### 2. Flashing Process +```bash + + +----------------+ + | blink.elf | #Executable + +----------------+ + | + v + +----------------+ + | OpenOCD | #Tool to download/Debug + +----------------+ + | + v + +----------------+ + | ST-LINK | #Debug probe + +----------------+ + | + v + +--------------------------------------------------+ + | STM32 FLASH | + |--------------------------------------------------| + | .isr_vector | + | .text | + | .rodata | + | Initial .data image | + +--------------------------------------------------+ + | + | RESET + v + +---------------------+ + | Reset_Handler() | + +---------------------+ + | + +----------------------+ + | Copy .data | + | Flash --> SRAM | + +----------------------+ + v + +--------------------------------------------------+ + | STM32 SRAM | + |--------------------------------------------------| + | .data | + | .bss <-- zero-filled | + | | + | Heap (optional) | + | | + | Stack (grows downward) | + +--------------------------------------------------+ + | + v + +---------+ + | main() | + +---------+ + | + v + #Application Runs# +``` + +### 2.1. Build executable +Compile the source files and link them into the `firmware.elf` +```bash +$ arm-none-eabi-gcc \ + -mcpu=cortex-m3 \ + -mthumb \ + -nostdlib \ + -ffreestanding \ + startup.c main.c \ + -T stm32_flash.ld \ + -Wl,-Map=firmware.map \ + -o build/firmware.elf +``` +- **Options:** + - `-mcpu=cortex-m3` : Generate code for the ARM Cortex-M3 processor. + - `-mthumb `: Use the Thumb instruction set. + - `-nostdlib` : Do not link the standard C runtime libraries. + - `-ffreestanding` : Compile for a freestanding environment (bare-metal system). + - `-T stm32_flash.ld` : Use the specified linker script. + - `-Wl,-Map=firmware.map` : Generate a linker map file. + - `-o build/firmware.elf` : Output executable file. +- **ELF file** does include symbol tables, debug information, or other metadata. +### 2.2. Generate a Binary Image +Convert the ELF executable into a raw binary image that contains only the program data that will be stored in Flash memory. +```bash +$ arm-none-eabi-objcopy \ + -O binary \ + build/firmware.elf \ + build/firmware.bin +``` + +### 2.3. Boot and Debug with QEMU +QEMU can emulate the `STM32VLDISCOVERY` board and load firmware +#### Start QEMU +```bash +$ qemu-system-arm \ + -M stm32vldiscovery \ + -kernel firmware.bin +``` + +### Connect with GDB +- Launch QEMU and wait for a debugger connection: + ```bash + $ qemu-system-arm \ + -M stm32vldiscovery \ + -kernel firmware.elf \ + -nographic \ + -S \ + -gdb tcp::1234 + ``` + - **Options:** + `-M stm32vldiscovery` : Emulate the STM32VLDISCOVERY board. + `-kernel firmware.elf` : Load the firmware image. + `-nographic` : Disable graphical output and use the terminal. + `-S` : Pause the CPU at reset. + `-gdb tcp::1234` : Start a GDB server on port 1234. + +- Start GDB using the ELF file so that symbol and debug information are available: + ```bash + $ arm-none-eabi-gdb firmware.elf + ``` +- Connect to the QEMU GDB server: + ```bash + target remote :1234 + ``` + +- **Useful commands:** +```bash +monitor reset # Reset the emulated target +load # Load firmware into target memory +continue # Start execution + +p counter # Inspect variables and symbols + +break main # set breakpoints +continue +``` + +### Quit QEMU +When running QEMU with `-nographic`, use: +```text +Ctrl + A, then X + +to exit the emulator. + +Other useful shortcuts: +Ctrl + A, then C Open QEMU monitor +Ctrl + A, then H Show help +Ctrl + A, then X Exit QEMU +``` + +--- \ No newline at end of file diff --git a/src/embedded/main.c b/src/embedded/main.c new file mode 100644 index 0000000..e353ce8 --- /dev/null +++ b/src/embedded/main.c @@ -0,0 +1,75 @@ +#include + +#define SYST_CSR (*(volatile uint32_t*)0xE000E010) +#define SYST_RVR (*(volatile uint32_t*)0xE000E014) +#define SYST_CVR (*(volatile uint32_t*)0xE000E018) + +#define RCC_APB2ENR (*(volatile uint32_t*)0x40021018) + +#define USART1_SR (*(volatile uint32_t*)0x40013800) +#define USART1_DR (*(volatile uint32_t*)0x40013804) +#define USART1_BRR (*(volatile uint32_t*)0x40013808) +#define USART1_CR1 (*(volatile uint32_t*)0x4001380C) + +/// @brief Generate a blocking delay using the Cortex-M SysTick timer. +/// @param ms Delay duration in milliseconds. +static void delay_ms(uint32_t ms) { + while (ms--) { + SYST_RVR = 8000 - 1; // 8 MHz -> 1 ms + SYST_CVR = 0; + SYST_CSR = 5; // ENABLE + CPU clock + + while (!(SYST_CSR & (1 << 16))) {} + + SYST_CSR = 0; + } +} + +/// @brief Send a null-terminated string to the host using semihosting. +/// @param s String to transmit. +static void semihost_write0(const char* s) { + register int op asm("r0") = 0x04; + register const char* msg asm("r1") = s; + + asm volatile("bkpt 0xAB" : : "r"(op), "r"(msg) : "memory"); +} + +static void uart_init(void) { + // Enable USART1 clock + RCC_APB2ENR |= (1 << 14); + + // 8 MHz PCLK -> 115200 baud + USART1_BRR = 0x45; + + // UE + TE + USART1_CR1 = (1 << 13) | (1 << 3); +} + +/// @brief +static void uart_putc(char c) { + while (!(USART1_SR & (1 << 7))) {} + + USART1_DR = c; +} + +/// @brief Transmit a null-terminated string over USART1. +/// @param s String to transmit. +static void uart_puts(const char* s) { + while (*s) { + if (*s == '\n') + uart_putc('\r'); + + uart_putc(*s++); + } +} + +int main(void) { + uart_init(); + + while (1) { + uart_puts("Hello STM32-QEMU UART1\n"); + delay_ms(1000); + semihost_write0("Hello STM32-QEMU semihosting\n"); + delay_ms(1000); + } +} \ No newline at end of file diff --git a/src/embedded/run.sh b/src/embedded/run.sh new file mode 100755 index 0000000..afc89d2 --- /dev/null +++ b/src/embedded/run.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +set -e + +BUILD_DIR=build +TARGET=firmware + +mkdir -p ${BUILD_DIR} + +echo "[1/3] Building ELF..." + +arm-none-eabi-gcc \ + -mcpu=cortex-m3 \ + -mthumb \ + -nostdlib \ + -ffreestanding \ + startup.c main.c \ + -T stm32_flash.ld \ + -Wl,-Map=${BUILD_DIR}/${TARGET}.map \ + -o ${BUILD_DIR}/${TARGET}.elf + +echo "[2/3] Generating BIN..." + +arm-none-eabi-objcopy \ + -O binary \ + ${BUILD_DIR}/${TARGET}.elf \ + ${BUILD_DIR}/${TARGET}.bin + +QEMU_ARGS=" + -M stm32vldiscovery + -kernel ${BUILD_DIR}/${TARGET}.elf + -semihosting-config enable=on,target=native +" + +case "$1" in + gui) + echo "[3/3] Starting QEMU (GUI)..." + + qemu-system-arm ${QEMU_ARGS} + ;; + + debug) + echo "[3/3] Starting QEMU (Debug Mode)..." + echo "Connect with:" + echo " arm-none-eabi-gdb ${BUILD_DIR}/${TARGET}.elf" + echo " target remote :1234" + + qemu-system-arm \ + ${QEMU_ARGS} \ + -S \ + -gdb tcp::1234 + ;; + + *) + echo "[3/3] Starting QEMU (Terminal)..." + + qemu-system-arm \ + ${QEMU_ARGS} \ + -nographic + ;; +esac diff --git a/src/embedded/startup.c b/src/embedded/startup.c new file mode 100644 index 0000000..9fbfb2f --- /dev/null +++ b/src/embedded/startup.c @@ -0,0 +1,78 @@ +#include + +/// @brief Symbols defined by the linker script. +extern uint32_t _sidata; ///< Start address of initialized data in Flash (LMA) +extern uint32_t _sdata; ///< Start address of .data section in RAM (VMA) +extern uint32_t _edata; ///< End address of .data section in RAM +extern uint32_t _sbss; ///< Start address of .bss section +extern uint32_t _ebss; ///< End address of .bss section +extern uint32_t _estack; ///< Initial stack pointer (top of RAM) + +extern int main(void); + +void Reset_Handler(void); +void Default_Handler(void); + +/// @brief Weak aliases allow users to override handlers bydefining their own implementation with the same name. +void NMI_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void HardFault_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void MemManage_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void BusFault_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void UsageFault_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void SVC_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void DebugMon_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void PendSV_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void SysTick_Handler(void) __attribute__((weak, alias("Default_Handler"))); + +/// @brief Interrupt Vector Table +__attribute__((section(".isr_vector"))) const void* vector_table[] = { + &_estack, // Initial SP + Reset_Handler, + + NMI_Handler, + HardFault_Handler, + MemManage_Handler, + BusFault_Handler, + UsageFault_Handler, + + 0, + 0, + 0, + 0, + + SVC_Handler, + DebugMon_Handler, + 0, + PendSV_Handler, + SysTick_Handler, +}; + +/// @brief Executed immediately after reset. +/// Responsible for initializing the C runtime environment +void Reset_Handler(void) { + // Copy initialized data (.data) * from Flash to RAM. + uint32_t* src = &_sidata; + uint32_t* dst = &_sdata; + + while (dst < &_edata) { + *dst++ = *src++; + } + + dst = &_sbss; + + // Clear uninitialized data (.bss) + while (dst < &_ebss) { + *dst++ = 0; + } + + // Jump to the application entry point + main(); + + // main() should never return + while (1) {} +} + +/// @brief Default handler for unimplemented exceptions. +void Default_Handler(void) { + while (1) {} +} \ No newline at end of file diff --git a/src/embedded/stm32_flash.ld b/src/embedded/stm32_flash.ld new file mode 100644 index 0000000..3c7c2af --- /dev/null +++ b/src/embedded/stm32_flash.ld @@ -0,0 +1,70 @@ +/* Reference: +* https://kleinembedded.com/stm32-without-cubeide-part-1-the-bare-necessities +FLASH (0x08000000) ++------------------+ +| .isr_vector | ++------------------+ +| .text | +| .rodata | ++------------------+ +| Initial .data | <-- _sidata (LMA) ++------------------+ + +RAM (0x20000000) ++------------------+ +| .data | <-- _sdata ++------------------+ +| .bss | <-- _sbss ++------------------+ +| Heap (optional) | <-- end +| | ++------------------+ +| Stack | ++------------------+ +| _estack | ++------------------+ +*/ + +ENTRY(Reset_Handler) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 8K +} + +_estack = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + .isr_vector : + { + KEEP(*(.isr_vector)) + } > FLASH + + .text : + { + *(.text*) + *(.rodata*) + } > FLASH + + _sidata = LOADADDR(.data); + + .data : + { + _sdata = .; + *(.data*) + _edata = .; + } > RAM AT > FLASH + + .bss : + { + _sbss = .; + *(.bss*) + *(COMMON) + _ebss = .; + } > RAM + + . = ALIGN(8); + end = .; +} \ No newline at end of file