COMS W4995 C++ for C Programmers

Index of 2023-5/code/0525

Parent directory
Makefile
basic4.cpp

Makefile

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

basic4.cpp

#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;
}