From 9c02414255c46033bba90772bff7619553fc1e3a Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Mon, 27 Jul 2026 09:18:50 +0700 Subject: [PATCH 01/20] Update for Proxy --- src/dp/structural/Proxy.cpp | 175 ++++++++++-------------------------- 1 file changed, 45 insertions(+), 130 deletions(-) diff --git a/src/dp/structural/Proxy.cpp b/src/dp/structural/Proxy.cpp index 649955a..dd7427c 100644 --- a/src/dp/structural/Proxy.cpp +++ b/src/dp/structural/Proxy.cpp @@ -1,36 +1,31 @@ -// Proxy is a structural design pattern that lets you provide a substitute or -// placeholder for another object. A proxy controls access to the original -// object, allowing you to perform something either before or after the request -// gets through to the original object. A proxy receives client requests, does -// some work (access control, caching, etc.) and then passes the request to a -// service object. Appicability: -// (*) Lazy initialization (virtual proxy) / Logging requests (logging proxy) -// / Caching request results (caching proxy) -// (**) Access control (protection proxy). -// (***) Local execution of a remote service (remote proxy). This is when the -// service object is located on a remote server. - // Composition - lazy // Aggregation - x -// UML: docs/uml/patterns_structural_proxy.drawio.svg -#include +#include #include #include "ExampleRegistry.h" +#include "Logger.h" namespace { -namespace problem { -const std::string kAdmin = "admin"; - +/// @class Server Interface class IServer { public: virtual ~IServer() = default; - virtual void request1() = 0; - virtual void request2() = 0; - virtual void request3() = 0; + + /// @brief api + virtual void request() = 0; }; +/// @brief User code +void clientCode(IServer* s) { + if (s != nullptr) { + s->request(); + } +} + +namespace problem { +const std::string kAdmin = "admin"; class Server : public IServer { private: std::string id_; @@ -38,184 +33,104 @@ class Server : public IServer { public: explicit Server(std::string id) : id_{std::move(id)} { // [P1] Heavy or complex construction, so ideally should be lazy-loaded - std::cout << "[Server] Constructor: " << id_ << "\n"; + LOG_S("CTR: " << id_); } // [P2] Need access control // [P3] Need to log requests without modifying the Server itself - void request1() override { + void request() override { if (id_ != kAdmin) { - std::cout << "[Server] Invalid ID: " << id_ << "\n"; + LOG_S("Invalid ID: " << id_); return; } - std::cout << "[Server] Handling request-1 for: " << id_ << "\n"; - } - - void request2() override { - if (id_ != kAdmin) { - std::cout << "[Server] Invalid ID: " << id_ << "\n"; - return; - } - std::cout << "[Server] Handling request-2 for: " << id_ << "\n"; - } - - void request3() override { - if (id_ != kAdmin) { - std::cout << "[Server] Invalid ID: " << id_ << "\n"; - return; - } - std::cout << "[Server] Handling request-3 for: " << id_ << "\n"; + LOG_S("Handling request for: " << id_); } }; -namespace client { -void clientCode(IServer* s) { - if (s != nullptr) { - s->request1(); - s->request2(); - s->request3(); - } -} -} // namespace client - void run() { - std::cout << "\n\n"; - { std::string connection_id = "admin"; // [P4] The Server is constructed immediately even if we do not call any // requests - IServer* server = new Server(connection_id); - std::cout << "User request\n"; - client::clientCode(server); - delete server; + auto server = std::make_unique(connection_id); + LOG("User request"); + clientCode(server.get()); } { // [P4] Server is constructed even for invalid ID, wasting resources std::string invalid_id = "xxx"; - auto* server = new Server(invalid_id); - client::clientCode(server); - delete server; + auto server = std::make_unique(invalid_id); + LOG("User request"); + clientCode(server.get()); } } } // namespace problem namespace proxy_pattern { const std::string kAdmin = "admin"; - -class IServer { - public: - virtual ~IServer() = default; - virtual void request1() = 0; - virtual void request2() = 0; - virtual void request3() = 0; -}; - class Server : public IServer { private: std::string id_; public: explicit Server(std::string id) : id_{std::move(id)} { - std::cout << "[Server] Constructor: " << id_ << "\n"; - } - - void request1() override { - std::cout << "[Server] Handling request-1 for: " << id_ << "\n"; - } - - void request2() override { - std::cout << "[Server] Handling request-2 for: " << id_ << "\n"; + LOG_S("CTR: " << id_); } - void request3() override { - std::cout << "[Server] Handling request-3 for: " << id_ << "\n"; - } + void request() override { LOG_S("Handling request for: " << id_); } }; class ServerProxy : public IServer { private: std::string id_; - Server* server_{}; + std::unique_ptr server_; bool checkAccess() { - std::cout << "[Proxy] Checking access before forwarding request.\n"; + LOG("Checking access before forwarding request."); if (id_ != kAdmin) { + LOG("Invalid id. Return"); return false; } // Lazy initialization: construct Server only on first access if (server_ == nullptr) { - server_ = new Server(id_); + server_ = std::make_unique(id_); } return true; } - void logAccess() const { - std::cout << "[Proxy] Logging request time: " << id_ << " .\n"; - } + void logAccess() const { LOG_S("Logging request time: " << id_); } public: explicit ServerProxy(std::string id) : id_{std::move(id)} { - std::cout << "[Proxy] Constructor: " << id_ << "\n"; - } - - ~ServerProxy() override { - std::cout << "[Proxy] Destructor: " << id_ << "\n"; - - delete server_; + LOG_S("CTR: " << id_); } - void request1() override { + void request() override { if (checkAccess()) { - server_->request1(); - logAccess(); - } - } - - void request2() override { - if (checkAccess()) { - server_->request2(); - logAccess(); - } - } - - void request3() override { - if (checkAccess()) { - server_->request3(); + server_->request(); logAccess(); } } }; -namespace client { -void clientCode(IServer* s) { - if (s != nullptr) { - s->request1(); - s->request2(); - s->request3(); - } -} -} // namespace client - void run() { - std::cout << "\n\n"; - { std::string connection_id = "admin"; // Server is not constructed until first request is made - IServer* server = new ServerProxy(connection_id); - std::cout << "User request\n"; - client::clientCode(server); - delete server; + auto server_proxy = std::make_unique(connection_id); + LOG("User request"); + clientCode(server_proxy.get()); } - // Server is not constructed if id is invalid - std::string invalid_id = "xxx"; - auto* server = new Server(invalid_id); - client::clientCode(server); - delete server; + { + // Server is not constructed if id is invalid + std::string invalid_id = "xxx"; + auto server_proxy = std::make_unique(invalid_id); + LOG("User request"); + clientCode(server_proxy.get()); + } } } // namespace proxy_pattern From aa331305f6f54b1a770f229b2c746e614e88f7b1 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Mon, 27 Jul 2026 09:28:16 +0700 Subject: [PATCH 02/20] Update for Adapter --- src/dp/structural/Adapter.cpp | 100 ++++++++++++++++------------------ 1 file changed, 46 insertions(+), 54 deletions(-) diff --git a/src/dp/structural/Adapter.cpp b/src/dp/structural/Adapter.cpp index 6ea3755..e881bd1 100644 --- a/src/dp/structural/Adapter.cpp +++ b/src/dp/structural/Adapter.cpp @@ -1,79 +1,73 @@ -// Adapters is a structural design pattern that allows objects with incompatible -// interfaces to collaborate. Appicability: -// (*) When you want to use some existing class, but its interface isn’t -// compatible with the rest of your code -// (**)When you want to reuse several existing subclasses that lack some common -// functionality that can’t be added to the superclass. - -// UML: docs/uml/patterns_structural_adapter.drawio.svg - +#include #include "Logger.h" +#include "ExampleRegistry.h" + namespace adapter_pattern { -/** - * The Adaptee contains some useful behavior, but its interface is incompatible - * with the existing client code. The Adaptee needs some adaptation before the - * client code can use it. - */ + +/// @class Adaptee +/// @brief Existing class with an incompatible interface class Adaptee { public: void specific_request() { - dummy_++; - LOG("executed"); + ++dummy_; + LOG("Adaptee::specific_request()"); } private: - int dummy_{}; + int dummy_{0}; }; -/** - * The Target defines the domain-specific interface used by the client code. - */ +/// @class Target +/// @brief Interface expected by the client class Target { public: - virtual void request() { LOG("executed"); } + virtual ~Target() = default; + + virtual void request() = 0; }; -// ============================================================================================================ -// [Q] How can we make the clientCode works with Adaptee without change this -// function (e.g this is in front-end) -// => Create an Adapter -// ============================================================================================================ +/// @class ConcreteTarget +/// @brief A normal implementation of Target +class ConcreteTarget : public Target { + public: + void request() override { LOG("request"); } +}; -/** - * The Adapter makes the Adaptee's interface compatible with the Target's - * interface. - */ +/// @class Adapter +/// @brief Converts the Target interface into the Adaptee interface class Adapter : public Target { - private: - Adaptee* adaptee_; - public: - explicit Adapter(Adaptee* adaptee) : adaptee_{adaptee} { LOG("constructed"); } + explicit Adapter(std::unique_ptr adaptee) + : adaptee_(std::move(adaptee)) { + LOG("CTR"); + } + + void request() override { adaptee_->specific_request(); } - void request() override { return adaptee_->specific_request(); } + private: + std::unique_ptr adaptee_; }; +/// @brief Client code only depends on Target. +void client_code(Target& target) { + LOG(""); + target.request(); +} + void run() { LOG("Adapter Example"); + { + ConcreteTarget target; + client_code(target); + } + LOG(""); - // The client code supports all classes that follow the Target interface. - auto client_code = [](Target* target) { - LOG("executed"); - target->request(); - }; - - LOG("Client: Can work just fine with the Target objects:"); - Target target = Target(); - client_code(&target); - - LOG("Client: Cannot work with the Adaptee objects:"); - Adaptee adaptee = Adaptee(); - // Client::clientCode(&adaptee); // error - - LOG("Client: But can work with it via the Adapter:"); - auto adapter = Adapter(&adaptee); - client_code(&adapter); + { + auto adaptee = std::make_unique(); + Adapter adapter(std::move(adaptee)); + client_code(adapter); + } } } // namespace adapter_pattern @@ -140,8 +134,6 @@ void run() { } } // namespace case_study -#include "ExampleRegistry.h" - class AdapterExample : public IExample { public: std::string group() const override { return "dp/structural"; } From 6a79e162d0bc136f038b9c076048e7672ee68d46 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Mon, 27 Jul 2026 10:19:19 +0700 Subject: [PATCH 03/20] Update for bridge --- src/dp/structural/Bridge.cpp | 59 +++++++++++------------------------- 1 file changed, 17 insertions(+), 42 deletions(-) diff --git a/src/dp/structural/Bridge.cpp b/src/dp/structural/Bridge.cpp index bceb942..c59b7d2 100644 --- a/src/dp/structural/Bridge.cpp +++ b/src/dp/structural/Bridge.cpp @@ -1,16 +1,3 @@ -// Bridge lets we split a large class or a set of closely related classes -// into two separate hierarchies—abstraction and implementation -// which can be developed independently of each other. -// Appicability: -// (*) when you want to divide and organize a monolithic class that has -// several variants of some functionality -// (for example, if the class can work with various database servers). -// (**) when you need to extend a class in several orthogonal (independent) -// dimensions. -// (***) when you you need to be able to switch implementations at runtime. - -// UML: docs/uml/patterns_structural_bridge.drawio.svg - #include #include #include "ExampleRegistry.h" @@ -24,7 +11,6 @@ class Widget { virtual void click_on() const = 0; }; -/* Concrete variations for Button */ class Button : public Widget { public: void click_on() const override { LOG("executed"); } @@ -46,7 +32,6 @@ class ButtonLinux : public Button { } }; -/* Concrete variations for Label */ class Label : public Widget { public: void click_on() const override { LOG("executed"); } @@ -70,8 +55,6 @@ class LabelLinux : public Label { 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 ... auto client_code = [](const Widget* widget) { if (widget != nullptr) @@ -87,13 +70,8 @@ void run() { } // namespace problem namespace bridge_pattern { -/** - * The Implementation defines the interface for all implementation classes. It - * doesn't have to match the Abstraction's interface. In fact, the two - * interfaces can be entirely different. Typically the Implementation interface - * provides only primitive Widgets, while the Abstraction defines higher- - * level Widgets based on those primitives. - */ +/// @class Implemetation Interface +/// @brief Define the interface for all implementation classes class OsImplemetation { public: virtual void click_on_ipl() const = 0; @@ -110,11 +88,8 @@ class LinuxImplemetation : public OsImplemetation { void click_on_ipl() const override { LOG("[Linux]"); } }; -/** - * The Abstraction defines the interface for the "control" part of the two class - * hierarchies. It maintains a reference to an object of the Implementation - * hierarchy and delegates all of the real work to this object. - */ +/// @class Abstractio Class +/// @brief Define the interface for the control part class WidgetAbstraction { protected: std::shared_ptr implementation_; @@ -127,9 +102,6 @@ class WidgetAbstraction { virtual void click_on() const = 0; }; -/** - * We can extend the Abstraction without changing the Implementation classes. - */ class ButtonAbstraction : public WidgetAbstraction { public: explicit ButtonAbstraction(std::shared_ptr implemetation) @@ -153,20 +125,23 @@ class LabelAbstraction : public WidgetAbstraction { void run() { LOG("Bridge Example"); auto client_code = [](const WidgetAbstraction* widget) { - if (widget != nullptr) + if (widget != nullptr) { + LOG(""); widget->click_on(); + } }; - std::shared_ptr os = - std::make_shared(); - WidgetAbstraction* widget = new ButtonAbstraction(os); - client_code(widget); - - os = std::make_shared(); - widget = new LabelAbstraction(os); - client_code(widget); + { + auto os = std::make_shared(); + auto widget = std::make_unique(os); + client_code(widget.get()); + } - delete widget; + { + auto os = std::make_shared(); + auto widget = std::make_unique(os); + client_code(widget.get()); + } } } // namespace bridge_pattern From 2b93fac0c65adca392961f37c789068ca51baa60 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Mon, 27 Jul 2026 11:19:34 +0700 Subject: [PATCH 04/20] Create an demo for dlopen --- src/CMakeLists.txt | 1 + src/demo/CMakeLists.txt | 1 + src/demo/dlopen/CMakeLists.txt | 16 +++++++++ src/demo/dlopen/README.md | 43 +++++++++++++++++++++++ src/demo/dlopen/bridge.cpp | 62 ++++++++++++++++++++++++++++++++++ src/demo/dlopen/bridge.h | 12 +++++++ src/demo/dlopen/main.cpp | 55 ++++++++++++++++++++++++++++++ src/demo/dlopen/sample_app.cpp | 22 ++++++++++++ 8 files changed, 212 insertions(+) create mode 100644 src/demo/CMakeLists.txt create mode 100644 src/demo/dlopen/CMakeLists.txt create mode 100644 src/demo/dlopen/README.md create mode 100644 src/demo/dlopen/bridge.cpp create mode 100644 src/demo/dlopen/bridge.h create mode 100644 src/demo/dlopen/main.cpp create mode 100644 src/demo/dlopen/sample_app.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 191aecb..2185b50 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(controller) add_subdirectory(dp) add_subdirectory(socket) add_subdirectory(ap) +add_subdirectory(demo) # Header files directory set(APP_HEADERS diff --git a/src/demo/CMakeLists.txt b/src/demo/CMakeLists.txt new file mode 100644 index 0000000..7641d6a --- /dev/null +++ b/src/demo/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(dlopen) \ No newline at end of file diff --git a/src/demo/dlopen/CMakeLists.txt b/src/demo/dlopen/CMakeLists.txt new file mode 100644 index 0000000..d3550ba --- /dev/null +++ b/src/demo/dlopen/CMakeLists.txt @@ -0,0 +1,16 @@ +# bridge shared lib +add_library(bridge SHARED + bridge.cpp +) + +# shared lib +add_library(sample_app SHARED + sample_app.cpp +) +target_link_libraries(sample_app PRIVATE bridge) + +# executable demo +add_executable(demo_dlopen + main.cpp +) +target_link_libraries(demo_dlopen PRIVATE bridge sample_app) \ No newline at end of file diff --git a/src/demo/dlopen/README.md b/src/demo/dlopen/README.md new file mode 100644 index 0000000..bf13be4 --- /dev/null +++ b/src/demo/dlopen/README.md @@ -0,0 +1,43 @@ +# dlopen demo + +Minimal demo of the **host process + `dlopen` plugin + shared-lib bridge** pattern + +## Layout + +```text +dlopen_plugin_demo/ +├── CMakeLists.txt +├── bridge.h/.cpp # shared bridge API + singleton +├── sample_app # SHARED lib with main() +├── main.cpp # executable that dlopen + dlsym(main) +└── README.md +``` + +--- +**Expected flow:** +1. `demo_dlopen` loads `libsample_app.so` +2. finds symbol `main` via `dlsym` +3. background thread marks bridge ready +4. plugin waits, then write/read a fake register through `libbridge.so` +5. both sides share **one** bridge singleton (because bridge is **SHARED**) + +```text +demo: host --dlopen--> libsample_app.so --calls--> libbridge.so +``` + +## Why bridge must be SHARED + +If `bridge` were STATIC, `host` and `libsample_app.so` would each get a private singleton -> `bridge_wait_ready()` would hang forever (plugin never sees host’s `ready` flag). + +Try the broken variant (optional experiment): + +```cmake +# in CMakeLists.txt, change: +add_library(bridge STATIC bridge.cpp) +``` + +Rebuild & run => plugin will block on `bridge_wait_ready()`. + +---- +EOF +---- \ No newline at end of file diff --git a/src/demo/dlopen/bridge.cpp b/src/demo/dlopen/bridge.cpp new file mode 100644 index 0000000..1a13ef6 --- /dev/null +++ b/src/demo/dlopen/bridge.cpp @@ -0,0 +1,62 @@ +#include "bridge.h" + +#include +#include +#include +#include + +namespace { + +struct BridgeState { + std::unordered_map regs; + std::mutex mtx; + std::condition_variable cv; + bool ready = false; +}; + +BridgeState& state() { + /// skelton + static BridgeState s; + return s; +} + +} // namespace + +void bridge_write_reg(std::uintptr_t addr, std::uint32_t value) { + auto& s = state(); + std::lock_guard lock(s.mtx); + s.regs[addr] = value; + std::cout << "[bridge] write 0x" << std::hex << addr << " = 0x" << value + << std::dec << "\n"; +} + +std::uint32_t bridge_read_reg(std::uintptr_t addr) { + auto& s = state(); + std::lock_guard lock(s.mtx); + auto it = s.regs.find(addr); + const std::uint32_t value = (it == s.regs.end()) ? 0U : it->second; + std::cout << "[bridge] read 0x" << std::hex << addr << " -> 0x" << value + << std::dec << "\n"; + return value; +} + +void bridge_set_ready() { + auto& s = state(); + { + std::lock_guard lock(s.mtx); + s.ready = true; + } + s.cv.notify_all(); + std::cout << "[bridge] host marked ready\n"; +} + +void bridge_wait_ready() { + auto& s = state(); + std::unique_lock lock(s.mtx); + s.cv.wait(lock, [&] { return s.ready; }); + std::cout << "[bridge] plugin saw ready\n"; +} + +const char* bridge_name() { + return "libbridge.so"; +} \ No newline at end of file diff --git a/src/demo/dlopen/bridge.h b/src/demo/dlopen/bridge.h new file mode 100644 index 0000000..b037f7b --- /dev/null +++ b/src/demo/dlopen/bridge.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +// Shared-lib bridge: one copy in the process for both host and plugin. +void bridge_write_reg(std::uintptr_t addr, std::uint32_t value); +std::uint32_t bridge_read_reg(std::uintptr_t addr); + +void bridge_set_ready(); +void bridge_wait_ready(); + +const char* bridge_name(); \ No newline at end of file diff --git a/src/demo/dlopen/main.cpp b/src/demo/dlopen/main.cpp new file mode 100644 index 0000000..722bce0 --- /dev/null +++ b/src/demo/dlopen/main.cpp @@ -0,0 +1,55 @@ +#include "bridge.h" + +#include + +#include +#include +#include + +using app_main_t = int (*)(int, char**); + +int main(int argc, char* argv[]) { + const char* plugin_path = "./libsample_app.so"; + if (argc > 1) { + plugin_path = argv[1]; + } + + std::cout << "[host] starting (bridge=" << bridge_name() << ")\n"; + std::cout << "[host] dlopen: " << plugin_path << "\n"; + + void* handle = dlopen(plugin_path, RTLD_LAZY | RTLD_GLOBAL); + if (!handle) { + std::cerr << "[host] dlopen failed: " << dlerror() << "\n"; + return 1; + } + + dlerror(); // clear + auto* app_main = reinterpret_cast(dlsym(handle, "main")); + const char* err = dlerror(); + if (err != nullptr || app_main == nullptr) { + std::cerr << "[host] dlsym(main) failed: " << (err ? err : "null") << "\n"; + dlclose(handle); + return 1; + } + + // init on another thread, then notify plugin + std::thread init_thread([] { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + bridge_write_reg(0x0000, 0x1U); // fake "device present" + bridge_set_ready(); + }); + + char arg0[] = "sample_app"; + char arg1[] = "hello"; + char* app_argv[] = {arg0, arg1, nullptr}; + const int app_argc = 2; + + std::cout << "[host] calling plugin main()\n"; + const int rc = app_main(app_argc, app_argv); + std::cout << "[host] plugin returned " << rc << "\n"; + + init_thread.join(); + dlclose(handle); + std::cout << "[host] done\n"; + return rc; +} \ No newline at end of file diff --git a/src/demo/dlopen/sample_app.cpp b/src/demo/dlopen/sample_app.cpp new file mode 100644 index 0000000..36e27fd --- /dev/null +++ b/src/demo/dlopen/sample_app.cpp @@ -0,0 +1,22 @@ +#include "bridge.h" + +#include +#include + +extern "C" int main(const int argc, const char* argv[]) { + std::cout << "[plugin] main() started (argc=" << argc << ")\n"; + for (int i = 0; i < argc; ++i) { + std::cout << "[plugin] argv[" << i << "] = " << argv[i] << "\n"; + } + + std::cout << "[plugin] waiting for host via " << bridge_name() << "...\n"; + bridge_wait_ready(); + + bridge_write_reg(0x1000, 0xA5A5A5A5U); + const std::uint32_t v = bridge_read_reg(0x1000); + std::cout << "[plugin] round-trip value = 0x" << std::hex << v << std::dec + << "\n"; + + std::cout << "[plugin] main() done\n"; + return 0; +} \ No newline at end of file From 91d28044329f32e9ef9780ff77e95b508a51feb3 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Fri, 7 Aug 2026 09:58:18 +0700 Subject: [PATCH 05/20] Update for decorator --- src/dp/structural/Decorator.cpp | 145 ++++++++++---------------------- 1 file changed, 45 insertions(+), 100 deletions(-) diff --git a/src/dp/structural/Decorator.cpp b/src/dp/structural/Decorator.cpp index 4947542..5676fc3 100644 --- a/src/dp/structural/Decorator.cpp +++ b/src/dp/structural/Decorator.cpp @@ -1,16 +1,10 @@ -// Decorator is a structural design pattern that lets you attach new behaviors -// to objects by placing these objects inside special wrapper objects that -// contain the behaviors. Appicability: -// (*) when you need to be able to assign extra behaviors to objects at -// runtime without breaking the code that uses these objects. -// (**) when it’s awkward or not possible to extend an object’s behavior using -// inheritance. UML: docs/uml/patterns_structural_decorator.drawio.svg - -#include +#include #include +#include "Logger.h" + +#include "ExampleRegistry.h" namespace { -namespace problem { class IComponent { public: @@ -18,6 +12,11 @@ class IComponent { virtual std::string operation() const = 0; }; +auto client_code = [](const IComponent* comp) { + LOG(comp->operation()); +}; + +namespace problem { class ConcreteComponent : public IComponent { public: std::string operation() const override { return "ConcreteComponent"; } @@ -44,86 +43,50 @@ class ComponentWithAandB : public ConcreteComponent { } }; -// [P1] -// If you have 3 features , e.g FeatureC -> many combinations -// If you have 5 features -> 32 subclasses - -namespace client { -void clientCode(const IComponent& comp) { - std::cout << comp.operation() << "\n"; -} -} // namespace client - void run() { - std::cout << "\n\nProblem\n"; - IComponent* simple = new ConcreteComponent; - client::clientCode(*simple); - - IComponent* with_a = new ComponentWithA; - client::clientCode(*with_a); + // [P1] + // If you have 3 features , e.g FeatureC -> many combinations + // If you have 5 features -> 32 subclasses + LOG("Problem"); + std::unique_ptr simple = std::make_unique(); + client_code(simple.get()); - IComponent* with_b = new ComponentWithB; - client::clientCode(*with_b); + std::unique_ptr with_a = std::make_unique(); + client_code(with_a.get()); - IComponent* with_ab = new ComponentWithAandB; - client::clientCode(*with_ab); + std::unique_ptr with_b = std::make_unique(); + client_code(with_b.get()); - delete simple; - delete with_a; - delete with_b; - delete with_ab; + std::unique_ptr with_ab = std::make_unique(); + client_code(with_ab.get()); } - } // namespace problem namespace decorator_pattern { -class IComponent { - public: - virtual ~IComponent() = default; - virtual std::string operation() const = 0; -}; - -/** - * Concrete Component - * - is a class of objects being wrapped. - * - defines the basic behavior, which can be altered by decorators. - */ +/// @class Concrete Component class ConcreteComponent : public IComponent { public: std::string operation() const override { return "ConcreteComponent"; } }; -/** - * The base Decorator class follows the same interface as the other components. - * - has a field for referencing a wrapped object. - * - the field’s type should be declared as the component interface so it can - * contain both concrete components and decorators. - * - the base decorator delegates all operations to the wrapped object. - */ +/// @class Base Decorator class BaseDecorator : public IComponent { protected: - IComponent* component_; + std::unique_ptr component_; public: - explicit BaseDecorator(IComponent* component) : component_{component} {} + explicit BaseDecorator(std::unique_ptr component) + : component_(std::move(component)) {} - /** - * The Decorator delegates all work to the wrapped component. - */ + /// the Decorator delegates all work to the wrapped componentx std::string operation() const override { return component_->operation(); } }; -/** - * Concrete Decorators : - * - call the wrapped object and alter its result in some way. - * - define extra behaviors that can be added to components dynamically. - * - override methods of the base decorator and execute their behavior either - * before or after calling the parent method. - */ +/// @class Concrete Decorator class ConcreteDecoratorA : public BaseDecorator { public: - explicit ConcreteDecoratorA(IComponent* component) - : BaseDecorator{component} {} + explicit ConcreteDecoratorA(std::unique_ptr component) + : BaseDecorator(std::move(component)) {} std::string operation() const override { return BaseDecorator::operation() + " + FeatureA"; @@ -132,8 +95,8 @@ class ConcreteDecoratorA : public BaseDecorator { class ConcreteDecoratorB : public BaseDecorator { public: - explicit ConcreteDecoratorB(IComponent* component) - : BaseDecorator{component} {} + explicit ConcreteDecoratorB(std::unique_ptr component) + : BaseDecorator(std::move(component)) {} std::string operation() const override { return BaseDecorator::operation() + " + FeatureB"; @@ -142,49 +105,31 @@ class ConcreteDecoratorB : public BaseDecorator { class ConcreteDecoratorC : public BaseDecorator { public: - explicit ConcreteDecoratorC(IComponent* component) - : BaseDecorator{component} {} + explicit ConcreteDecoratorC(std::unique_ptr component) + : BaseDecorator(std::move(component)) {} std::string operation() const override { return BaseDecorator::operation() + " + FeatureC"; } }; -namespace client { -void clientCode(const IComponent& comp) { - std::cout << comp.operation() << "\n"; -} -} // namespace client - void run() { - std::cout << "\n\nDecorator\n"; - IComponent* simple = new ConcreteComponent; - client::clientCode(*simple); - - IComponent* with_a = new ConcreteDecoratorA(simple); - client::clientCode(*with_a); - - IComponent* with_b = new ConcreteDecoratorB(simple); - client::clientCode(*with_b); - - IComponent* with_ab = new ConcreteDecoratorB(with_a); - client::clientCode(*with_ab); - - IComponent* with_abc = new ConcreteDecoratorC(with_ab); - client::clientCode(*with_abc); - - delete simple; - delete with_a; - delete with_b; - delete with_ab; - delete with_abc; + LOG("Decorator"); + std::unique_ptr simple = std::make_unique(); + client_code(simple.get()); + + std::unique_ptr with_a = + std::make_unique(std::move(simple)); + std::unique_ptr with_ab = + std::make_unique(std::move(with_a)); + std::unique_ptr with_abc = + std::make_unique(std::move(with_ab)); + client_code(with_abc.get()); } } // namespace decorator_pattern } // namespace -#include "ExampleRegistry.h" - class DecoratorExample : public IExample { public: std::string group() const override { return "dp/structural"; } From 60a267a438fe619483ec76e712fa75c581c9275d Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Fri, 11 Sep 2026 23:01:54 +0700 Subject: [PATCH 06/20] Preserve pre-existing working tree changes (tests disabled, gitignore) Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LEsQ7AcLHafb1tAgosoJFz --- .gitignore | 3 ++- CMakeLists.txt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e84cd09..5577478 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ coverage_lcov !.vscode/launch.json !.vscode/tasks.json .cache -__pycache__/ \ No newline at end of file +__pycache__/ +*Testing* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b03e79d..f7cc84f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,4 +61,5 @@ enable_testing() # Add project modules add_subdirectory(src) -add_subdirectory(tests) \ No newline at end of file + +# add_subdirectory(tests) \ No newline at end of file From 13b615e61454ad11f7a5500e1901342d38c66469 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Fri, 11 Sep 2026 23:01:54 +0700 Subject: [PATCH 07/20] Add CLAUDE.md Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LEsQ7AcLHafb1tAgosoJFz --- CLAUDE.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9b3022b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,67 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +A C++20 learning lab: many small, self-contained examples (language features, STL, concurrency, design patterns, sockets, a PID controller) compiled into one interactive menu-driven executable, plus a few standalone executables (GTK4 MVC/MVVM apps, a `dlopen` demo) and a bare-metal ARM example that sits outside CMake. + +## Build & run + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug # configure +cmake --build build # build all targets +./build/bin/cpp_lab_project # interactive examples menu (optional: -mode Dev|Uat|Prod) +./scripts/run.sh # build + cppcheck + run (needs private/genid.py, which is gitignored) +``` + +- Configuring needs **gtkmm-4.0** (`libgtkmm-4.0-dev`). `cmake/Dependencies.cmake` marks it `REQUIRED`, so configure fails without it, even if you only want the main executable. GoogleTest is fetched over the network with FetchContent on the first configure. +- Executables go to `build/bin/` and shared libs to `build/lib/`. VS Code tasks/launch use `build/debug` instead. +- Other targets: `ap`, `mvc_ap`, `mvvm_ap` (GTK4 GUIs in `src/ap/`) and `demo_dlopen` (loads `libsample_app.so` at runtime; see `src/demo/dlopen/README.md` for why `bridge` must stay SHARED). +- `src/embedded/` is not part of CMake. Build and boot it in QEMU with `cd src/embedded && ./run.sh [gui|debug]` (requires `gcc-arm-none-eabi` and `qemu-system-arm`). + +## Tests + +GoogleTest/GMock tests live in `tests/` (the target is `cpp_lab_project_unit_test`; mocks and fakes are in `tests/mock/`). **`add_subdirectory(tests)` is currently commented out in the top-level `CMakeLists.txt`**, so uncomment it before building or running tests. + +```bash +ctest --test-dir build --output-on-failure # all tests +ctest --test-dir build -R 'DivTest' # subset (tests registered via gtest_discover_tests) +./build/bin/cpp_lab_project_unit_test --gtest_filter='DivTest.InvalidNumbers' +./scripts/gen_coverage_lcov.sh # or gen_coverage_gcovr.sh; CI uses -DENABLE_COVERAGE=ON +``` + +The test target compiles only the sources listed in `tests/CMakeLists.txt` (currently `src/DeleteMe.cpp`). Add any source under test to that list. + +## Static analysis & formatting + +CI (`.github/workflows/cpp-build-test-coverage.yml`, on push/PR to `master`, inside the `urboob21/cpp-lab:latest` Docker image) fails on cppcheck findings: + +```bash +cppcheck --enable=warning,style,performance,portability --inconclusive --inline-suppr --quiet --error-exitcode=1 ./src ./include +clang-tidy -p build -header-filter='^src/.*' $(find src -name "*.cpp") # not enforced in CI +clang-format -i # Google-based style (.clang-format) +``` + +The `.clang-tidy` naming rules are lower_case variables and namespaces, CamelCase types, and a trailing `_` on class members. Code also uses `kName` for constants and enumerators. + +## Architecture: the example registry + +The main executable is a plugin-style registry that fills itself during static initialization: + +- `include/IExample.h` is the interface. It has `group()`, `name()`, `description()`, and `execute()`. +- `include/ExampleRegistry.h` is a singleton map `group -> name -> factory`. The `REGISTER_EXAMPLE(Class)` macro at the bottom of each example `.cpp` defines a static registrar object that registers the class before `main` runs. +- `src/main.cpp` only renders the menu from the registry. Groups are shown sorted, but examples inside a group follow `unordered_map` order, so menu numbers are unstable and piping choices into stdin is unreliable. + +**Adding an example:** +1. Create a `.cpp` that puts its code in an anonymous namespace (often `namespace { namespace problem {...} namespace some_pattern {...} }`, each with its own `run()`). +2. Define an `IExample` subclass and call `REGISTER_EXAMPLE(ThatClass);`. +3. **Add the file to the module's source list** (`CORE_SOURCES`, `DP_SOURCES`, `SOCKET_SOURCES`, or `CONTROLLER_SOURCES` in `src//CMakeLists.txt`). There is no globbing, and a file missing from the list simply never shows up in the menu, with no error. + +Every module's sources link into the same `cpp_lab_project` binary. Keep helpers in anonymous namespaces and give the registered example class a unique name, because many files share names like `Array.cpp` and define their own `run()`. The `group()` string sets the menu category and doesn't always match the directory (for example, `function/operator_overloading/*` registers as `core/overloading_operator`). + +Use `LOG(msg)` / `LOG_S(a << b)` from `include/Logger.h` for output. Debug builds add a timestamp, file:line, and function; `NDEBUG` builds print plain lines. `version.h` is generated into `build/generated/` from `include/version.h.in`. + +## Docs + +`docs/README.md` indexes the per-module READMEs (`src/**/README.md`), which explain each topic and embed the draw.io UML diagrams from `docs/uml/`. When you add a design pattern example, update the matching README (and diagram if one exists). From 00aabbfed898e8a42bf3ff6d3a3f2854c66299a0 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Sat, 12 Sep 2026 08:25:52 +0700 Subject: [PATCH 08/20] Rewrite the lab around a self-registering example framework Adding an example used to mean writing an IExample subclass, repeating the group name, and remembering to add the file to a source list in CMake - a file that was missing from the list simply never appeared in the menu. Menu entries were also unordered, so the numbers changed between runs. Framework (include/lab, src/lab): - LAB_EXAMPLE("Name", "description" [, lab::kInteractive]) { ... } registers a file-local function before main() runs; the menu group is derived from the file's location, so ids like core/smart_pointer/Weak always match the tree. - lab::Registry keeps examples sorted and rejects duplicate ids and invalid names instead of failing silently. - Folder-style menu with search, plus a command line: --list, --run, --run-all, --list-ids, --plain, --mode, --version, --help. - Logger gained LOG_FUNC/LOG_SECTION, colors only on a terminal, and a working Release build (the old release path could not compile LOG(string_view)). Build: - Example modules are globbed with CONFIGURE_DEPENDS, so no CMake edit is needed for a new example; OBJECT libraries keep the static registrations. - Options: CPPLAB_BUILD_TESTS/DEMOS/GUI, CPPLAB_ENABLE_SANITIZERS, CPPLAB_WARNINGS_AS_ERRORS, ENABLE_COVERAGE. gtkmm-4.0 is now optional. - Warnings, coverage and sanitizer flags moved to a cpplab::options target so they no longer apply to GoogleTest. - One ctest smoke test per non-interactive example, discovered at test time. Examples: all ~100 rewritten with a "what you will learn" header, sections, fixed bugs (leaks in Command/Memento/Visitor, Builder's build() leaving a null product, iterator vs nullptr comparison in UnorderedMap, wrong labels in TemplateMethod, Dollars->Cents conversion, showpos demo, wcout/cout mixing, a global set_terminate that stayed installed) and modern C++ (unique_ptr ownership, ranges, concepts, std::format, jthread, variant). Examples no longer execute undefined behavior or leave files behind, so the whole suite passes under ASan/UBSan. Sockets: TCPServer/TCPClient now report errno through std::system_error, close their sockets exactly once, can stop a blocked accept(), and support port 0; the new LoopbackEcho example runs a server thread and a client in one process. Also: PID controller uses a unique_ptr pimpl and validates its parameters, tests cover the framework, the PID and GoogleTest/GoogleMock usage, and the GTK apps escape user text before building Pango markup. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LEsQ7AcLHafb1tAgosoJFz --- .clang-format | 2 +- .clang-tidy | 2 +- .cppcheck-suppressions | 53 ++ .gitignore | 27 +- CMakeLists.txt | 83 ++- cmake/CompilerOptions.cmake | 36 ++ cmake/CppLab.cmake | 49 ++ cmake/Dependencies.cmake | 57 +- cmake/DiscoverExampleTests.cmake | 38 ++ include/DeleteMe.h | 13 - include/ExampleRegistry.h | 45 -- include/IExample.h | 12 - include/Logger.h | 89 --- include/lab/CommandLine.h | 45 ++ include/lab/Example.h | 57 ++ include/lab/Logger.h | 67 +++ include/lab/Menu.h | 16 + include/lab/Registry.h | 80 +++ include/lab/Runner.h | 19 + include/lab/version.h.in | 17 + include/version.h.in | 6 - src/CMakeLists.txt | 63 ++- src/DeleteMe.cpp | 32 -- src/ap/CMakeLists.txt | 29 +- src/ap/README.md | 3 + src/ap/mvc/controller/Controller.cpp | 5 +- src/ap/mvc/view/DisplayWidget.cpp | 8 +- src/ap/mvc/view/DisplayWidget.h | 2 + src/ap/mvc/view/EditorWidget.cpp | 7 +- src/ap/mvc/view/EditorWidget.h | 2 +- src/ap/mvvm/view/DisplayWidget.cpp | 6 +- src/ap/mvvm/view/DisplayWidget.h | 2 + src/ap/mvvm/view/EditorWidget.cpp | 2 + src/ap/simple_ap.cpp | 5 +- src/controller/CMakeLists.txt | 8 - src/controller/README.md | 27 + src/controller/pid/PIDSim.cpp | 82 ++- src/controller/pid/pid.cpp | 142 ++--- src/controller/pid/pid.h | 48 +- src/core/CMakeLists.txt | 113 ---- src/core/basics/ControlFlow.cpp | 189 +++---- src/core/basics/Initialization.cpp | 190 ++++--- src/core/basics/Operations.cpp | 207 ++++--- src/core/basics/README.md | 43 ++ src/core/basics/TypeQualifier.cpp | 139 +++-- src/core/class/Binding.cpp | 125 +++-- src/core/class/Constructor.cpp | 467 ++++++++-------- src/core/class/Destructor.cpp | 156 ++++-- src/core/class/Encapsulation.cpp | 145 ++--- src/core/class/Friend.cpp | 162 +++--- src/core/class/README.md | 15 + src/core/class/Relationship.cpp | 253 ++++++--- src/core/class/RoleOfThreeFiveZero.cpp | 290 ---------- src/core/class/RuleOfThreeFiveZero.cpp | 236 ++++++++ src/core/class/SallowDeepCopying.cpp | 167 ------ src/core/class/ShallowDeepCopying.cpp | 151 ++++++ src/core/class/VirtualBase.cpp | 149 +++-- src/core/concurrency/ConditionVariable.cpp | 174 ++++-- src/core/concurrency/FuturePromise.cpp | 176 +++--- src/core/concurrency/README.md | 44 ++ src/core/concurrency/RaceCondition.cpp | 147 ++--- src/core/concurrency/SharingData.cpp | 109 ++-- src/core/concurrency/ThreadManagement.cpp | 255 +++++---- src/core/concurrency/Timing.cpp | 83 ++- src/core/container/README.md | 27 + src/core/container/adapter/PriorityQueue.cpp | 78 +++ src/core/container/adapter/Queue.cpp | 105 ++-- src/core/container/adapter/Stack.cpp | 114 ++-- src/core/container/associative/Set.cpp | 95 +++- src/core/container/sequence/Array.cpp | 198 ++++--- src/core/container/sequence/Deque.cpp | 98 ++-- src/core/container/sequence/Vector.cpp | 170 ++++-- src/core/container/unordered/UnorderedMap.cpp | 162 +++--- src/core/datatype/Array.cpp | 102 +++- src/core/datatype/Enum.cpp | 146 +++-- src/core/datatype/Fundamental.cpp | 177 ++++-- src/core/datatype/Pointer.cpp | 214 ++++---- src/core/datatype/README.md | 46 ++ src/core/datatype/Reference.cpp | 179 +++--- src/core/datatype/Struct.cpp | 145 +++-- src/core/datatype/TypeConVersions.cpp | 180 ------- src/core/datatype/TypeConversions.cpp | 178 ++++++ src/core/datatype/Union.cpp | 150 ++++-- src/core/datetime/README.md | 9 + src/core/datetime/Time.cpp | 110 ++-- src/core/exception/BasicHandle.cpp | 125 ++++- src/core/exception/README.md | 44 ++ src/core/exception/ThrowNoexcept.cpp | 132 +++-- src/core/filehandle/BinaryFileHandling.cpp | 213 ++++---- src/core/filehandle/Directory.cpp | 116 ++-- src/core/filehandle/FileIO.cpp | 181 +++---- src/core/filehandle/IOStream.cpp | 97 +++- src/core/filehandle/OutputFormatting.cpp | 180 ++++--- src/core/filehandle/README.md | 13 + src/core/filehandle/StringStream.cpp | 121 +++-- src/core/filehandle/TerminalColor.cpp | 192 +++---- src/core/function/FunctionPointer.cpp | 161 ++++-- src/core/function/Functional.cpp | 147 +++-- src/core/function/Lambda.cpp | 169 ++++-- src/core/function/README.md | 52 ++ .../AllocationOperator.cpp | 165 +++--- .../ArithmeticOperator.cpp | 157 +++--- .../AssignmentOperator.cpp | 110 ++-- .../ClassMemberAccessOperator.cpp | 127 +++-- .../ComparisonOperator.cpp | 131 +++-- .../FunctionCallOperator.cpp | 112 +++- .../operator_overloading/InDecOperator.cpp | 120 +++-- .../operator_overloading/StreamOperator.cpp | 114 ++-- .../SubscriptOperator.cpp | 109 +++- .../operator_overloading/TypeCast.cpp | 97 ++-- .../operator_overloading/UnaryOperator.cpp | 85 ++- src/core/linkage/External.cpp | 23 +- src/core/linkage/External.h | 22 +- src/core/linkage/Internal.cpp | 34 +- src/core/linkage/Linkage.cpp | 94 ++-- src/core/linkage/README.md | 39 ++ src/core/linkage/sharing/Sharing.cpp | 82 +-- .../linkage/sharing/external/constants.cpp | 18 +- src/core/linkage/sharing/external/constants.h | 24 +- src/core/linkage/sharing/inline/constants.h | 24 +- src/core/linkage/sharing/internal/constants.h | 23 +- src/core/smart_pointer/README.md | 39 ++ src/core/smart_pointer/Shared.cpp | 149 +++-- src/core/smart_pointer/Unique.cpp | 139 ++++- src/core/smart_pointer/Weak.cpp | 141 +++-- src/core/string/CString.cpp | 333 +++++------- src/core/string/README.md | 41 ++ src/core/string/StdString.cpp | 327 +++++------ src/core/string/StringFormatting.cpp | 158 +++--- src/core/utils/Algorithm.cpp | 169 ++++-- src/core/utils/Optional.cpp | 189 +++---- src/core/utils/README.md | 11 + src/core/utils/Regex.cpp | 136 +++-- src/core/utils/TypeTrait.cpp | 119 ---- src/core/utils/TypeTraits.cpp | 157 ++++++ src/core/utils/Variadic.cpp | 184 ++++--- src/demo/dlopen/CMakeLists.txt | 33 +- src/demo/dlopen/README.md | 61 ++- src/demo/dlopen/main.cpp | 64 ++- src/demo/dlopen/sample_app.cpp | 18 +- src/dp/CMakeLists.txt | 32 -- src/dp/behavioral/ChainOfCommand.cpp | 147 ----- src/dp/behavioral/ChainOfResponsibility.cpp | 125 +++++ src/dp/behavioral/Command.cpp | 246 +++++---- src/dp/behavioral/Iterator.cpp | 300 +++++------ src/dp/behavioral/Mediator.cpp | 255 ++++----- src/dp/behavioral/Memento.cpp | 287 ++++------ src/dp/behavioral/Observer.cpp | 293 +++++----- src/dp/behavioral/README.md | 4 + src/dp/behavioral/State.cpp | 200 ++++--- src/dp/behavioral/Strategy.cpp | 197 ++++--- src/dp/behavioral/TemplateMethod.cpp | 183 +++---- src/dp/behavioral/Visitor.cpp | 369 ++++++------- src/dp/creational/AbstractFactory.cpp | 204 +++---- src/dp/creational/Builder.cpp | 278 ++++++---- src/dp/creational/FactoryMethod.cpp | 180 +++---- src/dp/creational/Prototype.cpp | 174 +++--- src/dp/creational/README.md | 3 + src/dp/creational/Singleton.cpp | 131 +++-- src/dp/structural/Adapter.cpp | 213 ++++---- src/dp/structural/Bridge.cpp | 235 ++++---- src/dp/structural/Composite.cpp | 508 ++++-------------- src/dp/structural/Decorator.cpp | 201 +++---- src/dp/structural/Facade.cpp | 329 ++++-------- src/dp/structural/Flyweight.cpp | 432 +++++---------- src/dp/structural/Proxy.cpp | 199 +++---- src/dp/structural/README.md | 3 + src/embedded/startup.c | 2 +- src/lab/CMakeLists.txt | 37 ++ src/lab/CommandLine.cpp | 171 ++++++ src/lab/Logger.cpp | 108 ++++ src/lab/Menu.cpp | 255 +++++++++ src/lab/Registry.cpp | 166 ++++++ src/lab/Runner.cpp | 89 +++ src/main.cpp | 296 +++++----- src/socket/CMakeLists.txt | 11 - src/socket/README.md | 15 + src/socket/multiple_client/MultiTCPServer.cpp | 115 ++-- src/socket/multiple_client/MultiTCPServer.h | 31 +- src/socket/simple_tcp/LoopbackEcho.cpp | 61 +++ src/socket/simple_tcp/SimpleTCPClient.cpp | 86 +-- src/socket/simple_tcp/SimpleTCPServer.cpp | 59 +- src/socket/simple_tcp/TCPClient.cpp | 121 +++-- src/socket/simple_tcp/TCPClient.h | 40 +- src/socket/simple_tcp/TCPServer.cpp | 310 ++++++----- src/socket/simple_tcp/TCPServer.h | 87 +-- tests/CMakeLists.txt | 65 +-- tests/DeleteMeTest.cpp | 99 ---- tests/README.md | 18 + tests/controller/PidTest.cpp | 47 ++ tests/gtest_primer/Calculator.h | 30 ++ tests/gtest_primer/CalculatorTest.cpp | 65 +++ tests/gtest_primer/TurtleMockTest.cpp | 66 +++ tests/lab/CommandLineTest.cpp | 93 ++++ tests/lab/MenuRunnerTest.cpp | 108 ++++ tests/lab/RegistryTest.cpp | 129 +++++ tests/mock/MockTurtle.h | 11 +- tests/mock/Painter.h | 9 +- tests/mock/Turtle.h | 5 +- 199 files changed, 13242 insertions(+), 9566 deletions(-) create mode 100644 .cppcheck-suppressions create mode 100644 cmake/CompilerOptions.cmake create mode 100644 cmake/CppLab.cmake create mode 100644 cmake/DiscoverExampleTests.cmake delete mode 100644 include/DeleteMe.h delete mode 100644 include/ExampleRegistry.h delete mode 100644 include/IExample.h delete mode 100644 include/Logger.h create mode 100644 include/lab/CommandLine.h create mode 100644 include/lab/Example.h create mode 100644 include/lab/Logger.h create mode 100644 include/lab/Menu.h create mode 100644 include/lab/Registry.h create mode 100644 include/lab/Runner.h create mode 100644 include/lab/version.h.in delete mode 100644 include/version.h.in delete mode 100644 src/DeleteMe.cpp delete mode 100644 src/controller/CMakeLists.txt create mode 100644 src/controller/README.md delete mode 100644 src/core/CMakeLists.txt create mode 100644 src/core/basics/README.md delete mode 100644 src/core/class/RoleOfThreeFiveZero.cpp create mode 100644 src/core/class/RuleOfThreeFiveZero.cpp delete mode 100644 src/core/class/SallowDeepCopying.cpp create mode 100644 src/core/class/ShallowDeepCopying.cpp create mode 100644 src/core/concurrency/README.md create mode 100644 src/core/container/adapter/PriorityQueue.cpp create mode 100644 src/core/datatype/README.md delete mode 100644 src/core/datatype/TypeConVersions.cpp create mode 100644 src/core/datatype/TypeConversions.cpp create mode 100644 src/core/exception/README.md create mode 100644 src/core/function/README.md create mode 100644 src/core/linkage/README.md create mode 100644 src/core/smart_pointer/README.md create mode 100644 src/core/string/README.md delete mode 100644 src/core/utils/TypeTrait.cpp create mode 100644 src/core/utils/TypeTraits.cpp delete mode 100644 src/dp/CMakeLists.txt delete mode 100644 src/dp/behavioral/ChainOfCommand.cpp create mode 100644 src/dp/behavioral/ChainOfResponsibility.cpp create mode 100644 src/lab/CMakeLists.txt create mode 100644 src/lab/CommandLine.cpp create mode 100644 src/lab/Logger.cpp create mode 100644 src/lab/Menu.cpp create mode 100644 src/lab/Registry.cpp create mode 100644 src/lab/Runner.cpp delete mode 100644 src/socket/CMakeLists.txt create mode 100644 src/socket/simple_tcp/LoopbackEcho.cpp delete mode 100644 tests/DeleteMeTest.cpp create mode 100644 tests/controller/PidTest.cpp create mode 100644 tests/gtest_primer/Calculator.h create mode 100644 tests/gtest_primer/CalculatorTest.cpp create mode 100644 tests/gtest_primer/TurtleMockTest.cpp create mode 100644 tests/lab/CommandLineTest.cpp create mode 100644 tests/lab/MenuRunnerTest.cpp create mode 100644 tests/lab/RegistryTest.cpp diff --git a/.clang-format b/.clang-format index dc69733..274fb98 100644 --- a/.clang-format +++ b/.clang-format @@ -78,6 +78,6 @@ SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: c++11 +Standard: c++20 TabWidth: 4 UseTab: Never \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy index da93ae8..e010fe1 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -120,7 +120,7 @@ Checks: > # Turn all the warnings from the checks above into errors. WarningsAsErrors: "*" -HeaderFilterRegex: "(google/cloud/|generator/).*\\.h$" +HeaderFilterRegex: "(include/lab|src|tests)/.*\\.h$" CheckOptions: - { key: readability-identifier-naming.NamespaceCase, value: lower_case } diff --git a/.cppcheck-suppressions b/.cppcheck-suppressions new file mode 100644 index 0000000..41fd99d --- /dev/null +++ b/.cppcheck-suppressions @@ -0,0 +1,53 @@ +# cppcheck suppressions for cpp-lab + +# cppcheck --suppressions-list=.cppcheck-suppressions ... ./src ./include + +# Format: or : or :: + +# --- checks that are noise in teaching code --------------------------------- +# Example classes keep their member functions non-static so the examples read +# like ordinary code. +functionStatic +# Explicit loops are often the point of an example; the algorithm version is +# shown where it is the lesson (see core/utils/Algorithm). +useStlAlgorithm + +# --- intentional demonstrations --------------------------------------------- +# "the condition is always true/false" is exactly what these examples show +knownConditionTrueFalse:src/core/datatype/Fundamental.cpp +knownConditionTrueFalse:src/core/datatype/Reference.cpp +knownConditionTrueFalse:src/core/datatype/TypeConversions.cpp +knownConditionTrueFalse:src/core/string/StdString.cpp +knownConditionTrueFalse:src/core/utils/Optional.cpp +knownConditionTrueFalse:src/dp/structural/Adapter.cpp +# pointer/reference rebinding and re-assignment shown on purpose +redundantInitialization:src/core/datatype/Pointer.cpp +redundantAssignment:src/core/function/operator_overloading/AssignmentOperator.cpp +unreadVariable:src/core/datatype/TypeConversions.cpp +constVariableReference:src/core/datatype/Array.cpp +# at(3) must throw here, that is the lesson +containerOutOfBounds:src/core/container/sequence/Array.cpp +# std::deque guarantees that references survive push_front/push_back +invalidContainerReference:src/core/container/sequence/Deque.cpp +# placement new constructs into deliberately raw storage +legacyUninitvar:src/core/function/operator_overloading/AllocationOperator.cpp +# the null check after a move is the point of the example +nullPointerRedundantCheck:src/core/smart_pointer/Unique.cpp +accessMoved:src/core/class/Constructor.cpp +accessMoved:src/core/class/RuleOfThreeFiveZero.cpp +accessMoved:src/core/datatype/Reference.cpp +# classes that show what the compiler generates for them +noCopyConstructor:src/core/class/RuleOfThreeFiveZero.cpp +noOperatorEq:src/core/class/RuleOfThreeFiveZero.cpp +noCopyConstructor:src/core/class/ShallowDeepCopying.cpp +noOperatorEq:src/core/class/ShallowDeepCopying.cpp +noExplicitConstructor:src/core/class/Constructor.cpp +duplInheritedMember:src/core/class/Binding.cpp +virtualCallInConstructor:src/core/class/Binding.cpp +postfixOperator:src/core/function/operator_overloading/InDecOperator.cpp +# writing raw doubles is what binary file I/O looks like +invalidPointerCast:src/core/filehandle/BinaryFileHandling.cpp +# the plugin entry point must have main's signature +constParameter:src/demo/dlopen/sample_app.cpp +# startup code compares the linker-provided section symbols +comparePointers:src/embedded/startup.c diff --git a/.gitignore b/.gitignore index 5577478..34b72fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,21 @@ -*build -*private* -.vscode/ +# Build directories (build/, build-asan/, build-release/, src/embedded/build/, ...) +build/ +build-*/ + +# Coverage reports (scripts/gen_coverage_*.sh) +coverage_gcovr/ +coverage_lcov/ + +# CTest output when ctest runs from the repository root +Testing/ + +# Local-only files +/private/ +.cache/ +__pycache__/ *Identifier -*Testing* -coverage_gcovr -coverage_lcov + +# Editor settings: keep the shared VS Code launch and task configurations +.vscode/* !.vscode/launch.json !.vscode/tasks.json -.cache -__pycache__/ -*Testing* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f7cc84f..e225b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,65 +1,60 @@ -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.16) # Project metadata project(cpp_lab_project # ${PROJECT_NAME} VERSION 1.0.0 - DESCRIPTION "A C/C++ project uses CMake, GoogleTest, gcc, g++, cppcheck, and lcov, integrated with Docker and GitHub Actions for CI/CD." + DESCRIPTION "A C/C++ learning lab built with CMake, GoogleTest, gcc/g++, cppcheck and lcov, integrated with Docker and GitHub Actions for CI/CD." LANGUAGES CXX ) -# Output directories to build/bin -# Executables -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -# Shared libraries -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -# Static libraries -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) - -# Compiler and language configuration -# Require at least C++17 for GoogleTest and modern C++ features +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + +# ---------------------------------------------------------------------------------------- +# Options (pass with -D