Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // 2025-04-10
- #include <string>
- #include <iostream>
- #include <sstream>
- #include <exception>
- #include <stdexcept>
- using namespace std;
- // create a class to represent a point on a plane
- class Point {
- // two fields saved as pointers - to have values in dynamic memory
- // and to learn how to do it for bigger objects (you don't need this
- // for simple/primitive types or for String in Java,
- // because it's immutable
- double *x, *y;
- public:
- // delegate the responsibility to a private method called 'init'
- // to avoid code duplication; no parametes - nothing to do
- Point(){
- init();
- }
- // two parameters - means we call setters for each one
- Point(double x, double y){
- init();
- setX(x);
- setY(y);
- }
- // we implement a deep copy below, because fields are in dynamic
- // memory, and thus we want to make sure that each object has its
- // own fields/values and we can change them independently of the
- // other copies we create
- // we follow the "rule of 3", i.e. if we had a reason to write abort
- // destructor - it means we (probably) need a deep copy too
- // for Java it is done by overriding/implementing the method 'clone()'
- // copy constructor (for C++) which creates a new
- // object from the copy, eg. Object o1 = o2 or Object o1(o2) -
- // works simply by extracting values from the copy
- Point(const Point &other){
- init();
- setX(other.getX());
- setY(other.getY());
- }
- // assingment operator (for C++) which modifies an existing object
- // (important difference) - eg. o1 = o2 (where o1 was created earlier)
- // and this means we do nothing for x = x (self assignment), otherwise
- // we clear old fields and then load new - essence of the deep copy
- const Point& operator=(const Point &other){
- if(this == &other) // check for self assignment
- return other;
- if(x)
- delete x;
- if(y)
- delete y;
- // from this point on identical to copy constructor
- init();
- setX(other.getX());
- setY(other.getY());
- return other;
- }
- // destructor - always provided is the class is a 'wrapper' which
- // is responsible for resource management, thus we implement
- // RAII, i.e. we tie the release of the resources to the
- // (automatic) call of the destructor
- ~Point(){
- if(x)
- delete x;
- if(y)
- delete y;
- }
- private:
- // private method to avoid duplication, we have used it
- // 4 times when creating/copying Point
- void init(){
- x = NULL;
- y = NULL;
- }
- public:
- // we use setters as methods responsible for the management
- // of the field; note that they are used multiple times,
- // and thus the decision to restrict *x to [0; 1] can only
- // be written once and is used everywhere, as every other
- // method both outside and inside of this class goes through
- // the 'gate' of 'setX'
- // alongside validation, we also implement lazy instantiation,
- // i.e. we only request for memory when X is needed to store
- // the value, aka implementing the "resources on demand" strategy
- // init assigns initial values to NULL, and then we check if
- // those have been set; if not, then we ask for memory, and if yes -
- // we just change the value
- void setX(double x){
- if(x < 0 || x > 1) // (random) validation
- throw invalid_argument("Out of bounds");
- if(this->x == NULL){ // check if we need to request for memory
- this->x = new double;
- }
- *(this->x) = x; // simple assignment
- }
- // see 'setX' for comments, this is the same as the method,
- // but rewritten so that it represents the "happy case first"
- // philosophy
- void setY(double y){
- if(y >= 0 & y <= 1){ // describe happy cases, not errors
- if(this->y == NULL){ // lazy instantiation
- this->y = new double; // ask for memory
- }
- *(this->y) = y;
- } else { // now exceptions
- throw invalid_argument("Out of bounds");
- }
- // note that brackets now also visually indicate
- // what is the structure/logic of the method
- }
- // aggregate method which initially did the job of two functions
- // setX and setY satisfy the 'strong guarantee', because either
- // they change the value, or they throw - and in this case
- // the value stays the same
- void setXY(double x, double y){
- // this would make the method satisfy the 'weak guarantee'
- // in terms of exception safety, i.e. no memory leaks,
- // but if setX suceeds and setY throws we distort the state
- // setX(x);
- // setY(y);
- // and to satisfy the 'strong guarantee' we now make a copy
- // and work with the copy until we are sure no exceptions occured
- // this is not always needed, sometimes 'weak' is enough,
- // because 'strong' requires additional resources
- Point temp(x, y); // try operations on the copy
- *this = temp; // return the copy if the previous line did not throw
- }
- // getters that also act as 'gates' for values, and this
- // allows us to work with lazy instantiation, because we need
- // to add logic which we cannot do if we used plain fields instead
- // of methods
- // marked as const method which indicates we do not modify fields
- // of 'this' object
- double getX() const {
- if(this->x == NULL)
- return 0;
- return *x;
- }
- double getY() const {
- if(this->y == NULL)
- return 0;
- return *y;
- }
- // method we use for debugging, in this case - visually,
- // but for larger blocks of code we should use tests, i.e.
- // write conditions/programs that test the code and print whether
- // they worked or not, programs printing "success" or "fail" or
- // whatever, so that we only debug when something fails
- string toString() const {
- stringstream ss;
- ss << getX() << " " << getY();
- return ss.str();
- }
- };
- int main(){
- // we make sure main has a 'no-throw' guarantee, i.e.
- // every possible exception is caught within the following block
- // even we don't expect it to throw anything
- try {
- // 'no naked new' in main, otherwise we would have memory leaks
- // we use RAII, i.e. we work with values in main, and new/delete
- // happens inside of the classes that are responsible for them
- // create values, p1 and p2 are on stack, yet values of the fields
- // are stored in dynamic memory (see setX and setY)
- Point p1;
- Point p2(1,1);
- // block we used when p1 and p2 were pointers
- // we use '->' instead of '.'
- // this was a baseline test aka 'project-specific hello-world'
- // i.e. we make sure it compiles and we can work with a
- // very basic version of Point before moving forward
- /*
- // print what we have after creating
- // through toString (all at once) of through specific getters
- // every method is called at least once
- cout << (*p1).toString() << endl;
- cout << p2->toString() << endl;
- cout << p2->getX() << " " << p2->getY() << endl;
- // modify and see if values changed
- p2->setX(4);
- p2->setY(5);
- cout << p2->toString() << endl;
- */
- // this was the block where we tested the assignment
- // the issue was the original and the copy
- // were not independent, we checked if they were
- /*
- cout << p1->toString() << endl;
- cout << p2->toString() << endl;
- *p1 = *p1; // self assignment
- *p1 = *p2; // assignment, not a copy constructor which would be
- Point p3(p2), p4 = p2; // examples of copy constructing
- // check what happened after the copies were created
- cout << p1->toString() << endl;
- cout << p2->toString() << endl;
- // the test for proper copying is trying to modify the copy
- // and see if original changed, or vice versa
- p2->setX(2);
- cout << p1->toString() << endl;
- cout << p2->toString() << endl;
- */
- // and this is the current block to try and cause and exception
- cout << p1.toString() << endl;
- try {
- p1.setXY(1, 10);
- } catch(...){
- }
- // we write try/catch to see what happens after the exception is
- // thrown, i.e. how p1 looks like afterwards
- // we could also do it inside of the 'catch' block, because
- // p1 was accessible before, this is just a test,
- // for a 'strong guarantee', we need p1 to be as before, unchanged
- cout << p1.toString() << endl;
- // there should be 'no naked new' in main, and it follows
- // that there should be no need for deleting anything in main
- // main is not responsible for resource management - classes are
- //delete p1;
- //delete p2;
- } catch(exception &e){
- cout << e.what() << endl;
- } catch(...){
- cout << "Unexpected error" << endl;
- }
- return 0;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement