* What is object oriented programming. - The humanization of data * What are the key characteristics of object oriented programming? - Objects - Subtyping - Dynamic Dispatch * What is an object? - Data - receives messages (methods) - is alive during the execution of the program * What is an object oriented program? - Is a program formed by objects that invoke methods upon each other. == Objects == * How are objects created in C++? - Objects are created by instantiating classes or structs. * What is a class? - It is a definition of an object * What is the difference between classes and structs? - class: everything is private by default - struct: everything is public by default * Can you change the program below to use a class, instead of a struct? #include struct Point { Point(const double xx, const double yy): x(xx), y(yy) {} const double x; const double y; std::string to_string() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; } }; int main() { Point p0(2.718, 1.618); std::cout << p0.to_string() << std::endl; } * Why simply changing 'struct' to 'class' breaks compilation? - error: calling a private constructor of class 'Point' - error: 'to_string' is a private member of 'Point' * How can you fix the program? #include class Point { public: Point(const double xx, const double yy): x(xx), y(yy) {} const double x; const double y; std::string to_string() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; } }; int main() { Point p0(2.718, 1.618); std::cout << p0.to_string() << std::endl; } * But now everything is public. Would it make sense to encapsulate part of the Point? - We can encapsulate the fields: #include class Point { public: Point(const double xx, const double yy): x(xx), y(yy) {} std::string to_string() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; } private: const double x; const double y; }; int main() { Point p0(2.718, 1.618); std::cout << p0.to_string() << std::endl; } * But now, how can we read the value of the fields? #include class Point { public: Point(const double xx, const double yy): x(xx), y(yy) {} std::string to_string() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; } double getX() const { return x; } double getY() const { return y; } private: const double x; const double y; }; int main() { Point p0(2.718, 1.618); std::cout << p0.to_string() << std::endl; std::cout << p0.getX() << std::endl; } * Can you add a 'norm' method to the point? #include ... class Point { public: Point(const double xx, const double yy): x(xx), y(yy) {} std::string to_string() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; } double getX() const { return x; } double getY() const { return y; } double norm() const { return sqrt(x * x + y * y); } private: const double x; const double y; }; * It would be interesting to make 'x' and 'y' mutable, so that we can update them without having to create a new point. Can you add a displaceX and a displaceY methods to this class? * Would this method solve our problem? void displaceX(const double xx) const { x += xx; } * How can we fix this problem? - Remove const from field declaration - Remove const from method declaration class Point { public: Point(const double xx, const double yy): x(xx), y(yy) {} std::string to_string() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; } double getX() const { return x; } double getY() const { return y; } double norm() const { return sqrt(x * x + y * y); } void displaceX(const double xx) { x += xx; } void displaceY(const double yy) { y += yy; } private: double x; double y; }; * When is the object destroyed? * Can you perform any action when the object is destroyed? class Point { public: Point(const double xx, const double yy): x(xx), y(yy) {} ~Point() { std::cout << "Object " << to_string() << " died\n"; } std::string to_string() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; } double getX() const { return x; } double getY() const { return y; } double norm() const { return sqrt(x * x + y * y); } void displaceX(const double xx) { x += xx; } void displaceY(const double yy) { y += yy; } private: double x; double y; }; * When is the destructor called? * For instance, is the destructor called any time in the code below? void foo() { Point p0(2.718, 1.618); std::cout << p0.to_string() << std::endl; } int main() { foo(); } * How many times the destructor will be called? double fact(Point p) { if (p.getX() > 1) { double n = p.getX(); p.displaceX(-1.0); return n * fact(p); } else { return 1.0; } } int main() { Point p0(10.0, 0.0); std::cout << fact(p0) << std::endl; } * Is the destructor going to be called at least once in the program below? int main() { Point *p0 = new Point(10.0, 0.0); std::cout << p0->to_string() << std::endl; } * How can we ensure that the destructor is called? int main() { Point *p0 = new Point(10.0, 0.0); std::cout << p0->to_string() << std::endl; delete p0; } == Inheritance == * Can you implement a point with mass? This point has the coordinates (x, y) and its mass. * Would it be possible to reuse the implementation of Point? * Can we prepare Point to be reused? We can move it into a header file: #include class Point { public: Point(const double xx, const double yy): x(xx), y(yy) {} ~Point() {} std::string to_string() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; } double getX() const { return x; } double getY() const { return y; } double norm() const { return sqrt(x * x + y * y); } void displaceX(const double xx) { x += xx; } void displaceY(const double yy) { y += yy; } private: double x; double y; }; * Now, how can you reuse Point, to produce a point with mass? #include #include #include "Point.h" class MassPoint : public Point { public: MassPoint(const double xx, const double yy, const double mmass): Point(xx, yy), mass(mmass) {} std::string to_string() const { return Point::to_string() + ", " + std::to_string(mass) + "g"; } double getMass() const { return mass; } private: const double mass; }; * Can you test this code? int main() { MassPoint *p0 = new MassPoint(2.718, 1.618, 3.1416); std::cout << p0->to_string() << std::endl; delete p0; } * Can you reuse the other public properties of Point in an object of type MassPoint? int main() { MassPoint *p0 = new MassPoint(2.718, 1.618, 3.1416); std::cout << p0->to_string() << std::endl; std::cout << p0->getX() << std::endl; std::cout << p0->getY() << std::endl; std::cout << p0->norm() << std::endl; p0->displaceX(1.0); p0->displaceY(3.5); std::cout << p0->to_string() << std::endl; delete p0; } * What would happen if you changed the declaration of MassPoint, to be like this one below? class MassPoint : Point {...} // ie.: removed 'public' == Subtyping and Dynamic Dispatch == * If I have a code like this program below, which 'to_string' is called, that from Point, or that from MassPoint? int main() { Point *p0 = new MassPoint(2.718, 1.618, 3.1416); std::cout << p0->to_string() << std::endl; delete p0; } * What would be more interesting, calling 'to_string' from Point or from MassPoint? * And how can we do it? // We can change the declaration of to_string in Point, marking it as // virtual. virtual std::string to_string() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; } * So, what's a virtual function? - It is a function name whose implementation depends on the dynamic type of the target. * What's static and dynamic types? - Static: the type of the declaration. - Dynamic: the type of instantiation. * Would this program below compile? int main() { Point *p0 = new MassPoint(2.718, 1.618, 3.1416); std::cout << p0->to_string() << std::endl; std::cout << p0->getMass() << std::endl; delete p0; } * And this program below, does it compile? int main() { MassPoint *p0 = new MassPoint(2.718, 1.618, 3.1416); std::cout << p0->to_string() << std::endl; std::cout << p0->getMass() << std::endl; delete p0; } * Which lines in the program below will not compile? int main() { Point* p0 = new Point(1.0, 1.0); // L0 Point* p1 = new MassPoint(2.0, 2.0, 1.0); // L1 MassPoint* p2 = new MassPoint(3.0, 3.0, 1.0); // L2 MassPoint* p3 = new Point(3.0, 3.0); // L3 } * Can you add MassPoints into a vector of Points? int main() { std::vector points; Point* p0 = new Point(1.0, 1.0); points.push_back(p0); Point* p1 = new MassPoint(2.0, 2.0, 1.0); points.push_back(p1); } * What about the inverse: add Points to a vector of MassPoints? int main() { std::vector points; Point* p0 = new Point(1.0, 1.0); points.push_back(p0); Point* p1 = new MassPoint(2.0, 2.0, 1.0); points.push_back(p1); } * Which lines in the program below do not compile? int main() { std::vector points; Point* p0 = new Point(1.0, 1.0); // L0 points.push_back(p0); // L1 Point* p1 = new MassPoint(2.0, 2.0, 1.0); // L2 points.push_back(p1); // L3 MassPoint* p2 = new MassPoint(3.0, 3.0, 1.0); // L4 points.push_back(p2); // L5 MassPoint* p3 = new Point(3.0, 3.0); // L6 points.push_back(p3); // L7 } * Can you write a function that receives a vector of points (and/or MassPoints) and finds their centroid? Point* centroid(std::vector points) { double x = 0.0; double y = 0.0; for (Point *point: points) { x += point->getX(); y += point->getY(); } return new Point(x/points.size(), y/points.size()); } int main() { std::vector points; Point* p0 = new Point(1.0, 1.0); points.push_back(p0); Point* p1 = new MassPoint(2.0, 2.0, 1.0); points.push_back(p1); std::cout << centroid(points)->to_string() << std::endl; } * What about this function below, does it work? Point* centroidMass(std::vector points) { double x = 0.0; double y = 0.0; for (MassPoint *point: points) { x += point->getX(); y += point->getY(); } return new Point(x/points.size(), y/points.size()); } int main() { std::vector points; MassPoint* p0 = new MassPoint(3.0, 3.0, 1.0); points.push_back(p0); MassPoint* p1 = new MassPoint(1.0, 1.0, 2.0); points.push_back(p1); std::cout << centroidMass(points)->to_string() << std::endl; } * Which function is better, centroid or centroidMass? * Have you ever heard about the open-closed principle?