Advertisement
irmantas_radavicius

Untitled

Apr 10th, 2025 (edited)
296
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 8.47 KB | Source Code | 0 0
  1. // 2025-04-10
  2.  
  3. #include <string>
  4. #include <iostream>
  5. #include <sstream>
  6. #include <exception>
  7. #include <stdexcept>
  8.  
  9.  
  10. using namespace std;
  11.  
  12. // create a class to represent a point on a plane
  13. class Point {
  14.    
  15.     // two fields saved as pointers - to have values in dynamic memory
  16.     // and to learn how to do it for bigger objects (you don't need this
  17.     // for simple/primitive types or for String in Java,
  18.     // because it's immutable
  19.    
  20.     double *x, *y;
  21.  
  22. public:
  23.    
  24.     // delegate the responsibility to a private method called 'init'
  25.     // to avoid code duplication; no parametes - nothing to do
  26.    
  27.     Point(){
  28.         init();
  29.     }
  30.    
  31.     // two parameters - means we call setters for each one
  32.     Point(double x, double y){
  33.         init();
  34.         setX(x);
  35.         setY(y);
  36.     }
  37.    
  38.     // we implement a deep copy below, because fields are in dynamic
  39.     // memory, and thus we want to make sure that each object has its
  40.     // own fields/values and we can change them independently of the
  41.     // other copies we create
  42.    
  43.     // we follow the "rule of 3", i.e. if we had a reason to write abort
  44.     // destructor - it means we (probably) need a deep copy too
  45.     // for Java it is done by overriding/implementing the method 'clone()'
  46.    
  47.     // copy constructor (for C++) which creates a new
  48.     // object from the copy, eg. Object o1 = o2 or Object o1(o2) -
  49.     // works simply by extracting values from the copy
  50.    
  51.     Point(const Point &other){
  52.         init();
  53.         setX(other.getX());
  54.         setY(other.getY());
  55.     }
  56.    
  57.     // assingment operator (for C++) which modifies an existing object
  58.     // (important difference) - eg. o1 = o2 (where o1 was created earlier)
  59.     // and this means we do nothing for x = x (self assignment), otherwise
  60.     // we clear old fields and then load new - essence of the deep copy
  61.    
  62.     const Point& operator=(const Point &other){
  63.         if(this == &other) // check for self assignment
  64.             return other;
  65.         if(x)
  66.             delete x;
  67.         if(y)
  68.             delete y;
  69.        
  70.         // from this point on identical to copy constructor
  71.         init();
  72.         setX(other.getX());
  73.         setY(other.getY());
  74.         return other;
  75.     }
  76.    
  77.     // destructor - always provided is the class is a 'wrapper' which
  78.     // is responsible for resource management, thus we implement
  79.     // RAII, i.e. we tie the release of the resources to the
  80.     // (automatic) call of the destructor
  81.    
  82.     ~Point(){
  83.         if(x)
  84.             delete x;
  85.         if(y)
  86.             delete y;
  87.     }
  88.    
  89. private:
  90.  
  91.     // private method to avoid duplication, we have used it
  92.     // 4 times when creating/copying Point
  93.    
  94.     void init(){
  95.         x = NULL;
  96.         y = NULL;
  97.     }
  98.  
  99. public:
  100.  
  101.     // we use setters as methods responsible for the management
  102.     // of the field; note that they are used multiple times,
  103.     // and thus the decision to restrict *x to [0; 1] can only
  104.     // be written once and is used everywhere, as every other
  105.     // method both outside and inside of this class goes through
  106.     // the 'gate' of 'setX'
  107.    
  108.     // alongside validation, we also implement lazy instantiation,
  109.     // i.e. we only request for memory when X is needed to store
  110.     // the value, aka implementing the "resources on demand" strategy
  111.    
  112.     // init assigns initial values to NULL, and then we check if
  113.     // those have been set; if not, then we ask for memory, and if yes -
  114.     // we just change the value
  115.    
  116.     void setX(double x){
  117.         if(x < 0 || x > 1) // (random) validation
  118.             throw invalid_argument("Out of bounds");
  119.         if(this->x == NULL){ // check if we need to request for memory
  120.             this->x = new double;
  121.         }
  122.         *(this->x) = x; // simple assignment
  123.     }
  124.    
  125.     // see 'setX' for comments, this is the same as the method,
  126.     // but rewritten so that it represents the "happy case first"
  127.     // philosophy
  128.    
  129.     void setY(double y){
  130.         if(y >= 0 & y <= 1){ // describe happy cases, not errors
  131.             if(this->y == NULL){ // lazy instantiation
  132.                 this->y = new double; // ask for memory
  133.             }
  134.             *(this->y) = y;        
  135.         } else { // now exceptions
  136.             throw invalid_argument("Out of bounds");
  137.         }
  138.  
  139.         // note that brackets now also visually indicate
  140.         // what is the structure/logic of the method
  141.     }
  142.    
  143.     // aggregate method which initially did the job of two functions
  144.     // setX and setY satisfy the 'strong guarantee', because either
  145.     // they change the value, or they throw - and in this case
  146.     // the value stays the same
  147.    
  148.     void setXY(double x, double y){
  149.         // this would make the method satisfy the 'weak guarantee'
  150.         // in terms of exception safety, i.e. no memory leaks,
  151.         // but if setX suceeds and setY throws we distort the state
  152.        
  153.         // setX(x);
  154.         // setY(y);
  155.        
  156.         // and to satisfy the 'strong guarantee' we now make a copy
  157.         // and work with the copy until we are sure no exceptions occured
  158.         // this is not always needed, sometimes 'weak' is enough,
  159.         // because 'strong' requires additional resources
  160.        
  161.         Point temp(x, y); // try operations on the copy
  162.         *this = temp; // return the copy if the previous line did not throw
  163.     }
  164.    
  165.     // getters that also act as 'gates' for values, and this
  166.     // allows us to work with lazy instantiation, because we need
  167.     // to add logic which we cannot do if we used plain fields instead
  168.     // of methods
  169.    
  170.     // marked as const method which indicates we do not modify fields
  171.     // of 'this' object
  172.    
  173.     double getX() const {
  174.         if(this->x == NULL)
  175.             return 0;
  176.         return *x;
  177.     }
  178.     double getY() const {
  179.         if(this->y == NULL)
  180.             return 0;
  181.         return *y;
  182.     }
  183.    
  184.     // method we use for debugging, in this case - visually,
  185.     // but for larger blocks of code we should use tests, i.e.
  186.     // write conditions/programs that test the code and print whether
  187.     // they worked or not, programs printing "success" or "fail" or
  188.     // whatever, so that we only debug when something fails
  189.    
  190.     string toString() const {
  191.         stringstream ss;
  192.         ss << getX() << " " << getY();
  193.         return ss.str();
  194.     }
  195.    
  196. };
  197.  
  198. int main(){
  199.    
  200.     // we make sure main has a 'no-throw' guarantee, i.e.
  201.     // every possible exception is caught within the following block
  202.     // even we don't expect it to throw anything
  203.    
  204.     try {      
  205.    
  206.         // 'no naked new' in main, otherwise we would have memory leaks
  207.         // we use RAII, i.e. we work with values in main, and new/delete
  208.         // happens inside of the classes that are responsible for them
  209.        
  210.         // create values, p1 and p2 are on stack, yet values of the fields
  211.         // are stored in dynamic memory (see setX and setY)
  212.        
  213.         Point p1;
  214.         Point p2(1,1);
  215.  
  216.         // block we used when p1 and p2 were pointers
  217.         // we use '->' instead of '.'
  218.         // this was a baseline test aka 'project-specific hello-world'
  219.         // i.e. we make sure it compiles and we can work with a
  220.         // very basic version of Point before moving forward
  221.        
  222.         /*
  223.         // print what we have after creating
  224.         // through toString (all at once) of through specific getters
  225.         // every method is called at least once
  226.        
  227.         cout << (*p1).toString() << endl;
  228.         cout << p2->toString() << endl;    
  229.         cout << p2->getX() << " " << p2->getY() << endl;       
  230.        
  231.         // modify and see if values changed
  232.         p2->setX(4);
  233.         p2->setY(5);       
  234.         cout << p2->toString() << endl;
  235.         */
  236.        
  237.         // this was the block where we tested the assignment
  238.         // the issue was the original and the copy
  239.         // were not independent, we checked if they were
  240.  
  241.         /*
  242.         cout << p1->toString() << endl;
  243.         cout << p2->toString() << endl;
  244.        
  245.         *p1 = *p1;  // self assignment
  246.         *p1 = *p2; // assignment, not a copy constructor which would be
  247.         Point p3(p2), p4 = p2; // examples of copy constructing
  248.        
  249.         // check what happened after the copies were created
  250.         cout << p1->toString() << endl;
  251.         cout << p2->toString() << endl;
  252.        
  253.         // the test for proper copying is trying to modify the copy
  254.         // and see if original changed, or vice versa
  255.         p2->setX(2);
  256.         cout << p1->toString() << endl;
  257.         cout << p2->toString() << endl;
  258.        
  259.         */
  260.        
  261.         // and this is the current block to try and cause and exception
  262.         cout << p1.toString() << endl; 
  263.         try {
  264.             p1.setXY(1, 10);
  265.         } catch(...){
  266.         }
  267.         // we write try/catch to see what happens after the exception is
  268.         // thrown, i.e. how p1 looks like afterwards
  269.         // we could also do it inside of the 'catch' block, because
  270.         // p1 was accessible before, this is just a test,
  271.        
  272.         // for a 'strong guarantee', we need p1 to be as before, unchanged
  273.         cout << p1.toString() << endl; 
  274.        
  275.         // there should be 'no naked new' in main, and it follows
  276.         // that there should be no need for deleting anything in main
  277.         // main is not responsible for resource management - classes are
  278.         //delete p1;
  279.         //delete p2;
  280.        
  281.     } catch(exception &e){
  282.         cout << e.what() << endl;
  283.     } catch(...){
  284.         cout << "Unexpected error" << endl;
  285.     }
  286.    
  287.     return 0;
  288. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement