Parent directory Makefile basic4.cpp
Download
CC = g++ CXX = g++ -fno-elide-constructors -std=c++14 CFLAGS = -g -Wall CXXFLAGS = -g -Wall basic4: basic4.o basic4.o: basic4.cpp .PHONY: clean clean: rm -f *.o basic4 .PHONY: all all: clean basic4
#include <iostream> class Pt { public: double x; double y; /* Pt() { x = 4.0; y = 5.0; } */ Pt(double _x = 4.0, double _y = 5.0) { x = _x; y = _y; std::cout << "hi" << std::endl; } Pt(const Pt& orig) { x = orig.x; y = orig.y; std::cout << "copy" << std::endl; } ~Pt() { std::cout << "bye" << std::endl; } const Pt& operator=(const Pt& rhs) { x = rhs.x; y = rhs.y; std::cout << "op=()" << std::endl; return *this; } void print() const { // const Pt *this; std::cout << "(" << this->x << "," << y << ")" << std::endl; } }; /* // C++-to-C translator would translate the print() member function // to a global function like this: void Pt_print(struct Pt *this) { // change x to this->x ... } */ //void transpose(Pt& p) void transpose(Pt p) // copy ctor case 1: passing by value { double t = p.x; p.x = p.y; p.y = t; p.print(); } void transpose(Pt *p) { double t = p->x; p->x = p->y; p->y = t; } Pt expand(Pt p); int main() { using namespace std; cout << "*** p1: stack allocation" << endl; { Pt p1; /* More examples of construction: Pt p1(6,7); Pt p1 = {6,7}; Pt p1 {6,7}; Pt p1 {6}; */ p1.print(); // C++-to-C translator would convert this // member function invocation to a regular // function call like this: Pt_print(&p1); } cout << "*** p2: heap allocation" << endl; // For heap allocation, we used malloc() in C like this: // // Pt *p2 = (Pt *)malloc(sizeof(Pt)); // p2->print(); // free(p2); // // But it cannot be used for heap alloc in C++ because // the constructor needs to be called. // Hence the new "new" keyword. Pt *p2 = new Pt {6,7}; p2->print(); delete p2; cout << "*** p3: heap allocation of array of objects" << endl; Pt *p3 = new Pt[5] {{6,7}, {8,9}, {10}}; p3[0].print(); p3[1].print(); p3[2].print(); p3[3].print(); p3[4].print(); // g++ stores the # of objects in the 8 bytes before p3 int64_t *i = ((int64_t *)p3) - 1; cout << *i << endl; delete[] p3; cout << "*** p4: passing by value vs. passing by reference" << endl; const Pt p4; p4.print(); // In C, we had to pass a pointer in order to modify a struct // like this: // transpose(&p4); // In C++, a function can take an object by reference transpose(p4); // copy ctor case 1: passing by value p4.print(); cout << "*** p5: direct invocation of copy ctor" << endl; { Pt p5 {p4}; // copy ctor case 2: direct invocation p5.print(); // All 3 lines below are equivalent to Pt p5 {p4}; // Pt p5 = {p4}; // Pt p5(p4); // Pt p5 = p4; // this is not assignment, but initialization } cout << "*** p6: returning object by value" << endl; { // Compile with g++ -fno-elide-constructors // You will see: // 3 copy constructor calls: // - passing p4 by value // - expand() returning a temporary object by value // - direct copy ctor call for p6 // And 3 destructor calls: // - parameter p going out of scope in expand() // - temporary object returned by expand() going away // - p6 going out of scope Pt p6 { expand(p4) }; // copy ctor case 3: return by value } cout << "*** p7: copy assignment" << endl; { Pt p7 {8,9}; p7.print(); (p7 = p4).print(); // (p7.operator=(p4)).print() } cout << "That's all folks!" << endl; } Pt expand(Pt p) // copy ctor case 3: returning object by value { p.x *= 2; p.y *= 2; return p; }