Classes with Pointer Data Members (I) Ying Wu Electrical & Computer Engineering Northwestern University ECE230 Lectures Series
Warming up… char *str_dup_new(const char *str) { return ( strcpy(new char [strlen(str)+1], str) ); } char *strcpy( char *strDestination, const char *strSource ); Example: char string[80]; strcpy( string, "Hello world from " ); size_t strlen( const char *string ); Example: void main( void ) { char buffer[61] = "How long am I?"; int len = strlen( buffer ); cout << buff << “ is “ << len << “characters long\n"; } Output: 'How long am I?' is 14 characters long
Let’s program … Task: construct a class “CPerson” –Name? –Address? –Phone?
1 st version class CPerson{ public: CPerson(); CPerson(const char* n, const char*a, const char* p); void set_name(const char *n) { m_name = str_dup_new(n); }; void set_address(const char *a) { m_address = str_dup_new(a); }; void set_phone(const char *p) { m_phone = str_dup_new(p); }; const char* get_name() const { return m_name; }; const char* get_address() const { return m_address; }; const char* get_phone() const { return m_phone; }; private: char *m_name; char *m_address; char *m_phone; }; Is it a good version?
CPerson::CPerson() { m_name = NULL; m_address = NULL; m_phone = NULL; } CPerson::CPerson(const char* n, const char* a, const char *p) { set_name(n); set_address(a); set_phone(p); }
What to learn today? Why they are troublesome? Troubles and Solutions –destructor –Assignment operation –The this pointer –The copy constructor: initialization vs. assignment
What are special? The class contains pointer data members We have to take care of the memory those pointer data members pointing to –i.e., using dynamic memory management –allocate memory for these pointer data members when instantiate each object –deallocate memory back when destroy objects Where does the trouble come? –initialization –Assignment –copying Special care has to be taken!
A destructor is needed CPerson::~CPerson() { delete [] m_name; delete [] m_address; delete [] m_phone; } void main(){ CPerson kk(“Karel”, “Evanston”, “ ”); CPerson *bb = new CPerson(“Bill Clinton”, “DC”, “ ”); cout << kk.get_name()<< kk.get_address() << kk.get_phone() << endl; cout get_name() get_address() get_phone(); delete bb; }
So, this is the 2 nd version class CPerson{ public: CPerson(); CPerson(const char* n, const char* a, const char* p); ~CPerson(); void set_name(const char *n) { m_name = str_dup_new(n); }; void set_address(const char *a) { m_address = str_dup_new(a); }; void set_phone(const char *p) { m_phone = str_dup_new(p); }; const char* get_name() const { return m_name; }; const char* get_address() const { return m_address; }; const char* get_phone() const { return m_phone; }; private: char *m_name; char *m_address; char *m_phone; };
Trouble 1: Call-by-Value void do_nothing(CPerson p) { // I actually do nothing here // but, unfortunately, I am in trouble! } m_name m_address m_phone mike object p Call-by-value m_name m_address m_phone mike object p After destruction of p m_name m_address m_phone mike object p Before function call ? ? ? void main() { CPerson mike(“Mike”, “Evanston”,”1111”); do_nothing(mike); }
Wild Pointer The deallocated memory will likely become occupied during subsequent memory allocations But the pointer members are still pointing to those memory locations Therefore, these pointer members become “wild” We can not track these pointer any more Wild pointers are very hard to trace and debug! So, try to avoid them when coding
Trouble 2: Return-by-Value CPerson test() { CPerson mike(“mike”,”IL”,”1111”); return mike; } m_name m_address m_phone mike object tmp Return-by-value m_name m_address m_phone tmp mike After destruction of mike m_name m_address m_phone mike object tmp Before return ? ? ? void main() { CPerson t; t = test(); }
3 rd Version: Copy Constructor class CPerson{ public: CPerson(); CPerson(const char*n, const char* a, const char* p); CPerson(const CPerson& person); // copy constructor ~CPerson(); void set_name(const char *n) { m_name = str_dup_new(n); }; void set_address(const char *a) { m_address = str_dup_new(a); }; void set_phone(const char *p) { m_phone = str_dup_new(p); }; const char* get_name() const { return m_name; }; const char* get_address() const { return m_address; }; const char* get_phone() const { return m_phone; }; private: char *m_name; char *m_address; char *m_phone; };
3 rd Version: Copy Constructor CPerson::CPerson(const CPerson & person) { set_name(person.get_name()); set_address(person.get_address()); set_phone(person.get_phone()); }
Trouble still…wild pointer void print_person(const CPerson &p) { CPerson tmp; tmp = p; // assignment operator cout << tmp.get_name() << tmp.get_address() << tmp.get_phone() << endl; } m_name m_address m_phone object p object tmp After the assignment m_name m_address m_phone object p object tmp After destruction of tmp m_name m_address m_phone object p object tmp Before the assignment ? ? ?
Solution to Assignment object tmp Before the assignment ? ? ? m_name m_address m_phone object p After the assignment m_name m_address m_phone object p object tmp m_name m_address m_phone After destruction of tmp m_name m_address m_phone object p object tmp m_name m_address m_phone
4th Version: Assignment class CPerson{ public: CPerson(); CPerson(const char*n, const char* a, const char* p); CPerson(const CPerson& person); // copy constructor ~CPerson(); void set_name(const char *n) { m_name = str_dup_new(n); }; void set_address(const char *a) { m_address = str_dup_new(a); }; void set_phone(const char *p) { m_phone = str_dup_new(p); }; const char* get_name() const { return m_name; }; const char* get_address() const { return m_address; }; const char* get_phone() const { return m_phone; }; voidassign(const CPerson &p); // assignment function private: char *m_name; char *m_address; char *m_phone; };
4 th version: Assignment void CPerson::assign(const CPerson &p) { // delete our own previously used memory delete [] m_name; delete [] m_address; delete [] m_phone; // now copy the new data passed m_name = str_dup_new(p.get_name()); m_address = str_dup_new(p.get_address()); m_phone = str_dup_new(p.get_phone()); } void print_person(const CPerson &p) { CPerson tmp; tmp.assign(p); cout << tmp.get_name() << tmp.get_address() << tmp.get_phone() << endl; }
What if I want = ? void main() { CPerson mike(“Michael”, “Evanston”, “ ”); CPerson tmp; tmp = mike; tmp.print_person(); }
5th Version: Elegant class CPerson{ public: CPerson(); CPerson(const char*n, const char* a, const char* p); CPerson(const CPerson &person); // copy constructor ~CPerson(); void set_name(const char *n) { m_name = str_dup_new(n); }; void set_address(const char *a) { m_address = str_dup_new(a); }; void set_phone(const char *p) { m_phone = str_dup_new(p); }; const char* get_name() const { return m_name; }; const char* get_address() const { return m_address; }; const char* get_phone() const { return m_phone; }; voidassign(const CPerson &p); voidoperator=(const CPerson &p); // overloading = private: char *m_name; char *m_address; char *m_phone; };
5 th version (cont.) void CPerson::operator=(const CPerson &p) { // delete our own previously used memory delete [] m_name; delete [] m_address; delete [] m_phone; // now copy the new data passed m_name = str_dup_new(p.get_name()); m_address = str_dup_new(p.get_address()); m_phone = str_dup_new(p.get_phone()); }
Potential trouble… self-destruction void trouble(const CPerson &p) { p = p; // auto-assignment } When an object is assigned to itself, i.e., auto-assignment, a trouble occurs: the allocated strings of the receiving objects are first released, but this also leads to the release of the strings of the right-hand side variable, which is called “self-destruction”.
6th Version: this class CPerson{ public: CPerson(); CPerson(const char*n, const char* a, const char* p); CPerson(const CPerson &person); // copy constructor ~CPerson(); void set_name(const char *n) { m_name = str_dup_new(n); }; void set_address(const char *a) { m_address = str_dup_new(a); }; void set_phone(const char *p) { m_phone = str_dup_new(p); }; const char* get_name() const { return m_name; }; const char* get_address() const { return m_address; }; const char* get_phone() const { return m_phone; }; voidassign(const CPerson &p); voidoperator=(const CPerson &p); // overloading = private: char *m_name; char *m_address; char *m_phone; };
6 th version: using this void CPerson::operator=(const CPerson &p) { if (this != &p){ // delete our own previously used memory delete [] m_name; delete [] m_address; delete [] m_phone; // now copy the new data passed m_name = str_dup_new(p.get_name()); m_address = str_dup_new(p.get_address()); m_phone = str_dup_new(p.get_phone()); }
What if I want … void main() { CPerson mike(“Michael”, “Evanston”, “ ”); CPerson a, b; a = b = mike; a.print_person(); }
7th Version: Cascading class CPerson{ public: CPerson(); CPerson(const char*n, const char* a, const char* p); CPerson(const CPerson &person); // copy constructor ~CPerson(); void set_name(const char *n) { m_name = str_dup_new(n); }; void set_address(const char *a) { m_address = str_dup_new(a); }; void set_phone(const char *p) { m_phone = str_dup_new(p); }; const char* get_name() const { return m_name; }; const char* get_address() const { return m_address; }; const char* get_phone() const { return m_phone; }; void assign(const CPerson &p); const CPerson& operator=(const CPerson &p); // enable cascading private: char *m_name; char *m_address; char *m_phone; };
7 th version (cont.) const CPerson& CPerson::operator=(const CPerson &p) { if (this != &p){ delete [] m_name; delete [] m_address; delete [] m_phone; m_name = str_dup_new(p.get_name()); m_address = str_dup_new(p.get_address()); m_phone = str_dup_new(p.get_phone()); } return (*this); // enable cascading }
Summary Classes with pointer data members are special, and need more attentions! How to handle it? –Destructor –Copy constructor –Assignment Assignment function Overloading = Using this –Avoiding self-destruction –Enabling cascading