#include #include /* memcpy function */ #include #include #include using namespace std; // From old to new C++: // Ultimate goal: implement a modern version of the vector class // Show why it's still not quite safe. /* Memory errors caused by imbalance of lifetimes of pointer and pointee. 1. Pointer to invalid data, dangling pointer 2. memory leak. pointee outlives pointer 3. Double delete (double free) 4. call delete on something not on heap (pointer to stack) 5. call delete on null pointer. */ int* memerrors() { int A[10] = {0}; int B[10] = {1}; A[15] = 3; for(int x:B) cout << x << endl; // 3 appears in B int* HA = new int[10]; // heap allocated array // .. do stuff with HA delete[] HA; int* HB = new int[10]; HA[3] = 99; // accessing memory that's been deleted! (pointer outlives..) cout << HB[3] << " HB[3]\n"; HB = new int[20]; // memory leak! int* HC = HB; delete[] HB; delete[] HC; // double free int zz = 1; return A+1; // same as &A[1], but no compiler warning } int main() { memerrors(); ///// emulate/redefine vector? vector v1{2,4,6,8,10}; vector v2 = v1; // is this a copy, or just a reference? v1[0] += 1; cout << v1[0] << " and " << v2[0] << endl; int& r0 = v1[0]; int* p1 = &v1[1]; cout << r0 << endl; cout << *p1 << endl; v1.push_back(12); v1.push_back(13); cout << r0 << endl; cout << *p1 << endl; }//main ////// myvector done the right way (modern, CLEAN C++): ////// FOLLOW THE RULE OF ZERO: no destructor, copy constructor, copy =, ////// move =, or move constructor. ////// USE ONLY REFERENCES AND SMART POINTERS ////// std::{unique_ptr,shared_ptr,weak_ptr}, unique_ptr whenever possible ////// because it's more reliable and more efficient than shared_ptr. ////// Use a=b (copy assignment) only on primitive types (int, float, etc) ////// and structures that can be completely stack-allocated. ////// Use a=move(b) on anything that could include heap pointers. /// In doing so we ensure that: /// 1. copy semantics is LOCKED and always do a bit-by-bit copy. /// Distinguish copying from cloning. write separate procedure to clone. /// 2. move semantics is LOCKED and always do the following: /// if something can be copied, copy it: int x = move(3); //copies /// otherwise, move =/move constructor perform TRANSFER OF OWNERSHIP. /// Can't have myvector(myvector&& B) { cout << "too tired to move"; } /// 3. The only destructor (original inovation of Stroustrup) needed are /// the ones for the smart pointers. /// any structure with a unique_ptr, not copyable, will not get a /// default copy constructor/copy = operator // compile with -std=c++20: template requires std::movable && std::destructible struct myvec { private: int size{0}; int cap{0}; unique_ptr harray{}; void resize() { cap *= 2; unique_ptr h2 = make_unique(cap); for(int i=0;i(cap); } myvec(std::initializer_list m) { harray = make_unique(m.size()); int i=0; for(T x:m) harray[i++] = x; size = cap = i; } T& operator [](int i) { return harray[i]; } // unchecked, zero-overhead T& operator ()(int i) { // checked, with overhead (java-ish) if (i<0 || i>=size) throw std::out_of_range("bad index"); else return harray[i]; } void push_back(T x) { if (size>=cap) resize(); harray[size++] = move(x); // may copy or move } }; void myvectest() { myvec v{1,2,3,4}; int& p1 = v[0]; //myvec v2 = v; // won't compile - move semantics transferred to myvec myvec v2 = move(v); // compiles, invokes default move constructor ////////////// BUT THIS IS STILL NOT ENOUGH! /////////////////// cout << p1 << endl; // null pointer error - v has moved int& p2 = v2[1]; v2.push_back(10); cout << p2 << endl; // data moved } /// No guard against using a reference after the referred-to's lifetime /// ends. No guard against using a unique_ptr after a move. /// and memory leaks are still possible... struct badnews { unique_ptr A; unique_ptr next; badnews() { A = make_unique(100); } }; void it_still_leaks() { unique_ptr toobad = make_unique(); toobad->next = move(toobad); // this causes a memory leak } /////// Principle of Exclusive Mutation ....