From c18745904832d1095ef08f2a570d6fe79de8ae2c Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Thu, 13 Nov 2025 22:11:40 +0700 Subject: [PATCH 1/4] Update docs --- src/patterns/structural/Adapter.cpp | 6 +++++- src/patterns/structural/Bridge.cpp | 6 ++++++ src/patterns/structural/Composite.cpp | 8 ++++++++ src/patterns/structural/Proxy.cpp | 11 ++++++++++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/patterns/structural/Adapter.cpp b/src/patterns/structural/Adapter.cpp index 9174b22..822c8aa 100644 --- a/src/patterns/structural/Adapter.cpp +++ b/src/patterns/structural/Adapter.cpp @@ -1,4 +1,8 @@ -// Adapters make legacy code work with modern classes. +// 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 diff --git a/src/patterns/structural/Bridge.cpp b/src/patterns/structural/Bridge.cpp index ea974bd..e2adc81 100644 --- a/src/patterns/structural/Bridge.cpp +++ b/src/patterns/structural/Bridge.cpp @@ -1,6 +1,12 @@ // 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 diff --git a/src/patterns/structural/Composite.cpp b/src/patterns/structural/Composite.cpp index 0c1ee60..af62971 100644 --- a/src/patterns/structural/Composite.cpp +++ b/src/patterns/structural/Composite.cpp @@ -1,3 +1,11 @@ +// Composite is a structural design pattern +// that lets you compose objects into "tree structures" +// and then work with these structures as if they were individual objects. +// Appicability: +// (*) when you have to implement a tree-like object structure. +// (**) when you want the client code to treat both simple and complex elements uniformly. +// UML: docs/uml/patterns_structural_Composite.drawio.svg + #include #include #include diff --git a/src/patterns/structural/Proxy.cpp b/src/patterns/structural/Proxy.cpp index be12cc4..3095e4a 100644 --- a/src/patterns/structural/Proxy.cpp +++ b/src/patterns/structural/Proxy.cpp @@ -1,5 +1,14 @@ -// Proxy is a structural design pattern that provides an object that acts as a substitute for a real service object used by a client. +// 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 From c4a66820fe6fef21be8af4cb62a9ce1fe032ea17 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Fri, 14 Nov 2025 09:46:45 +0700 Subject: [PATCH 2/4] TODO: fix the fault segment --- src/patterns/structural/Composite.cpp | 36 +++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/patterns/structural/Composite.cpp b/src/patterns/structural/Composite.cpp index af62971..d75706d 100644 --- a/src/patterns/structural/Composite.cpp +++ b/src/patterns/structural/Composite.cpp @@ -1,4 +1,4 @@ -// Composite is a structural design pattern +// Composite is a structural design pattern // that lets you compose objects into "tree structures" // and then work with these structures as if they were individual objects. // Appicability: @@ -284,6 +284,11 @@ namespace return this->_name; } + virtual int size() const + { + return 1; + } + void setName(const std::string &name) { this->_name = name; @@ -373,10 +378,13 @@ namespace ~Folder() { // Delete folder should delete all children - for (FileSystem *f : _children) + for (auto f : _children) { if (f != nullptr) + { delete f; + f = nullptr; + } } } @@ -397,7 +405,7 @@ namespace std::cout << "Open Folder: " << this->getName() << "\n"; } - int size() const + int size() const override { int size = static_cast(_children.size()); std::for_each(_children.begin(), _children.end(), [&size](FileSystem *fs) @@ -474,7 +482,7 @@ namespace { void clientCode(const FileSystem *fs) { - std::cout << "File name: " << fs->getName() << "\n"; + std::cout << "File name: " << fs->getName() << ", size: " << fs->size() << "\n"; fs->open(); } } @@ -492,6 +500,7 @@ namespace root->add(file1); root->add(file2); root->add(file3); + Client::clientCode(file1); Client::clientCode(root); Folder *subFolder1 = new Folder("subFolder1"); @@ -500,9 +509,26 @@ namespace root->add(subFolder1); root->add(subFolder2); root->add(subFolder3); - Client::clientCode(root); + // delete file1; + // file1 = nullptr; + + // delete file2; + // file2 = nullptr; + + // delete file3; + // file3 = nullptr; + + // delete subFolder1; + // subFolder1 = nullptr; + + // delete subFolder2; + // subFolder2 = nullptr; + + // delete subFolder3; + // subFolder3 = nullptr; + delete root; // deletes all files/subfolders inside recursively } From 3a01e48e1632a37691c96b5dba3fdd0c8bb2d471 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Fri, 14 Nov 2025 15:21:21 +0700 Subject: [PATCH 3/4] Add flyweight pattern TODO: Need to fix the error with sallow copy --- CMakeLists.txt | 35 ++-- src/patterns/structural/Flyweight.cpp | 250 ++++++++++++++++++++++++++ 2 files changed, 268 insertions(+), 17 deletions(-) create mode 100644 src/patterns/structural/Flyweight.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ff9b7f..fca9bc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,23 +76,24 @@ set(APP_HEADERS # Core source files set(APP_SOURCES "src/DeleteMe.cpp" - "src/core/basics/InitializeVariable.cpp" - "src/core/basics/Operations.cpp" - "src/core/basics/ControlFlow.cpp" - "src/core/datatypes/Fundamental.cpp" - "src/core/datatypes/CArray.cpp" - "src/core/datatypes/CReferences.cpp" - "src/core/datatypes/CPointers.cpp" - "src/core/datatypes/CEnum.cpp" - "src/core/datatypes/CStruct.cpp" - "src/core/datatypes/CUnion.cpp" - "src/core/datatypes/TypeConVersions.cpp" - "src/core/datatypes/class/CConstructors.cpp" - "src/core/datatypes/class/CDestructors.cpp" - "src/patterns/structural/Adapter.cpp" - "src/patterns/structural/Bridge.cpp" - "src/patterns/structural/Proxy.cpp" - "src/patterns/structural/Composite.cpp" + # "src/core/basics/InitializeVariable.cpp" + # "src/core/basics/Operations.cpp" + # "src/core/basics/ControlFlow.cpp" + # "src/core/datatypes/Fundamental.cpp" + # "src/core/datatypes/CArray.cpp" + # "src/core/datatypes/CReferences.cpp" + # "src/core/datatypes/CPointers.cpp" + # "src/core/datatypes/CEnum.cpp" + # "src/core/datatypes/CStruct.cpp" + # "src/core/datatypes/CUnion.cpp" + # "src/core/datatypes/TypeConVersions.cpp" + # "src/core/datatypes/class/CConstructors.cpp" + # "src/core/datatypes/class/CDestructors.cpp" + # "src/patterns/structural/Adapter.cpp" + # "src/patterns/structural/Bridge.cpp" + # "src/patterns/structural/Proxy.cpp" + # "src/patterns/structural/Composite.cpp" + "src/patterns/structural/Flyweight.cpp" ) # Test files diff --git a/src/patterns/structural/Flyweight.cpp b/src/patterns/structural/Flyweight.cpp new file mode 100644 index 0000000..e2a079f --- /dev/null +++ b/src/patterns/structural/Flyweight.cpp @@ -0,0 +1,250 @@ +// +// Appicability: +// (*) +// (**) + +// UML: docs/uml/patterns_structural_adapter.drawio.svg + +#include +#include +#include + +namespace +{ + namespace Problem + { + class ImageContext + { + protected: + private: + // Shared attrs + std::string fileName_; + int width_, heigh_, dpi_; + int *data_; + + // Unique attrs + float scale_; + float opacity_; + + public: + // Role of five + // CONSTRUCTOR + explicit ImageContext(const std::string &fileName, int w = 8, int h = 8, int dpi = 96, float scale = 1, float opacity = 1) : fileName_{fileName}, width_{w}, heigh_{h}, dpi_{dpi}, data_{new int[w * h]}, + scale_{scale}, opacity_{opacity} + { + std::cout << "Image: " << fileName_ << " created.\n"; + } + + // DESTRUCTOR + virtual ~ImageContext() + { + if (data_ != nullptr) + { + delete[] data_; + data_ = nullptr; + } + } + + // COPY CONSTRUCTOR + + // COPY ASSIGNMENT + + // MOVE CONSTRUCTOR + + // MOVE ASSIGNMENT + + void setScale(const float scale) + { + this->scale_ = scale; + } + + void setOpacity(const float opacity) + { + this->opacity_ = opacity; + } + + void display() const + { + std::cout << "Image: " << fileName_ << " displayed. \n"; + std::cout << "\tWidth: " << width_ << " Height: " << heigh_ << " DPI: " << dpi_ << "\n"; + std::cout << "\tScale: " << scale_ << " Opacity: " << opacity_ << "\n"; + } + }; + + namespace Client + { + void clientCode(const ImageContext *img) + { + img->display(); + } + } + + void run() + { + std::cout << "\n\n"; + ImageContext **imgs = new ImageContext *[5]; + for (int i = 0; i < 5; i++) + { + imgs[i] = new ImageContext("img1.svg"); + imgs[i]->setOpacity(1 - (float)1 / (i + 1)); + imgs[i]->setScale((float)1 / (i + 1)); + Client::clientCode(imgs[i]); + } + + // delete objects first + for (int i = 0; i < 5; i++) + { + delete imgs[i]; + } + + // then delete array + delete[] imgs; + imgs = nullptr; + std::cout << "Size of objects: " << sizeof(ImageContext) * 5 << "\n"; + } + } + + namespace FlyweightPattern + { + + /** + * The Flyweight stores a common portion of the state (also called intrinsic + * state) that belongs to multiple real business entities. + * The Flyweight accepts the rest of the state (extrinsic state, unique for each entity) via its + * method parameters. + */ + class ImageFlyweight + { + private: + std::string fileName_; + int width_, heigh_, dpi_; + int *data_; + + public: + explicit ImageFlyweight(const std::string &fileName, int w = 8, int h = 8, int dpi = 96) : fileName_{fileName}, width_{w}, heigh_{h}, dpi_{dpi}, data_{new int[w * h]} {}; + + ~ImageFlyweight() + { + if (data_ != nullptr) + { + delete[] data_; + data_ = nullptr; + } + } + + /** + * Display the image at give scale, opacity + */ + void display(const float &scale, const float &opacity) const + { + std::cout << "Image: " << fileName_ << " displayed. \n"; + std::cout << "\tWidth: " << width_ << " Height: " << heigh_ << " DPI: " << dpi_ << "\n"; + std::cout << "\tScale: " << scale << " Opacity: " << opacity << "\n"; + } + }; + + /** + * The Flyweight Factory manages a pool of existing flyweights. + * With the factory, clients don’t create flyweights directly. Instead, they call the factory, passing it bits of the intrinsic state of the desired flyweight. + * The factory looks over previously created flyweights and either returns an existing one that matches search criteria or creates a new one if nothing is found. + */ + class ImageFlyweightFactory + { + private: + std::unordered_map m_imageFlyweights; + static std::string getKey(const std::string &name, int w, int h, int dpi) + { + return "key_" + name + std::to_string(w * h * dpi); + } + + public: + ImageFlyweight &getImangeFlyweight(const std::string &fileName, int w = 8, int h = 8, int dpi = 96) + { + std::string key = getKey(fileName, w, h, dpi); + std::cout << "Key: " << key << "\n"; + if (this->m_imageFlyweights.find(key) == this->m_imageFlyweights.end()) + { + std::cout << "ImageFlyweightFactory: Can't find a image flyweight, creating new one.\n"; + this->m_imageFlyweights.insert(std::make_pair(key, ImageFlyweight(fileName, w, h, dpi))); + } + else + { + std::cout << "ImageFlyweightFactory: Reusing existing image flyweight.\n"; + } + return this->m_imageFlyweights.at(key); + } + }; + + /** + * The Context class contains the extrinsic state, unique across all original objects. + * When a context is paired with one of the flyweight objects, it represents the full state of the original object. + */ + class ImageContext + { + private: + float scale_; + float opacity_; + ImageFlyweight *m_flyweight; + + public: + ImageContext(ImageFlyweight &imageFlyweight, const float &s, const float &o) : scale_{s}, opacity_{o}, m_flyweight{&imageFlyweight} + { + } + + void display() const + { + this->m_flyweight->display(scale_, opacity_); + } + }; + + /** + * The Client calculates or stores the extrinsic state of flyweights. + * From the client’s perspective, a flyweight is a template object which can be configured at runtime by passing some contextual data into parameters of its methods. + */ + namespace Client + { + void clientCode(const ImageContext *img) + { + img->display(); + } + } + + void run() + { + std::cout << "\n\n"; + // Regis the images to the factory + ImageFlyweightFactory *imageRegister = new ImageFlyweightFactory(); + ImageContext *img1 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.1); + Client::clientCode(img1); + ImageContext *img2 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.2); + Client::clientCode(img2); + ImageContext *img3 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.3); + Client::clientCode(img3); + ImageContext *img4 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.4); + Client::clientCode(img4); + ImageContext *img5 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.5); + Client::clientCode(img5); + std::cout << "Size of objects: " << sizeof(ImageFlyweightFactory) + sizeof(ImageContext) * 5 << "\n"; + + delete img1; + delete img2; + delete img3; + delete img4; + delete img5; + delete imageRegister; + } + + } +} + +struct FlyweightAutoRuner +{ + FlyweightAutoRuner() + { + std::cout << "\n--- Flyweight Pattern Example ---\n"; + // Problem::run(); + // FlyweightPattern::run(); + } +}; + +static FlyweightAutoRuner instance; \ No newline at end of file From 44d6e6ddf46c89417e313581b2211e766617099b Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Sat, 15 Nov 2025 18:54:26 +0700 Subject: [PATCH 4/4] Add Rule of *, shallow/deep copying and update doc --- CMakeLists.txt | 36 +- src/core/datatypes/class/CConstructors.cpp | 16 +- src/core/datatypes/class/CDestructors.cpp | 3 + .../datatypes/class/RoleOfThreeFiveZero.cpp | 307 ++++++++++++++++++ .../datatypes/class/SallowDeepCopying.cpp | 184 +++++++++++ 5 files changed, 528 insertions(+), 18 deletions(-) create mode 100644 src/core/datatypes/class/RoleOfThreeFiveZero.cpp create mode 100644 src/core/datatypes/class/SallowDeepCopying.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fca9bc4..7c2082c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,23 +76,25 @@ set(APP_HEADERS # Core source files set(APP_SOURCES "src/DeleteMe.cpp" - # "src/core/basics/InitializeVariable.cpp" - # "src/core/basics/Operations.cpp" - # "src/core/basics/ControlFlow.cpp" - # "src/core/datatypes/Fundamental.cpp" - # "src/core/datatypes/CArray.cpp" - # "src/core/datatypes/CReferences.cpp" - # "src/core/datatypes/CPointers.cpp" - # "src/core/datatypes/CEnum.cpp" - # "src/core/datatypes/CStruct.cpp" - # "src/core/datatypes/CUnion.cpp" - # "src/core/datatypes/TypeConVersions.cpp" - # "src/core/datatypes/class/CConstructors.cpp" - # "src/core/datatypes/class/CDestructors.cpp" - # "src/patterns/structural/Adapter.cpp" - # "src/patterns/structural/Bridge.cpp" - # "src/patterns/structural/Proxy.cpp" - # "src/patterns/structural/Composite.cpp" + "src/core/basics/InitializeVariable.cpp" + "src/core/basics/Operations.cpp" + "src/core/basics/ControlFlow.cpp" + "src/core/datatypes/Fundamental.cpp" + "src/core/datatypes/CArray.cpp" + "src/core/datatypes/CReferences.cpp" + "src/core/datatypes/CPointers.cpp" + "src/core/datatypes/CEnum.cpp" + "src/core/datatypes/CStruct.cpp" + "src/core/datatypes/CUnion.cpp" + "src/core/datatypes/TypeConVersions.cpp" + "src/core/datatypes/class/CConstructors.cpp" + "src/core/datatypes/class/CDestructors.cpp" + "src/core/datatypes/class/SallowDeepCopying.cpp" + "src/core/datatypes/class/RoleOfThreeFiveZero.cpp" + "src/patterns/structural/Adapter.cpp" + "src/patterns/structural/Bridge.cpp" + "src/patterns/structural/Proxy.cpp" + "src/patterns/structural/Composite.cpp" "src/patterns/structural/Flyweight.cpp" ) diff --git a/src/core/datatypes/class/CConstructors.cpp b/src/core/datatypes/class/CConstructors.cpp index f8c00cd..aa6a19f 100644 --- a/src/core/datatypes/class/CConstructors.cpp +++ b/src/core/datatypes/class/CConstructors.cpp @@ -55,7 +55,10 @@ namespace InitializerList } } -// *2. Default constructor: is a constructor that accepts no arguments. +// *2. Default constructor: +// It is a constructor that accepts no arguments. +// Generated if no constructors are declared +// Initializes members: basic types uninitialized, class types call their default constructor namespace Default { class UConstructors @@ -115,6 +118,7 @@ namespace Default } // *3. Delegate constructor: allow to delegate initialization to another constructor +// Never generated automatically namespace Delegate { class CConstructor @@ -154,6 +158,8 @@ namespace Delegate } // *4. Copy constructor: initialize an copy object with an existing object +// Generated if no copy/move constructor or destructor is declared +// Performs memberwise (shallow) copy namespace Copy { class ICConstructor // C++ will create a public implicit copy constructor for us if we do not provide a one. @@ -240,6 +246,14 @@ namespace Copy } } +// *4. Copy constructor: initialize an copy object with an existing object +// Generated if no copy/move constructor or destructor is declared +// Performs memberwise (shallow) copy +namespace Copy +{ + +} + struct CConstructorsAutoRuner { CConstructorsAutoRuner() diff --git a/src/core/datatypes/class/CDestructors.cpp b/src/core/datatypes/class/CDestructors.cpp index 333925b..2baebde 100644 --- a/src/core/datatypes/class/CDestructors.cpp +++ b/src/core/datatypes/class/CDestructors.cpp @@ -2,6 +2,9 @@ using namespace std; // *1. Basic Destructor +// Generated if no destructor is declared +// Calls destructors of members automatically +// Does not free dynamically allocated memory unless you write it namespace Basic { class CDestructors diff --git a/src/core/datatypes/class/RoleOfThreeFiveZero.cpp b/src/core/datatypes/class/RoleOfThreeFiveZero.cpp new file mode 100644 index 0000000..8fd7cd4 --- /dev/null +++ b/src/core/datatypes/class/RoleOfThreeFiveZero.cpp @@ -0,0 +1,307 @@ +#include +#include +#include + +namespace +{ + namespace Problem + { + class Model + { + private: + char *cstring; + + public: + explicit Model(const char *s = "") : cstring{nullptr} + { + if (s) + { + cstring = new char[std::strlen(s) + 1]; // allocate + std::strcpy(cstring, s); // populate + } + } + + // I. destructor + ~Model() + { + // delete[] cstring; // [P2] Double call here + std::cout << "Deleted cstring at: " << static_cast(cstring) << "\n"; + } + + // [P] + // What happen if we didn't define + // II. copy constructor + // III. copy assignment + + // Helper functions + const char *c_str() const // accessor + { + return cstring; + } + + void set_first_char(char ch) + { + if (cstring) + cstring[0] = ch; + } + }; + + void run() + { + { + std::cout << "\n\nProblem\n"; + Model str1{"str1"}; + Model str2 = str1; // shallow copy with "assigment = " + std::cout << "Before change:\n"; + std::cout << " str1 = " << str1.c_str() << "\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + std::cout << "\n[P1] Shallow copy (shared pointer).\n"; + str2.set_first_char('X'); + std::cout << "\nAfter modifying str2.set_first_char('X'):\n"; + std::cout << " str1 = " << str1.c_str() << " <-- also changed!\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + std::cout << "\n[P2] Decontructor issue when releasing the shared pointer.\n"; + } + } + } + + // Rule of Three: define + // - destructor + // - copy constructor + // - copy assignment + namespace RoleOfThree + { + class Model + { + private: + char *cstring; + + public: + explicit Model(const char *s = "") : cstring{nullptr} + { + if (s) + { + cstring = new char[std::strlen(s) + 1]; // allocate + std::strcpy(cstring, s); // populate + } + } + + // I. destructor + ~Model() + { + delete[] cstring; // [P2] Double call here + std::cout << "Deleted cstring at: " << static_cast(cstring) << "\n"; + } + + // II. copy constructor + Model(const Model &other) : Model(other.cstring) {} + + // III. copy assignment + Model &operator=(const Model &other) + { + Model temp(other); + std::swap(cstring, temp.cstring); + return *this; + } + + // Helper functions + const char *c_str() const // accessor + { + return cstring; + } + + void set_first_char(char ch) + { + if (cstring) + cstring[0] = ch; + } + }; + + void run() + { + { + std::cout << "\n\nRole of three\n"; + Model str1{"str1"}; + Model str2 = str1; // shallow copy with "assigment = " + 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() << " <-- not changed!\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + // [P] Role of three not cover this std::move + Model str{"there"}; + Model str_move = std::move(str); + std::cout << "\nAfter moving str into str_move:\n"; + std::cout << " str.c_str() = " << (str.c_str() ? str.c_str() : "null") << "\n"; + std::cout << " str_move.c_str() = " << (str_move.c_str() ? str_move.c_str() : "null") << "\n"; + } + } + } + + // Rule of Five: Rule of Three and define + // - move constructor + // - move assignment + namespace RoleOfFive + { + class Model + { + private: + char *cstring; + + public: + explicit Model(const char *s = "") : cstring{nullptr} + { + if (s) + { + cstring = new char[std::strlen(s) + 1]; // allocate + std::strcpy(cstring, s); // populate + } + } + + // I. destructor + ~Model() + { + delete[] cstring; // [P2] Double call here + std::cout << "Deleted cstring at: " << static_cast(cstring) << "\n"; + } + + // II. copy constructor + Model(const Model &other) : Model(other.cstring) {} + + // III. copy assignment + Model &operator=(const Model &other) + { + Model temp(other); + std::swap(cstring, temp.cstring); + return *this; + } + + // IV. move constructor + // noexcept is a specifier that indicates a function will not throw exceptions. + Model(Model &&other) noexcept : cstring(std::exchange(other.cstring, nullptr)) {}; + + // V. move assignment + Model &operator=(Model &&other) noexcept + { + std::swap(cstring, other.cstring); + return *this; + } + + // Helper functions + const char *c_str() const // accessor + { + return cstring; + } + + void set_first_char(char ch) + { + if (cstring) + cstring[0] = ch; + } + }; + + void run() + { + { + std::cout << "\n\nRole of Five\n"; + Model str1{"str1"}; + Model str2 = str1; // shallow copy with "assigment = " + 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() << " <-- not changed!\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + // Role of five covers this std::move + Model str{"there"}; + Model str_move = std::move(str); + std::cout << "\nAfter moving str into str_move:\n"; + std::cout << " str.c_str() = " << (str.c_str() ? str.c_str() : "null") << "\n"; + std::cout << " str_move.c_str() = " << (str_move.c_str() ? str_move.c_str() : "null") << "\n"; + } + } + } + + // Rule of Zero: use RAII (std::string, std::vector, smart pointers,...), no need to define any special functions except constructor + namespace RoleOfZero + { + class base_of_five_defaults + { + public: + base_of_five_defaults(const base_of_five_defaults &) = default; + base_of_five_defaults(base_of_five_defaults &&) = default; + base_of_five_defaults &operator=(const base_of_five_defaults &) = default; + base_of_five_defaults &operator=(base_of_five_defaults &&) = default; + virtual ~base_of_five_defaults() = default; + }; + + class Model + { + private: + std::string cstring; + // int* ptr; -> std::unique_ptr ptr; (exclusive ownership) + // int* arr; -> std::vector arr; (automatic array management) + // int* shared; -> std::shared_ptr shared; (shared ownership) + + public: + explicit Model(const std::string &str) : cstring{str} + { + } + + // Helper functions + const std::string c_str() const // accessor + { + return cstring; + } + + void set_first_char(char ch) + { + cstring.at(0) = ch; + } + }; + + void run() + { + { + std::cout << "\n\nRole of Zero\n"; + Model str1{"str1"}; + Model str2 = str1; // shallow copy with "assigment = " + 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() << " <-- not changed!\n"; + std::cout << " str2 = " << str2.c_str() << "\n"; + + Model str{"there"}; + Model str_move = std::move(str); + std::cout << "\nAfter moving str into str_move:\n"; + std::cout << " str.c_str() = " << str.c_str() << "\n"; + std::cout << " str_move.c_str() = " << str_move.c_str() << "\n"; + } + } + } +} +struct RoleOfThreeFiveZeroAutoRuner +{ + // Virtual default destructor: Does not break Rule of Three, Five, or Zero + RoleOfThreeFiveZeroAutoRuner() + { + Problem::run(); + RoleOfThree::run(); + RoleOfFive::run(); + RoleOfZero::run(); + } +}; + +static RoleOfThreeFiveZeroAutoRuner instance; \ No newline at end of file diff --git a/src/core/datatypes/class/SallowDeepCopying.cpp b/src/core/datatypes/class/SallowDeepCopying.cpp new file mode 100644 index 0000000..b5bacef --- /dev/null +++ b/src/core/datatypes/class/SallowDeepCopying.cpp @@ -0,0 +1,184 @@ +#include + +namespace +{ + namespace Shallow + { + class Model + { + private: + int m_x{0}, m_y{1}; + int *ptr; + + public: + // Default constructor: Model a = Model(1,2) + explicit Model(int x, int y, int z) : m_x{x}, m_y{y}, ptr{nullptr} + { + ptr = new int; + *ptr = z; + } + + ~Model() + { + // [P1] + // Shallow copying makes both objects use the same pointer. + // If one object deletes the pointer, the other object now + // points to invalid memory. + // delete ptr; // Commented out on purpose for demo + } + + void changePtr(int value) + { + if (ptr != nullptr) + { + *ptr = value; + } + } + + int getPtr() + { + if (ptr != nullptr) + { + return *ptr; + } + } + + // Implicit copy constructor: Model a{b}; + // Model(const Model &f) : m_x{f.m_x}, m_y{f.m_y} {} + + // Implicit assignment operator (like the way compiler gen this function): Model a = b; + // Model &operator=(const Model &f) + // { + // // self-assignment guard + // if (this == &f) + // { + // return *this; + // } + + // // do the copy + // m_x = f.m_x; + // m_y = f.m_y; + + // // return the existing object so we can chain this operator + // return *this; + // } + }; + + void run() + { + Model obj1 = Model(1, 2, 3); + Model obj2 = obj1; + obj2.changePtr(30); + // [P2] + std::cout << "\n=== Shallow Copy Demo ===\n"; + std::cout << "obj1.ptr = " << obj1.getPtr() << "\n"; + std::cout << "obj2.ptr = " << obj2.getPtr() << "\n"; + } + + } + + // To do a deep copy on any non-null pointers being copied + // Requires that we write our own `copy constructors` and `overloaded assignment operators`. + namespace DeepCopying + { + class Model + { + private: + int m_x{0}, m_y{1}; + int *ptr; + + void deepCopy(const Model &source) + { + // first we need to deallocate any value that this Model is holding! + delete ptr; + + // sallow copy the normal fields + m_x = source.m_x; + m_y = source.m_y; + + // m_data is a pointer, so we need to deep copy it if it is non-null + if (source.ptr != nullptr) + { + // allocate memory for our copy + ptr = new int; + // do the copy + *ptr = *source.ptr; + } + else + { + ptr = nullptr; + } + } + + public: + // Constructor + explicit Model(int x, int y, int z) : m_x{x}, m_y{y}, ptr{nullptr} + { + ptr = new int; + *ptr = z; + } + + // Destructor + ~Model() + { + delete ptr; + } + + // Copy constructor + Model(const Model &source) + { + this->deepCopy(source); + } + + // Assignment operator + Model &operator=(const Model &source) + { + if (this != &source) + { + // now do the deep copy + this->deepCopy(source); + } + return *this; + } + + void changePtr(int value) + { + if (ptr != nullptr) + { + *ptr = value; + } + } + + int getPtr() + { + if (ptr != nullptr) + { + return *ptr; + } + } + }; + + void run() + { + Model obj1 = Model(1, 2, 3); + Model obj2 = obj1; + obj2.changePtr(30); + + std::cout << "\n=== Deep Copy Demo ===\n"; + std::cout << "obj1.ptr = " << obj1.getPtr() << "\n"; + std::cout << "obj2.ptr = " << obj2.getPtr() << "\n"; + } + + } +} + +struct ShallowDeepCopying +{ + ShallowDeepCopying() + { + Shallow::run(); + DeepCopying::run(); + } +}; + +static ShallowDeepCopying instance; \ No newline at end of file