From cdac7bae13716fa15d623f7c34ed9aa5cd70cdf7 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Wed, 4 Feb 2026 13:44:00 +0700 Subject: [PATCH 01/10] Update README --- tests/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/README.md b/tests/README.md index 429cb2d..f332245 100644 --- a/tests/README.md +++ b/tests/README.md @@ -16,6 +16,12 @@ - write a default destructor or `TearDown()` function to release resources. - use `TEST_F(TestFixtureClassName, TestName)` instead of `TEST()` +>All documentation is covered in the official github repo. The primer documentation also covers a lot of information regarding the test macros. You could use the following summary and the examples linked to choose what you want to use. (https://stackoverflow.com/questions/58600728/what-is-the-difference-between-test-test-f-and-test-p) + +- **TEST()** is useful when you want to write unit tests for static or global functions or simple classes. +- **TEST_F()** is useful when you need access to objects and subroutines in the unit test. +- **TEST_P()** is useful when you want to write tests with a parameter. Instead of writing multiple tests with different values of the parameter, you can write one test using TEST_P() which uses GetParam() and can be instantiated using INSTANTIATE_TEST_SUITE_P(). + - e.g. ```cpp #include "this/package/foo.h" From 3e84e84f5ca9466b279deac6f3b3cc5be315bd4b Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Wed, 4 Feb 2026 14:14:15 +0700 Subject: [PATCH 02/10] add LC18 --- CMakeLists.txt | 2 + src/leetcode/arrays/4sum/Solution.cpp | 66 +++++++++++++++++++++++++++ src/leetcode/arrays/4sum/Solution.h | 41 +++++++++++++++++ tests/four_sum_ut.cpp | 29 ++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 src/leetcode/arrays/4sum/Solution.cpp create mode 100644 src/leetcode/arrays/4sum/Solution.h create mode 100644 tests/four_sum_ut.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d4b9090..334f153 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,6 +137,7 @@ set(APP_SOURCES "src/leetcode/arrays/container_with_most_water/ContainerWithMostWater.cpp" "src/leetcode/arrays/longest_common_prefix/Solution.cpp" "src/leetcode/arrays/3sum/Solution.cpp" + "src/leetcode/arrays/4sum/Solution.cpp" ) # Test files @@ -147,6 +148,7 @@ set(APP_TESTS "tests/container_with_most_water_ut.cpp" "tests/longest_common_prefix_ut.cpp" "tests/three_sum_ut.cpp" + "tests/four_sum_ut.cpp" ) # ---------------------------------------------------------------------------------------- diff --git a/src/leetcode/arrays/4sum/Solution.cpp b/src/leetcode/arrays/4sum/Solution.cpp new file mode 100644 index 0000000..fd08fdc --- /dev/null +++ b/src/leetcode/arrays/4sum/Solution.cpp @@ -0,0 +1,66 @@ +#include "Solution.h" +#include +#include + +/** + * @brief Complexity + * - Time: O(n3) + * - Space: O(logn) // belong to sort + */ +std::vector> Solution::fourSum(std::vector& nums, + int target) { + size_t size = nums.size(); + if (size < 4) { + return {}; + } + + std::sort(nums.begin(), nums.end()); + if (nums[0] > target) { + return {}; + } + + std::vector> result; + for (size_t fI = 0; fI < size - 3; ++fI) { + if (fI > 0 && nums[fI] == nums[fI - 1]) { + ++fI; + continue; + } + + for (size_t sI = fI + 1; sI < size - 2; ++sI) { + if (sI > 1 && nums[sI] == nums[sI - 1]) { + continue; + } + + size_t tI = sI + 1; + size_t frI = size - 1; + while (tI < frI) { + int f = nums[fI]; + int s = nums[sI]; + int t = nums[tI]; + int fr = nums[frI]; + + int diff = f + s + t + fr - target; + if (diff == 0) { + result.push_back({f, s, t, fr}); + // Next + ++tI; + --frI; + + while (tI < frI && nums[tI] == nums[tI - 1]) { + ++tI; + } + + while (tI < frI && nums[frI] == nums[frI + 1]) { + --frI; + } + } else if (diff < 0) { + ++tI; + } else { + --frI; + } + } + } + } + + return result; +} diff --git a/src/leetcode/arrays/4sum/Solution.h b/src/leetcode/arrays/4sum/Solution.h new file mode 100644 index 0000000..5555781 --- /dev/null +++ b/src/leetcode/arrays/4sum/Solution.h @@ -0,0 +1,41 @@ +//cppcheck-suppress-file [functionStatic,ctuOneDefinitionRuleViolation] + +// 18. 4Sum +// Medium +// Topics +// premium lock icon +// Companies +// Given an array nums of n integers, return an array of all the unique quadruplets [nums[a], nums[b], nums[c], nums[d]] such that: + +// 0 <= a, b, c, d < n +// a, b, c, and d are distinct. +// nums[a] + nums[b] + nums[c] + nums[d] == target +// You may return the answer in any order. + + + +// Example 1: + +// Input: nums = [1,0,-1,0,-2,2], target = 0 +// Output: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]] +// Example 2: + +// Input: nums = [2,2,2,2,2], target = 8 +// Output: [[2,2,2,2]] + + +// Constraints: + +// 1 <= nums.length <= 200 +// -109 <= nums[i] <= 109 +// -109 <= target <= 109 + +#pragma once + +#include +#include + +class Solution { + public: + std::vector> fourSum(std::vector& nums, int target); +}; diff --git a/tests/four_sum_ut.cpp b/tests/four_sum_ut.cpp new file mode 100644 index 0000000..fa71fd2 --- /dev/null +++ b/tests/four_sum_ut.cpp @@ -0,0 +1,29 @@ +#include +#include "../src/leetcode/arrays/4sum/Solution.h" + +// Create a struct for our tcs +struct FourSumCase { + int target; + std::vector numsInput; + std::vector> expected; +}; + +// Param Test Fixture +class FourSumTest : public ::testing::TestWithParam {}; + +// Test Data +INSTANTIATE_TEST_SUITE_P( + BasicTestCases, FourSumTest, + ::testing::Values( + FourSumCase{0, + {1, 0, -1, 0, -2, 2}, + {{-2, -1, 1, 2}, {-2, 0, 0, 2}, {-1, 0, 0, 1}}}, + FourSumCase{8, {2, 2, 2, 2, 2}, {{2, 2, 2, 2}}})); + +TEST_P(FourSumTest, ReturnCorrectValues) { + const auto& tc = GetParam(); // get the test case from test suit + Solution s{}; + auto nums = tc.numsInput; + auto target = tc.target; + EXPECT_EQ(s.fourSum(nums, target), tc.expected); +} From 4d27625b2f4ba2845fff45e88be2ba5d1d814460 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Wed, 4 Feb 2026 14:14:45 +0700 Subject: [PATCH 03/10] correct space complexity of the 3sum --- src/leetcode/arrays/3sum/Solution.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leetcode/arrays/3sum/Solution.cpp b/src/leetcode/arrays/3sum/Solution.cpp index 78bd324..184fa4b 100644 --- a/src/leetcode/arrays/3sum/Solution.cpp +++ b/src/leetcode/arrays/3sum/Solution.cpp @@ -3,7 +3,7 @@ /** * @brief Complexity * - Time: O(nlog(n) + n(n-1)) ~ O(n2) - * - Space: O(1) + * - Space: O(logn) // sort */ std::vector> Solution::threeSum(std::vector& nums) { std::vector> trips{}; From d867ba427dd43cf83b97c3d960fa5e2546afe053 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Wed, 4 Feb 2026 15:54:22 +0700 Subject: [PATCH 04/10] fix bugs for LC15 --- src/leetcode/arrays/4sum/Solution.cpp | 9 ++------- tests/four_sum_ut.cpp | 7 +++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/leetcode/arrays/4sum/Solution.cpp b/src/leetcode/arrays/4sum/Solution.cpp index fd08fdc..78f5f30 100644 --- a/src/leetcode/arrays/4sum/Solution.cpp +++ b/src/leetcode/arrays/4sum/Solution.cpp @@ -15,19 +15,15 @@ std::vector> Solution::fourSum(std::vector& nums, } std::sort(nums.begin(), nums.end()); - if (nums[0] > target) { - return {}; - } std::vector> result; for (size_t fI = 0; fI < size - 3; ++fI) { if (fI > 0 && nums[fI] == nums[fI - 1]) { - ++fI; continue; } for (size_t sI = fI + 1; sI < size - 2; ++sI) { - if (sI > 1 && nums[sI] == nums[sI - 1]) { + if (sI > fI + 1 && nums[sI] == nums[sI - 1]) { continue; } @@ -38,8 +34,7 @@ std::vector> Solution::fourSum(std::vector& nums, int s = nums[sI]; int t = nums[tI]; int fr = nums[frI]; - - int diff = f + s + t + fr - target; + long diff = static_cast(f) + s + t + fr - target; if (diff == 0) { result.push_back({f, s, t, fr}); // Next diff --git a/tests/four_sum_ut.cpp b/tests/four_sum_ut.cpp index fa71fd2..8b9bbeb 100644 --- a/tests/four_sum_ut.cpp +++ b/tests/four_sum_ut.cpp @@ -18,6 +18,13 @@ INSTANTIATE_TEST_SUITE_P( FourSumCase{0, {1, 0, -1, 0, -2, 2}, {{-2, -1, 1, 2}, {-2, 0, 0, 2}, {-1, 0, 0, 1}}}, + FourSumCase{0, + {-2, -1, -1, 1, 1, 2, 2}, + {{-2, -1, 1, 2}, {-1, -1, 1, 1}}}, + FourSumCase{3, {-3, -4, -5, 0, -5, -2, 5, 2, -3}, {{-4, 0, 2, 5}}}, + FourSumCase{-294967296, + {1000000000, 1000000000, 1000000000, 1000000000}, + {}}, FourSumCase{8, {2, 2, 2, 2, 2}, {{2, 2, 2, 2}}})); TEST_P(FourSumTest, ReturnCorrectValues) { From 42793fa90e6e0fca458fa219cbd832f0e79adf1d Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Thu, 5 Feb 2026 13:43:55 +0700 Subject: [PATCH 05/10] Add a dummy test --- tests/dummy/UsbHostSerial_read.cpp | 72 ++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/dummy/UsbHostSerial_read.cpp diff --git a/tests/dummy/UsbHostSerial_read.cpp b/tests/dummy/UsbHostSerial_read.cpp new file mode 100644 index 0000000..a2ce891 --- /dev/null +++ b/tests/dummy/UsbHostSerial_read.cpp @@ -0,0 +1,72 @@ +#include +#include +#include + +/** + * This is a quick test for a PR in https://github.com/bertmelis/USBHostSerial + */ + +#define F_SIZE 9999 +uint8_t fakeResponses[F_SIZE]; + +static uint8_t* mockRingbufferReceiveUpTo(std::size_t* pxItemSize, + std::size_t xMaxSize) { + std::cout << "xMaxSize: " << xMaxSize << std::endl; + static std::size_t offset = 0; + + if (offset >= sizeof(fakeResponses)) { + return nullptr; // no more data + } + + std::size_t remaining = sizeof(fakeResponses) - offset; + std::size_t chunkSize = remaining < xMaxSize ? remaining : xMaxSize; + + // simulate fixed 5-byte chunks + if (chunkSize > 5) { + chunkSize = 5; + } + + *pxItemSize = chunkSize; + + uint8_t* ret = const_cast(fakeResponses + offset); + offset += chunkSize; + return ret; +} + +std::size_t read(uint8_t* dest, std::size_t size) { + std::size_t retVal = 0; + std::size_t pxItemSize = 0; + while (size > retVal) { + void* ret = mockRingbufferReceiveUpTo(&pxItemSize, size - retVal); + if (ret) { + std::memcpy(dest + retVal, ret, pxItemSize); + std::cout << "ret: " << (size_t)pxItemSize << std::endl; + retVal += pxItemSize; + } else { + break; + } + } + return retVal; +} + +static void testRead() { + for (int i = 0; i < F_SIZE; i++) { + fakeResponses[i] = i; + } + + uint8_t checkBuff[100]{}; + + std::size_t bytes = read(checkBuff, sizeof(checkBuff)); + + // basic checks + if (bytes != 100) { + std::cout << "Error: Len missmatch \n"; + } + + std::cout << "checkBuff \n"; + for (int i = 0; i < 100; ++i) { + std::cout << (int)checkBuff[i] << " "; + } + + std::cout << std::endl; +} \ No newline at end of file From a4c44256805a52b817b5b8bc0ca25ba562964e8a Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Thu, 5 Feb 2026 14:17:51 +0700 Subject: [PATCH 06/10] add examples for exception handling --- CMakeLists.txt | 3 ++ src/core/exception/BasicHandle.cpp | 30 ++++++++++++++++++++ src/core/exception/README.md | 13 +++++++++ src/core/exception/ThrowNoexcept.cpp | 41 ++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 src/core/exception/BasicHandle.cpp create mode 100644 src/core/exception/README.md create mode 100644 src/core/exception/ThrowNoexcept.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 334f153..807bfb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,9 @@ set(APP_SOURCES "src/patterns/creational/AbstractFactory.cpp" "src/patterns/creational/Builder.cpp" "src/patterns/creational/Prototype.cpp" + ## Exceptions + "src/core/exception/BasicHandle.cpp" + "src/core/exception/ThrowNoexcept.cpp" ## Streams "src/core/filehandle/IOStream.cpp" "src/core/filehandle/StringStream.cpp" diff --git a/src/core/exception/BasicHandle.cpp b/src/core/exception/BasicHandle.cpp new file mode 100644 index 0000000..01703ec --- /dev/null +++ b/src/core/exception/BasicHandle.cpp @@ -0,0 +1,30 @@ +#include +#include + +namespace { +void errorFnc() { + throw std::runtime_error("[L] error fnc\n"); +} + +void run() { + try { + try { + errorFnc(); + } catch (std::exception& e) { + std::cout << e.what(); + throw std::runtime_error("[M] Middle error \n"); + // throw; // rethrow + } + } catch (std::exception& e) { + std::cout << "[H] " << e.what(); + std::cout << "Caught an exception of type: " << typeid(e).name() + << std::endl; + } +} +} // namespace + +struct BasicHandleRunner { + BasicHandleRunner() { run(); } +}; + +static BasicHandleRunner autoRunner; \ No newline at end of file diff --git a/src/core/exception/README.md b/src/core/exception/README.md new file mode 100644 index 0000000..d36c2c9 --- /dev/null +++ b/src/core/exception/README.md @@ -0,0 +1,13 @@ +# 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) + diff --git a/src/core/exception/ThrowNoexcept.cpp b/src/core/exception/ThrowNoexcept.cpp new file mode 100644 index 0000000..563c386 --- /dev/null +++ b/src/core/exception/ThrowNoexcept.cpp @@ -0,0 +1,41 @@ +#include +#include + +namespace { + +void errorFnc() { + throw std::runtime_error("errorFnc\n"); +} + +// void noExpectExcpt() throw() { +void noExpectExcpt() noexcept { + try { + errorFnc(); + } catch (std::exception& e) { + std::cout << typeid(e).name() << " " << e.what(); + } +} + +void expectExcpt() noexcept(false) { + try { + errorFnc(); + } catch (std::exception& e) { + std::cout << typeid(e).name() << " " << e.what(); + throw; + } +} + +void run() { + noExpectExcpt(); + + std::set_terminate([]() { exit(-1); }); + + // expectExcpt(); terminated program when got unhandle exception +} +} // namespace + +struct ThrowNoexceptRunner { + ThrowNoexceptRunner() { run(); } +}; + +static ThrowNoexceptRunner autoRunner; \ No newline at end of file From 67b8f68bf3c6848d168a722b0ff9ff587a8e13eb Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Thu, 5 Feb 2026 16:52:01 +0700 Subject: [PATCH 07/10] add PID implemetation example --- CMakeLists.txt | 3 + src/controller/pid/PIDSim.cpp | 22 +++++++ src/controller/pid/pid.cpp | 115 ++++++++++++++++++++++++++++++++++ src/controller/pid/pid.h | 49 +++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 src/controller/pid/PIDSim.cpp create mode 100644 src/controller/pid/pid.cpp create mode 100644 src/controller/pid/pid.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 807bfb1..a871a77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,9 @@ set(APP_SOURCES "src/leetcode/arrays/longest_common_prefix/Solution.cpp" "src/leetcode/arrays/3sum/Solution.cpp" "src/leetcode/arrays/4sum/Solution.cpp" + ## Controller + "src/controller/pid/pid.cpp" + "src/controller/pid/PIDSim.cpp" ) # Test files diff --git a/src/controller/pid/PIDSim.cpp b/src/controller/pid/PIDSim.cpp new file mode 100644 index 0000000..f037fe5 --- /dev/null +++ b/src/controller/pid/PIDSim.cpp @@ -0,0 +1,22 @@ +#include +#include "pid.h" + +namespace { +void run() { + PID pid = PID(0.1, 100, -100, 0.1, 0.01, 0.5); + double setpoint = 100; + double val = 20; + for (int i = 0; i < 200; i++) { + double inc = pid.calculate(setpoint, val); + std::cout << "setpoint:" << setpoint << " - val:" << val << " - inc:" << inc + << std::endl; + val += inc; + } +} +} // namespace + +struct PIDSimRunner { + PIDSimRunner() { run(); } +}; + +static PIDSimRunner autoRunner; diff --git a/src/controller/pid/pid.cpp b/src/controller/pid/pid.cpp new file mode 100644 index 0000000..0548ef8 --- /dev/null +++ b/src/controller/pid/pid.cpp @@ -0,0 +1,115 @@ +/** + * Copyright 2019 Bradley J. Snyder + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _PID_SOURCE_ +#define _PID_SOURCE_ + +#include +#include +#include "pid.h" + +using namespace std; + +class PIDImpl +{ + public: + PIDImpl( double dt, double max, double min, double Kp, double Kd, double Ki ); + ~PIDImpl(); + double calculate( double setpoint, double pv ); + + private: + double _dt; + double _max; + double _min; + double _Kp; + double _Kd; + double _Ki; + double _pre_error; + double _integral; +}; + + +PID::PID( double dt, double max, double min, double Kp, double Kd, double Ki ) +{ + pimpl = new PIDImpl(dt,max,min,Kp,Kd,Ki); +} +double PID::calculate( double setpoint, double pv ) +{ + return pimpl->calculate(setpoint,pv); +} +PID::~PID() +{ + delete pimpl; +} + +/** + * Implementation + */ +PIDImpl::PIDImpl( double dt, double max, double min, double Kp, double Kd, double Ki ) : + _dt(dt), + _max(max), + _min(min), + _Kp(Kp), + _Kd(Kd), + _Ki(Ki), + _pre_error(0), + _integral(0) +{ +} + +double PIDImpl::calculate( double setpoint, double pv ) +{ + + // Calculate error + double error = setpoint - pv; + + // Proportional term + double Pout = _Kp * error; + + // Integral term + _integral += error * _dt; + double Iout = _Ki * _integral; + + // Derivative term + double derivative = (error - _pre_error) / _dt; + double Dout = _Kd * derivative; + + // Calculate total output + double output = Pout + Iout + Dout; + + // Restrict to max/min + if( output > _max ) + output = _max; + else if( output < _min ) + output = _min; + + // Save error to previous error + _pre_error = error; + + return output; +} + +PIDImpl::~PIDImpl() +{ +} + +#endif \ No newline at end of file diff --git a/src/controller/pid/pid.h b/src/controller/pid/pid.h new file mode 100644 index 0000000..d9a2445 --- /dev/null +++ b/src/controller/pid/pid.h @@ -0,0 +1,49 @@ +/** + * Copyright 2019 Bradley J. Snyder + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _PID_H_ +#define _PID_H_ + +class PIDImpl; +class PID { + public: + // Kp - proportional gain + // Ki - Integral gain + // Kd - derivative gain + // dt - loop interval time + // max - maximum value of manipulated variable + // min - minimum value of manipulated variable + PID(double dt, double max, double min, double Kp, double Kd, double Ki); + + // Returns the manipulated variable given a setpoint and current process value + double calculate(double setpoint, double pv); + ~PID(); + + PID() = delete; + PID(const PID& other) = delete; + PID& operator=(const PID& other) = delete; + + private: + PIDImpl* pimpl; +}; + +#endif \ No newline at end of file From 63b44c5216388efd018af84aa065dff2f1594c97 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Fri, 6 Feb 2026 11:11:30 +0700 Subject: [PATCH 08/10] update for reinterpret_cast --- src/core/datatypes/TypeConVersions.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/core/datatypes/TypeConVersions.cpp b/src/core/datatypes/TypeConVersions.cpp index fb4b683..0706aa8 100644 --- a/src/core/datatypes/TypeConVersions.cpp +++ b/src/core/datatypes/TypeConVersions.cpp @@ -65,6 +65,21 @@ void explicitConversion() { const void* pVoid = reinterpret_cast(&pi); cout << "reinterpret_cast: address of pi = " << pVoid << "\n"; + // ** Use case: Memory-Mapped I/O ** + // C + // #define REG_ADDR 0x000fff01 + // volatile uint8_t* reg = reinterpret_cast(REG_ADDR); + // *reg = 0xF; + // *reg = 0x1; + + // C++ + // #include + // constexpr std::uintptr_t REG_ADDR = 0x000fff02; + // auto* const reg = reinterpret_cast(REG_ADDR); + // *reg = 0xF; + + // *reg = 0x1; + // *5. dynamic_cast: safe cast between related classes (runtime checked) Base* basePtr = new Derived(); const Derived* derivedPtr = dynamic_cast(basePtr); From d20e848c23b592bad440f144a6603194400beb5b Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Fri, 6 Feb 2026 14:05:09 +0700 Subject: [PATCH 09/10] Update logs --- src/core/exception/BasicHandle.cpp | 5 ++++- src/core/exception/ThrowNoexcept.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/exception/BasicHandle.cpp b/src/core/exception/BasicHandle.cpp index 01703ec..0125541 100644 --- a/src/core/exception/BasicHandle.cpp +++ b/src/core/exception/BasicHandle.cpp @@ -24,7 +24,10 @@ void run() { } // namespace struct BasicHandleRunner { - BasicHandleRunner() { run(); } + BasicHandleRunner() { + std::cout << "\n--- Basic Expception Handle Example ---\n"; + run(); + } }; static BasicHandleRunner autoRunner; \ No newline at end of file diff --git a/src/core/exception/ThrowNoexcept.cpp b/src/core/exception/ThrowNoexcept.cpp index 563c386..fbfa0ca 100644 --- a/src/core/exception/ThrowNoexcept.cpp +++ b/src/core/exception/ThrowNoexcept.cpp @@ -35,7 +35,10 @@ void run() { } // namespace struct ThrowNoexceptRunner { - ThrowNoexceptRunner() { run(); } + ThrowNoexceptRunner() { + std::cout << "\n--- Exception throw/noexcept Example ---\n"; + run(); + } }; static ThrowNoexceptRunner autoRunner; \ No newline at end of file From 07d031fac8c7884f34a639acc99b355edd1f9438 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Fri, 6 Feb 2026 14:05:37 +0700 Subject: [PATCH 10/10] Add examples for the smart pointers --- CMakeLists.txt | 4 + src/core/datatypes/smart_pointer/README.md | 163 ++++++++++++++++++++ src/core/datatypes/smart_pointer/Shared.cpp | 75 +++++++++ src/core/datatypes/smart_pointer/Unique.cpp | 63 ++++++++ src/core/datatypes/smart_pointer/Weak.cpp | 56 +++++++ 5 files changed, 361 insertions(+) create mode 100644 src/core/datatypes/smart_pointer/README.md create mode 100644 src/core/datatypes/smart_pointer/Shared.cpp create mode 100644 src/core/datatypes/smart_pointer/Unique.cpp create mode 100644 src/core/datatypes/smart_pointer/Weak.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a871a77..cccefe5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,6 +144,10 @@ set(APP_SOURCES ## Controller "src/controller/pid/pid.cpp" "src/controller/pid/PIDSim.cpp" + ## Smart Pointers + "src/core/datatypes/smart_pointer/Unique.cpp" + "src/core/datatypes/smart_pointer/Shared.cpp" + "src/core/datatypes/smart_pointer/Weak.cpp" ) # Test files diff --git a/src/core/datatypes/smart_pointer/README.md b/src/core/datatypes/smart_pointer/README.md new file mode 100644 index 0000000..05b87bc --- /dev/null +++ b/src/core/datatypes/smart_pointer/README.md @@ -0,0 +1,163 @@ +## 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. 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. 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. 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/datatypes/smart_pointer/Shared.cpp b/src/core/datatypes/smart_pointer/Shared.cpp new file mode 100644 index 0000000..519a12f --- /dev/null +++ b/src/core/datatypes/smart_pointer/Shared.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +namespace { +// Shared resource rather than copy to optimize memory +struct AppConfig { + std::string server; + int port; + + AppConfig(std::string s, int p) : server(std::move(s)), port(p) { + std::cout << "Config loaded\n"; + } + + void setPort(int p) { port = p; } + + ~AppConfig() { std::cout << "Config destroyed\n"; } +}; + +// Module A +class NetworkManager { + public: + explicit NetworkManager(std::shared_ptr cfg) + : mConfig(std::move(cfg)) {} + + void connect() const { + std::cout << "Connecting to " << mConfig->server << ":" << mConfig->port + << "\n"; + } + + private: + // const means we cannot modify config at this point + std::shared_ptr mConfig; +}; + +// Module B +class Logger { + public: + explicit Logger(std::shared_ptr cfg) + : mConfig(std::move(cfg)) {} + + void log_start() const { + std::cout << "Logging enabled for " << mConfig->server << "\n"; + } + + private: + std::shared_ptr mConfig; +}; + +void run() { + // auto config = std::make_shared("api.example.com", 443); + std::shared_ptr config = + std::make_shared("test.server.com", 80); + + NetworkManager net(config); + Logger logger(config); + + net.connect(); + logger.log_start(); + + std::cout << "use_count = " << config.use_count() << "\n"; + config->setPort(8080); + net.connect(); + logger.log_start(); +} + +} // namespace + +struct SharedRunner { + SharedRunner() { + std::cout << "\n--- Shared Pointer Example ---\n"; + run(); + } +}; + +static SharedRunner autoRunner; \ No newline at end of file diff --git a/src/core/datatypes/smart_pointer/Unique.cpp b/src/core/datatypes/smart_pointer/Unique.cpp new file mode 100644 index 0000000..0869713 --- /dev/null +++ b/src/core/datatypes/smart_pointer/Unique.cpp @@ -0,0 +1,63 @@ +#include +#include +#include // for smart ptr + +namespace { +class Model { + private: + std::unique_ptr cstring; + + public: + explicit Model(const char* s) : cstring{nullptr} { + if (s) { + // allocate + cstring = std::make_unique(std::strlen(s) + 1); + std::strcpy(cstring.get(), s); // populate + } + } + + ~Model() { std::cout << "Model deleted :" << c_str() << "\n"; } + // Helper functions + const char* c_str() const // accessor + { + return cstring.get(); + } + + void set_first_char(char ch) { + if (cstring) + cstring[0] = ch; + } +}; + +void run() { + std::cout << "\n\nProblem\n"; + Model str1{"str1"}; + Model str2(str1.c_str()); + std::cout << "Before change:\n"; + std::cout << " str1 = " << str1.c_str() << "\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + str2.set_first_char('X'); + std::cout << "\nAfter modifying str2.set_first_char('X'):\n"; + std::cout << " str1 = " << str1.c_str() << "\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + { + std::cout << "str1-model,str2-model in scope. \n"; + std::unique_ptr ptr1 = std::make_unique("str1-model"); + std::unique_ptr ptr2(new Model("str2-model")); + std::cout << " str1-model = " << ptr1->c_str() << "\n"; + std::cout << " str2-model = " << ptr2->c_str() << "\n"; + } + std::cout << "str1-model,str2-model out of scope. \n"; +} +} // namespace + +struct UniqueRunner { + UniqueRunner() { + std::cout << "\n--- Unique Pointer Example ---\n"; + run(); + } +}; + +static UniqueRunner autoRunner; \ No newline at end of file diff --git a/src/core/datatypes/smart_pointer/Weak.cpp b/src/core/datatypes/smart_pointer/Weak.cpp new file mode 100644 index 0000000..9f3f164 --- /dev/null +++ b/src/core/datatypes/smart_pointer/Weak.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +namespace { +// Shared resource rather than copy to optimize memory +struct AppConfig { + std::string server; + int port; + + AppConfig(std::string s, int p) : server(std::move(s)), port(p) { + std::cout << "Config loaded\n"; + } + + void setPort(int p) { port = p; } + + ~AppConfig() { std::cout << "Config destroyed\n"; } +}; + +void run() { + std::weak_ptr observeConfig; + { + std::shared_ptr config = + std::make_shared("test.server.com", 80); + + // check lock() to determine if pointer is valid + observeConfig = config; + if (auto tmpConfig = observeConfig.lock()) { + std::cout << "[O] config value is: " << tmpConfig->port << " " + << tmpConfig->server << '\n'; + } else { + std::cout << "[O] config is expired\n"; + } + } + + if (auto tmpConfig = observeConfig.lock()) { + std::cout << "[O] config value is: " << tmpConfig->port << " " + << tmpConfig->server << '\n'; + } else { + std::cout << "[O] config is expired\n"; + } +} + +} // namespace + +/** + * wp.expired() == false // at least one shared_ptr still owns it + * wp.lock() != nullptr // same condition + */ +struct WeakRunner { + WeakRunner() { + std::cout << "\n--- Weak Pointer Example ---\n"; + run(); + } +}; + +static WeakRunner autoRunner; \ No newline at end of file