== Constructors == * What is the size that an object occupies in memory? * What will be printed by the program below? #include struct A { A(unsigned u=0, bool b=false, long long l=0): _u(u), _b(b), _l(l) {} private: unsigned _u; bool _b; long long _l; }; class B { public: B(unsigned u=0, bool b=false, long long l=0): _u(u), _b(b), _l(l) {} private: unsigned _u; bool _b; long long _l; }; int main() { A a; std::cout << sizeof(a) << std::endl; B b; std::cout << sizeof(b) << std::endl; unsigned uu; std::cout << sizeof(uu) << std::endl; bool bb; std::cout << sizeof(bb) << std::endl; long long ll; std::cout << sizeof(ll) << std::endl; } * What about the following program? #include struct AM { AM(unsigned u=0, bool b=false, long long l=0): _u(u), _b(b), _l(l) {} unsigned getU() const { return _u; } bool getB() const { return _b; } long long getL() const { return _l; } private: unsigned _u; bool _b; long long _l; }; class BM { public: BM(unsigned u=0, bool b=false, long long l=0): _u(u), _b(b), _l(l) {} unsigned getU() const { return _u; } bool getB() const { return _b; } long long getL() const { return _l; } private: unsigned _u; bool _b; long long _l; }; int main() { AM am; std::cout << sizeof(am) << std::endl; BM bm; std::cout << sizeof(bm) << std::endl; } * So, how are objects laid out in memory? * Can you explain the output of the following program? #include class BM { public: BM(unsigned u=0, bool b=false, long long l=0): _u(u), _b(b), _l(l) {} unsigned getU() const { return _u; } bool getB() const { return _b; } long long getL() const { return _l; } private: unsigned _u; bool _b; long long _l; }; int main() { BM bm(13, true, 17); char* c = (char*)&bm; for (int i = 0; i < sizeof(bm); i++) { std::cout << (int)c[i] << std::endl; } } * Would it be possible to modify the value of, say, _u in the program above? #include class BM { public: BM(unsigned u=0, bool b=false, long long l=0): _u(u), _b(b), _l(l) {} unsigned getU() const { return _u; } bool getB() const { return _b; } long long getL() const { return _l; } private: unsigned _u; bool _b; long long _l; }; int main() { BM bm(13, true, 17); char* c = (char*)&bm; std::cout << bm.getU() << std::endl; c[0] = 29; std::cout << bm.getU() << std::endl; } * And what will be printed by the program below? #include struct AM { AM(unsigned u=0, bool b=false, long long l=0): _u(u), _b(b), _l(l) {} unsigned getU() const { return _u; } bool getB() const { return _b; } virtual long long getL() const { return _l; } // virtual method here! private: unsigned _u; bool _b; long long _l; }; class BM { public: BM(unsigned u=0, bool b=false, long long l=0): _u(u), _b(b), _l(l) {} unsigned getU() const { return _u; } bool getB() const { return _b; } virtual long long getL() const { return _l; } // virtual method here! private: unsigned _u; bool _b; long long _l; }; int main() { AM am; std::cout << sizeof(am) << std::endl; BM bm; std::cout << sizeof(bm) << std::endl; } * When is memory reserved for an object? - Stack allocation - Heap allocation * And when is memory freed? - Stack allocation - Heap allocation * What's stack allocation? int main() { AM am; } * What's heap allocation? int main() { AM *am = new AM(); delete am; } * What is an object constructor? - In class-based object-oriented programming, a constructor (abbreviation: ctor) is a special type of subroutine called to create an object. * What types of constructors exist? - Default (https://www.geeksforgeeks.org/constructors-c/) - Parameterized - Copy (https://www.geeksforgeeks.org/copy-constructor-in-cpp/) * What is the default constructor? A default constructor is a constructor which can be called with no arguments (either defined with an empty parameter list, or with default arguments provided for every parameter). * If we do not initialize the fields of an object explicitly, what happens? * What does the program below print? class A { public: unsigned getU() const { return _u; } bool getB() const { return _b; } long long getL() const { return _l; } private: unsigned _u; bool _b; long long _l; }; void printObj(A* a) { std::cout << a->getU() << std::endl; std::cout << a->getB() << std::endl; std::cout << a->getL() << std::endl; } int main() { A a0, a1, a2, a3; printObj(&a0); printObj(&a1); printObj(&a2); printObj(&a3); } * What if we add a parameterized constructor to the program above, i.e.: #include class A { public: A(unsigned u, bool b, long long l): _u(u), _b(b), _l(l) {} unsigned getU() const { return _u; } bool getB() const { return _b; } long long getL() const { return _l; } private: unsigned _u; bool _b; long long _l; }; void printObj(A* a) { std::cout << a->getU() << std::endl; std::cout << a->getB() << std::endl; std::cout << a->getL() << std::endl; } int main() { A a0, a1, a2, a3; printObj(&a0); printObj(&a1); printObj(&a2); printObj(&a3); } * But, what if I still want to have a default constructor? - Default arguments class A { public: A(unsigned u=0, bool b=false, long long l=0): _u(u), _b(b), _l(l) {} ... }; - Parameterless constructor class A { public: A() {} A(unsigned u, bool b, long long l): _u(u), _b(b), _l(l) {} ... }; - Default keyword class A { public: A(unsigned u, bool b, long long l): _u(u), _b(b), _l(l) {} A() = default; ... } * What is the difference between the 'default' keywords and the empty ctor? * Why does this program fail to compile? And how to fix it? struct A { int x; A(int x): x(x) {} }; int main() { A a; } * Why does this program below fail to compile? struct A { const int x; A() {} }; int main() { A a; } * And this program below, why does it fail to compile? struct A { int& x; A() {} }; int main() { A a; } * And this program below? What's the problem, and how to fix it? struct A { const int x; A(int y) { int aux = y + 1; x = aux * aux; } }; int main() { A a(1); } * Does this program below compile? struct A { const int x; A(int y): x(init(y)) { } private: int init(int y) { return (y + 1)*(y + 1); } }; int main() { A a(1); } * What is a copy constructor? - Copy constructors define the actions performed by the compiler when copying class objects. A Copy constructor has one formal parameter that is the type of the class (the parameter may be a reference to an object). It is used to create a copy of an existing object of the same class. * What does the program below print? #include struct A { int n; A(int n = 1) : n(n) { } A(const A& a) : n(a.n) { } // user-defined copy ctor }; int main(){ A a1(13); A a2(a1); std::cout << a1.n << std::endl; std::cout << a2.n << std::endl; } * If we make 'n' private, does the program still compile? #include struct A { A(int n = 1) : n(n) { } A(const A& a) : n(a.n) { } // user-defined copy ctor int getN() const { return n; } private: int n; }; int main(){ A a1(13); A a2(a1); std::cout << a1.getN() << std::endl; std::cout << a2.getN() << std::endl; } * What is this program doing? #include struct A { A(int n = 1) : n(n) { } int n; }; int main(){ A a1 = 13; std::cout << a1.n << std::endl; } * What about this program? (Valid in c++11): #include struct A { A(int n, int f, int l) : _n(n), _f(f), _l(l) { std::cout << "Invoking ctor\n"; } int _n; int _f; int _l; }; int main(){ A a1 = {13, 0, 17}; std::cout << a1._n << std::endl; } * What is the problem with the program below? struct A { A(int x): _x(x) { } int _x; }; struct B: A { B(int y): _y(y) { } int _y; }; int main() { A a(1); B b(2); } * How can you fix it? struct A { A(int x): _x(x) { } int _x; }; struct B: A { B(int y): A(0), _y(y) { } int _y; }; int main() { A a(1); B b(2); } * What does this program print? #include struct A { A() { std::cout << "A()\n"; } A(int x): _x(x) { std::cout << "A(" << x << ")\n"; } int _x; }; int main() { A a0; A a1(1); } * What about this program? #include struct A { A() { std::cout << "A()\n"; } A(int x): _x(x) { std::cout << "A(" << x << ")\n"; } int _x; }; void printX(A a) { std::cout << a._x << std::endl; } int main() { A a0; A a1(1); printX(a0); printX(a1); } * What about this program? #include struct A { A() { std::cout << "A()\n"; } A(const A& a) : _x(a._x) { std::cout << "A(&A)\n"; } A(int x): _x(x) { std::cout << "A(" << x << ")\n"; } int _x; }; void printX(A a) { std::cout << a._x << std::endl; } int main() { A a0; A a1(1); printX(a0); printX(a1); } * And if we have an assignment? #include struct A { A() { std::cout << "A()\n"; } A(const A& a) : _x(a._x) { std::cout << "A(&A)\n"; } A(int x): _x(x) { std::cout << "A(" << x << ")\n"; } int _x; }; void printX(A a) { std::cout << a._x << std::endl; } int main() { A a0; A a2 = a0; } * What if we pass an object through a pointer assignment? #include struct A { A() { std::cout << "A()\n"; } A(const A& a) : _x(a._x) { std::cout << "A(&A)\n"; } A(int x): _x(x) { std::cout << "A(" << x << ")\n"; } int _x; }; void printX(A* a) { std::cout << a->_x << std::endl; } int main() { A a0; printX(&a0); } * And, what if we pass by reference, e.g.: void printX(A& a) { std::cout << a._x << std::endl; } int main() { A a0; printX(a0); } == The assignment operator == * What does this program print? struct A { A() { std::cout << "A()\n"; } A(const A& a) : _x(a._x) { std::cout << "A(&A)\n"; } A(int x): _x(x) { std::cout << "A(" << x << ")\n"; } A& operator=(const A& a) { std::cout << "=&A\n"; _x = a._x; return *this; } int _x; }; void printX(A& a) { std::cout << a._x << std::endl; } int main() { A a0; A a1 = a0; a1 = a0; printX(a0); } * What about this program? #include struct A { A() { std::cout << "A()\n"; } A(const A& a) : _x(a._x) { std::cout << "A(&A)\n"; } A(int x): _x(x) { std::cout << "A(" << x << ")\n"; } A& operator=(A a) { std::cout << "=A\n"; _x = a._x; return *this; } int _x; }; void printX(A& a) { std::cout << a._x << std::endl; } int main() { A a0; A a1 = a0; a1 = a0; printX(a0); } * Can you explain the different between them? == Destructors == A destructor is a special member function that is called when the lifetime of an object ends. The purpose of the destructor is to free the resources that the object may have acquired during its lifetime. * What does this program print? #include struct A { A() { std::cout << "A()\n"; } A(const A& a) : _x(a._x) { std::cout << "A(&A)\n"; } A(int x): _x(x) { std::cout << "A(" << x << ")\n"; } A& operator=(const A& a) { std::cout << "=&A\n"; _x = a._x; return *this; } ~A() { std::cout << "~A\n"; } int _x; }; void printX(A& a) { std::cout << a._x << std::endl; } int main() { A a0; A a1 = a0; a1 = a0; printX(a0); std::cout << "End of main\n"; } * What does this program print? #include struct A { A() { std::cout << "A()\n"; } A(const A& a) : _x(a._x) { std::cout << "A(&A)\n"; } A(int x): _x(x) { std::cout << "A(" << x << ")\n"; } A& operator=(const A& a) { std::cout << "=&A\n"; _x = a._x; return *this; } ~A() { std::cout << "~A\n"; } int _x; }; void printX(A a) { std::cout << a._x << std::endl; std::cout << "End of printX\n"; } int main() { A a0; A a1 = a0; a1 = a0; printX(a0); std::cout << "End of main\n"; } * What about this program, which contains inheritance? #include struct A { A(int x = 13): _x(x) { std::cout << "A(" << x << ")\n"; } ~A() { std::cout << "~A\n"; } int _x; }; struct B: A { B(int y = 17): _y(y) { std::cout << "B(" << _y << ")\n"; } ~B() { std::cout << "~B\n"; } int _y; }; void printX(B b) { std::cout << b._x << std::endl; std::cout << "End of printX\n"; } int main() { B b0; printX(b0); std::cout << "End of main\n"; } * What if we have multiple inheritance? #include struct A0 { A0(int x = 13): _x0(x) { std::cout << "A0(" << x << ")\n"; } ~A0() { std::cout << "~A0\n"; } int _x0; }; struct A1 { A1(int x = 13): _x1(x) { std::cout << "A1(" << x << ")\n"; } ~A1() { std::cout << "~A1\n"; } int _x1; }; struct B: A0, A1 { B(int y = 17): _y(y) { std::cout << "B(" << _y << ")\n"; } ~B() { std::cout << "~B\n"; } int _y; }; void printX0(B b) { std::cout << b._x0 << std::endl; std::cout << "End of printX\n"; } int main() { B b0; printX0(b0); std::cout << "End of main\n"; } * And what happens if I invert the order of inheritance, i.e.: struct B: A1, A0 { B(int y = 17): _y(y) { std::cout << "B(" << _y << ")\n"; } ~B() { std::cout << "~B\n"; } int _y; }; * Why is the dtor of B not called in the program below? #include struct A { A(int x = 13): _x(x) { std::cout << "A(" << x << ")\n"; } virtual int getX() { return _x; }; ~A() { std::cout << "~A\n"; } int _x; }; struct B: A { B(int y = 17): _y(y) { std::cout << "B(" << _y << ")\n"; } int getX() { return _x; } ~B() { std::cout << "~B\n"; } int _y; }; int main() { A *a = new B(); std::cout << "End of main\n"; delete a; } * How can we fix this problem? #include struct A { A(int x = 13): _x(x) { std::cout << "A(" << x << ")\n"; } virtual int getX() { return _x; }; virtual ~A() { std::cout << "~A\n"; } int _x; }; struct B: A { B(int y = 17): _y(y) { std::cout << "B(" << _y << ")\n"; } int getX() { return _x; } ~B() { std::cout << "~B\n"; } int _y; }; int main() { A *a = new B(); std::cout << "End of main\n"; delete a; } * Why are both dtors called in the program below? #include struct A { A(int x = 13): _x(x) { std::cout << "A(" << x << ")\n"; } virtual int getX() = 0; ~A() { std::cout << "~A\n"; } int _x; }; struct B: A { B(int y = 17): _y(y) { std::cout << "B(" << _y << ")\n"; } int getX() { return _x; } ~B() { std::cout << "~B\n"; } int _y; }; int main() { B *b = new B(); std::cout << "End of main\n"; delete b; }