Parent directory
Makefile
basic4.cpp
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;
}