diff --git a/docs/uml/patterns_structural_flyweight.drawio.svg b/docs/uml/patterns_structural_flyweight.drawio.svg new file mode 100644 index 0000000..af4c136 --- /dev/null +++ b/docs/uml/patterns_structural_flyweight.drawio.svg @@ -0,0 +1,4 @@ + + + +
Client
+ clientCode(ImageContext * s): type
Use
ImageContext 
// shared fields
- fileName: string
- width,height,dpi: int
- data: int*

// unique fields
- scale, opacity: float
+ ImageContext(type..)

+ display(): void
// We need 5 images loaded from the same source, each with different scale and opacity
// => creates 5 separate ImageContext instances
//    => wastes memory and CPU because each instance loads its own copy of the same image
//      even though only scale and opacity differ.

ImageContext* img1 = new ImageContext("file.svg", 0,1, 0,1);
...

ImageContext* img5 = new ImageContext("file.svg", 0.5, 0.5);


...
im1->display();
...
im5->display();

// => We should load the image data only once and share it across all ImageContext instances




Client
+ clientCode(ImageContext * s): type
Use
ImageContext 
// shared fields => ImageFlyweight
- m_flyweight: ImageFlyweight*

// unique fields
- scale, opacity: float
+ ImageContext(ImageFlyweight& i, float s, float o)

+ setScale(float s): void
+ setOpacity(float o): void
+ display(): void
// Using Flyweight: load the shared image data once, reuse it with different scale/opacity

ImageFlyweightFactory *imageRegister = new ImageFlyweightFactory();
ImageFlyweight flyweight = imageRegister->getImangeFlyweight("file.svg")
ImageContext *img1 = new ImageContext(flyweight, 0.1, 0.1);
...

ImageContext *img5 = new ImageContext(flyweight, 0.5, 0.5);


...
im1->display();
...
im5->display();

ImageFlyweight
// shared fields
- fileName: string
- width,height,dpi: int
- data: int*
+ ImageFlyweight(type ..)
+ display(float scale, float opacity): void
1
ImageFlyweightFactory
- m_imageFlyweights: map<string, ImageFlyweight>
+ getImageFlyweight(sharedFields): ImageFlyweight
1..*
getImageFlyweight:

if(m_imageFlyweight[sharedFields] == null){
m_imageFlyweight[sharedFields] = new ImageFlyweight(sharedField);
}
return m_imageFlyweight[sharedFields];
display:

this->m_flyweight->display(scale,opacity)
\ No newline at end of file diff --git a/src/core/basics/Operations.cpp b/src/core/basics/Operations.cpp index 06dfee2..7b0176b 100644 --- a/src/core/basics/Operations.cpp +++ b/src/core/basics/Operations.cpp @@ -52,7 +52,7 @@ void arithmeticOperator() // Increment cout << "a = " << a << "\n"; - int preIn = ++a; // increase a, return copy + int preIn = ++a; // increase a, return a cout << "preIn = " << preIn << "\n"; cout << "a = " << a << "\n"; diff --git a/src/core/datatypes/CReferences.cpp b/src/core/datatypes/CReferences.cpp index 533fea5..f690537 100644 --- a/src/core/datatypes/CReferences.cpp +++ b/src/core/datatypes/CReferences.cpp @@ -48,6 +48,45 @@ void references() std::cout << "By reference: " << b << '\n'; } +/** + * + a (lvalue) + | + | std::move(a) (rvalue reference) + V +source (rvalue reference parameter, but it's the lvalue inside the function) + | + | steal data + V + b + */ +namespace RvalueReference +{ + using namespace std; + + // Move-like function taking an rvalue reference as parameter + int copyConstructor(int &&x) + { + // Inside the function, x is a named lvalue that refers to the original object passed in (here, 'a') + int result = x * 10; // compute result based on x + x = 0; // reset the original object to 0 + return result; // return the computed result + } + + void run() + { + int a{10}; // original value + + // Call function with an rvalue reference using std::move + // std::move(a) casts a (lvalue) into an rvalue reference (int&&) + int b = copyConstructor(std::move(a)); + + cout << b << endl; // prints 100 + cout << a << endl; // prints 0, because x in the function referred to a and was reset + } + +} + // A struct that runs code when its object is created struct CReferences { @@ -58,6 +97,7 @@ struct CReferences << "Compound type: References\n"; references(); + RvalueReference::run(); } }; diff --git a/src/core/datatypes/class/CConstructors.cpp b/src/core/datatypes/class/CConstructors.cpp index aa6a19f..9d4ed72 100644 --- a/src/core/datatypes/class/CConstructors.cpp +++ b/src/core/datatypes/class/CConstructors.cpp @@ -158,8 +158,14 @@ namespace Delegate } // *4. Copy constructor: initialize an copy object with an existing object -// Generated if no copy/move constructor or destructor is declared +// Generated if no copy/move constructor or `destructor` is declared // Performs memberwise (shallow) copy +// * Copy constructor +// Object obj1 = obj2 +// Object obj1(obj2) +// Object obj1{obj2} +// * Copy assigment +// obj1 = obj1 namespace Copy { class ICConstructor // C++ will create a public implicit copy constructor for us if we do not provide a one. @@ -192,14 +198,27 @@ namespace Copy cout << "Called ICConstructor(int x, int y) : m_x{x}, m_y{y} \n"; } + // Implicit Copy constructor if there is no desconstrutor // using `default` keyword // ECConstructor(const ECConstructor &ref) = default; + // Explicit Copy constructor (a=ECConstructor(b)) ECConstructor(const ECConstructor &ref) : m_x{ref.m_x}, m_y{ref.m_x} { cout << "Called ECConstructor(const ECConstructor& ref) : m_x{ref.m_x}, m_y{ref.m_x} \n"; } + // Copy assigment (a=b) + ECConstructor &operator=(const ECConstructor &other) + { + if (this != &other) + { + this->m_x = other.m_x; + this->m_y = other.m_y; + } + return *this; + } + void print() const { cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; @@ -246,12 +265,90 @@ 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 +// *4. Move Constructor: +// Move constructor and move assignment transfer resource ownership +// from one object to another. This is usually cheaper than copying. +// A move constructor is implicitly generated only if no user-declared +// copy constructor, move constructor, or destructor exists. +namespace Move { + class Model // C++ will create a public implicit copy constructor for us if we do not provide a one. + { + private: + int m_x; + int m_y; + public: + Model(int x, int y) : m_x{x}, m_y{y} + { + cout << "Call constructor \n"; + } + + ~Model() + { + cout << "Call destructor \n"; + } + + Model(const Model &other) : Model(other.m_x, other.m_y) + { + cout << "Call copy constructor \n"; + } + + Model &operator=(const Model &other) + { + cout << "Call copy assigment \n"; + if (this != &other) + { + this->m_x = other.m_x; + this->m_y = other.m_y; + } + return *this; + } + + Model(Model &&source) noexcept : m_x(source.m_x), m_y(source.m_y) + { + cout << "Call move constructor\n"; + + // reset source + source.m_x = 0; + source.m_y = 0; + } + + Model &operator=(Model &&source) noexcept + { + cout << "Call move assigment \n"; + if (this != &source) + { + this->m_x = source.m_x; + this->m_y = source.m_y; + + // reset source + source.m_x = 0; + source.m_y = 0; + } + + return *this; + } + + void print() const + { + cout << "m_x = " << m_x << ", m_y = " << m_y << "\n"; + } + }; + + void constructers() + { + cout << "\n--- Move Constructor Examples ---\n"; + Model a(10, 20); + + cout << "\nCase 1: Model b = std::move(a);\n"; + Model b = std::move(a); // move constructor + + cout << "\nCase 2: Model c(5,6); c = std::move(b);\n"; + Model c(5, 6); + c = std::move(b); // move assignment + cout << "\n"; + } } struct CConstructorsAutoRuner @@ -262,6 +359,7 @@ struct CConstructorsAutoRuner Default::constructers(); Delegate::constructors(); Copy::constructors(); + Move::constructers(); } }; diff --git a/src/core/datatypes/class/SallowDeepCopying.cpp b/src/core/datatypes/class/SallowDeepCopying.cpp index 08c391d..28563a8 100644 --- a/src/core/datatypes/class/SallowDeepCopying.cpp +++ b/src/core/datatypes/class/SallowDeepCopying.cpp @@ -92,14 +92,14 @@ namespace 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; + // deep copy the ptr field // m_data is a pointer, so we need to deep copy it if it is non-null + // first we need to deallocate any value that this Model is holding! + delete ptr; if (source.ptr != nullptr) { // allocate memory for our copy @@ -128,12 +128,16 @@ namespace } // Copy constructor + // no need to check self-copy [if (this != &source)] + // because it cannot happen a(a); Model(const Model &source) { this->deepCopy(source); } // Assignment operator + // need to check self-copy [if (this != &source)] + // because it posible a = a; Model &operator=(const Model &source) { if (this != &source) diff --git a/src/patterns/structural/Composite.cpp b/src/patterns/structural/Composite.cpp index d75706d..ce2942e 100644 --- a/src/patterns/structural/Composite.cpp +++ b/src/patterns/structural/Composite.cpp @@ -267,7 +267,10 @@ namespace public: explicit FileSystem(const std::string &fileName) : _parent{nullptr}, _name{fileName} {} - virtual ~FileSystem() = default; + virtual ~FileSystem() + { + std::cout << "Destructor: " << this->getName() << "\n"; + } FileSystem *getParent() const { @@ -380,22 +383,21 @@ namespace // Delete folder should delete all children for (auto f : _children) { - if (f != nullptr) - { + std::cout << "Folder '" << this->getName() << "' deleted : " << f->getName() << "\n"; delete f; - f = nullptr; - } } } void add(FileSystem *fs) override { + std::cout << "Folder '" << this->getName() << "' added : " << fs->getName() << "\n"; _children.push_back(fs); fs->setParent(this); } void remove(FileSystem *fs) override { + std::cout << "Folder: " << this->getName() << "removed : " << fs->getName() << "\n"; _children.remove(fs); fs->setParent(nullptr); } @@ -511,23 +513,14 @@ namespace root->add(subFolder3); Client::clientCode(root); - // delete file1; - // file1 = nullptr; - - // delete file2; - // file2 = nullptr; - - // delete file3; - // file3 = nullptr; - - // delete subFolder1; - // subFolder1 = nullptr; + root->remove(file1); + delete file1; - // delete subFolder2; - // subFolder2 = nullptr; + root->remove(file2); + delete file2; - // delete subFolder3; - // subFolder3 = nullptr; + root->remove(file3); + delete file3; delete root; // deletes all files/subfolders inside recursively } @@ -541,7 +534,7 @@ struct CompositeAutoRuner CompositeAutoRuner() { std::cout << "\n--- Composite Pattern Example ---\n"; - Problem::run(); + // Problem::run(); CompositePattern::run(); } }; diff --git a/src/patterns/structural/Flyweight.cpp b/src/patterns/structural/Flyweight.cpp index e2a079f..eaa6a9d 100644 --- a/src/patterns/structural/Flyweight.cpp +++ b/src/patterns/structural/Flyweight.cpp @@ -1,9 +1,10 @@ -// +// Flyweight is a structural design pattern that lets you fit more objects +// into the available amount of RAM by sharing common parts of state between multiple objects +// instead of keeping all of the data in each object. // Appicability: -// (*) -// (**) - -// UML: docs/uml/patterns_structural_adapter.drawio.svg +// (*) when your program must support a huge number of objects which barely fit into available RAM, +// and the objects contain duplicate states which can be extracted and shared between multiple objects +// UML: docs/uml/patterns_structural_flyweight.drawio.svg #include #include @@ -19,18 +20,51 @@ namespace private: // Shared attrs std::string fileName_; - int width_, heigh_, dpi_; + int width_, height_, dpi_; int *data_; // Unique attrs float scale_; float opacity_; + void deepCopy(const ImageContext &other) + { + // normal field + this->fileName_ = other.fileName_; + this->width_ = other.width_; + this->height_ = other.height_; + this->dpi_ = other.dpi_; + this->scale_ = other.scale_; + this->opacity_ = other.opacity_; + + // ptr field + delete[] this->data_; + if (other.data_ != nullptr) + { + int size = this->width_ * this->height_; + this->data_ = new int[size]; + for (int i = 0; i < size; ++i) + { + this->data_[i] = other.data_[i]; + } + } + else + { + this->data_ = nullptr; + } + } + 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} + 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}, height_{h}, + dpi_{dpi}, + data_{new int[w * h]}, + scale_{scale}, opacity_{opacity} { std::cout << "Image: " << fileName_ << " created.\n"; } @@ -38,20 +72,24 @@ namespace // DESTRUCTOR virtual ~ImageContext() { - if (data_ != nullptr) - { - delete[] data_; - data_ = nullptr; - } + delete[] data_; } // COPY CONSTRUCTOR + ImageContext(const ImageContext &other) + { + this->deepCopy(other); + } // COPY ASSIGNMENT - - // MOVE CONSTRUCTOR - - // MOVE ASSIGNMENT + ImageContext &operator=(const ImageContext &other) + { + if (this != &other) + { + this->deepCopy(other); + } + return *this; + } void setScale(const float scale) { @@ -66,7 +104,7 @@ namespace void display() const { std::cout << "Image: " << fileName_ << " displayed. \n"; - std::cout << "\tWidth: " << width_ << " Height: " << heigh_ << " DPI: " << dpi_ << "\n"; + std::cout << "\tWidth: " << width_ << " Height: " << height_ << " DPI: " << dpi_ << "\n"; std::cout << "\tScale: " << scale_ << " Opacity: " << opacity_ << "\n"; } }; @@ -81,7 +119,7 @@ namespace void run() { - std::cout << "\n\n"; + std::cout << "\n\nProblem\n"; ImageContext **imgs = new ImageContext *[5]; for (int i = 0; i < 5; i++) { @@ -99,7 +137,6 @@ namespace // then delete array delete[] imgs; - imgs = nullptr; std::cout << "Size of objects: " << sizeof(ImageContext) * 5 << "\n"; } } @@ -117,19 +154,101 @@ namespace { private: std::string fileName_; - int width_, heigh_, dpi_; + int width_, height_, dpi_; int *data_; + void deepCopy(const ImageFlyweight &other) + { + // normal field + this->fileName_ = other.fileName_; + this->width_ = other.width_; + this->height_ = other.height_; + this->dpi_ = other.dpi_; + + // ptr field + delete[] this->data_; + if (other.data_ != nullptr) + { + int size = this->width_ * this->height_; + this->data_ = new int[size]; + for (int i = 0; i < size; ++i) + { + this->data_[i] = other.data_[i]; + } + } + else + { + this->data_ = nullptr; + } + } + 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]} {}; + explicit ImageFlyweight(const std::string &fileName, int w = 8, int h = 8, int dpi = 96) : fileName_{fileName}, width_{w}, height_{h}, dpi_{dpi}, data_{new int[w * h]} {}; ~ImageFlyweight() { - if (data_ != nullptr) + delete[] data_; + } + + // Note: + // [Copy constructor/assignment]: creates a new copy(shallow or deep) of the source object's data. + // -> Allocates new memory and duplicates all elements. + // -> Source object remains unchanged. + // + // [Move constructor/assignment[] ("steal"): transfers ownership of the source's resources + // -> No new memory allocation; destination uses the same memory as source. + // -> Source is reset to a safe empty state (nullptr, 0, etc.). + // ==> Much faster for large objects since no deep copy occurs. + + ImageFlyweight(const ImageFlyweight &other) + { + this->deepCopy(other); + } + + ImageFlyweight &operator=(const ImageFlyweight &other) + { + if (this != &other) + { + this->deepCopy(other); + } + return *this; + } + + ImageFlyweight(ImageFlyweight &&source) noexcept + : fileName_(std::move(source.fileName_)), + width_(source.width_), + height_(source.height_), + dpi_(source.dpi_), + data_(source.data_) + { + source.width_ = 0; + source.height_ = 0; + source.dpi_ = 0; + source.data_ = nullptr; + } + + ImageFlyweight &operator=(ImageFlyweight &&source) noexcept + { + if (this != &source) { + // stealSource + // free current resource delete[] data_; - data_ = nullptr; + + // move resource from source + fileName_ = std::move(source.fileName_); // move string + width_ = source.width_; + height_ = source.height_; + dpi_ = source.dpi_; + data_ = source.data_; + + // reset source + source.width_ = 0; + source.height_ = 0; + source.dpi_ = 0; + source.data_ = nullptr; } + return *this; } /** @@ -138,7 +257,7 @@ namespace 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 << "\tWidth: " << width_ << " Height: " << height_ << " DPI: " << dpi_ << "\n"; std::cout << "\tScale: " << scale << " Opacity: " << opacity << "\n"; } }; @@ -151,7 +270,7 @@ namespace class ImageFlyweightFactory { private: - std::unordered_map m_imageFlyweights; + std::unordered_map m_imageFlyweights; // unordered_map => need to role of five static std::string getKey(const std::string &name, int w, int h, int dpi) { return "key_" + name + std::to_string(w * h * dpi); @@ -211,7 +330,7 @@ namespace void run() { - std::cout << "\n\n"; + std::cout << "\n\nFlyweight\n"; // Regis the images to the factory ImageFlyweightFactory *imageRegister = new ImageFlyweightFactory(); ImageContext *img1 = new ImageContext(imageRegister->getImangeFlyweight("img1.svg"), 1, 0.1); @@ -242,8 +361,8 @@ struct FlyweightAutoRuner FlyweightAutoRuner() { std::cout << "\n--- Flyweight Pattern Example ---\n"; - // Problem::run(); - // FlyweightPattern::run(); + Problem::run(); + FlyweightPattern::run(); } };