this
binds objects to member functionsThe Person class has appeared previously in two object-copying examples. The first demonstrating the copy constructor and the second demonstrating the assignment operator. Both examples lack authenticity because they provide an identification number rather than a name, arguably the most frequently used attribute characterizing a "person." The following examples replace the ID number with a name implemented as a C-string. The first example manages the name as a fixed-length character array and the second as a character pointer.
Unless programmers deliberately choose another argument-passing mechanism, C++ passes them by value. C-strings are the exception to this rule, with C++ passing them by pointer. Consequently, functions in both versions use pointers, so the primary difference between them is where the Person class allocates the C-string storing a person's name. The array version allocates a fixed-length array on the stack, while the pointer version allocates a variable-length array on the heap. The following figures highlight the differences between the array- and pointer-based versions.
Person1: Array Version | Person2: Pointer Version |
---|---|
#include <iostream> #include <cstring> using namespace std; class Person { private: char name[100] = ""; // (a) int weight = 0; double height = 0; public: Person() {} Person(char* n, int w, double h) : weight(w), height(h) // (c) { strcpy(name, n); } Person(const Person& p); Person& operator=(Person& p); void change(char* n, int w, double h); friend ostream& operator<<(ostream& out, Person& me); friend istream& operator>>(istream& in, Person& me); }; |
#include <iostream> #include <cstring> using namespace std; class Person { private: char* name = nullptr; // (b) int weight = 0; double height = 0; public: Person() {} Person(char* n, int w, double h) : name(strcpy(new char[strlen(n)+1], n)), // (d) weight(w), height(h) {} Person(const Person& p); Person& operator=(Person& p); ~Person() { if (name != nullptr) delete name; } void change(char* n, int w, double h); friend ostream& operator<<(ostream& out, Person& me); friend istream& operator>>(istream& in, Person& me); }; |
strlen(n)+1
is the size of the array the class allocates on the heap to store the person's name. n is the constructor parameter with the name, the strlen function counts and returns the number of characters in n and increases the size by 1, allowing for the null termination character.new char[...]
, allocates memory on the heap to store the name; new
returns the address of the allocated memory.strcpy(..., n)
copies n to the allocated memory. Recall that the function's first argument is a pointer and that it returns a pointer.name(...)
forms the first element of the initializer list, initializing the name member variable.Sometimes, it's difficult to distinguish which copy operation a C++ statement represents. For example, the statement Person p2 = p1;
looks like an invocation of the assignment operator. However, it creates a new object, p2, by calling the copy constructor. To clarify the examples, the copy constructors and assignment operators announce when they run, a practice not included in "real" functions.
Person1: Array Version | Person2: Pointer Version |
---|---|
Person::Person(const Person& p) { cout << "Copy Constructor" << endl; memcpy(this, &p, sizeof(Person)); /*strcpy(name, p.name); weight = p.weight; height = p.height;*/ } |
Person::Person(const Person& p) { cout << "Copy Constructor" << endl; memcpy(this, &p, sizeof(Person)); name = new char[strlen(p.name)+1]; strcpy(name, p.name); //weight = p.weight; //height = p.height; } |
(a) | (b) |
Person1: Array Version | Person2: Pointer Version |
---|---|
Person& Person::operator=(Person& p) { cout << "Assignment Operator" << endl; if (this == &p) return *this; memcpy(this, &p, sizeof(Person)); /*strcpy(name, p.name); weight = p.weight; height = p.height;*/ return *this; } |
Person& Person::operator=(Person& p) { cout << "Assignment Operator" << endl; if (this == &p) return *this; memcpy(this, &p, sizeof(Person)); name = new char[strlen(p.name)+1]; strcpy(name, p.name); //weight = p.weight; //height = p.height; return *this; } |
The assignment operator has two tasks not shared with the copy constructor. First, it must check for self-assignment, for example, p = p
, skipping the copy if it is detected (blue). This test improves the function's efficiency by avoiding needless copy operations and protects the memcpy function (if used), which doesn't handle overlapping memory. Second, the function must return a reference to the operator's left-hand operand (pink), allowing programs to form operator chains: p3 = p2 = p1
. Member functions implementing overloaded operators refer to the left-hand operand with the this
pointer, but the function's return type is not a pointer. Therefore, the return
statements dereference this
, forming a return-by-reference.
Person1: Array Version | Person2: Pointer Version |
---|---|
istream& operator>>(istream& in, Person& me) { in.getline(me.name, 100); in >> me.weight; in >> me.height; return in; } |
istream& operator>>(istream& in, Person& me) { if (me.name != nullptr); // (i) delete me.name; char name[100]; // (ii) in.getline(name, 100); // (iii) me.name = new char[strlen(name)+1]; // (iv) strcpy(me.name, name); // (v) in >> me.weight; in >> me.height; return in; } |
(a) | (b) |
void Person::change(char* n, int w, double h) { strcpy(name, n); weight = w; height = h; } |
ostream& operator<<(ostream& out, Person& me) { out << me.name << ", "; out << me.weight << ", "; out << me.height; return out; } |
(a) | (b) |
Implementation | View | Download |
---|---|---|
Array | Person1.cpp | Person1.cpp |
Pointer | Person2.cpp | Person2.cpp |
//name = new char[strlen(p.name)+1]; //strcpy(name, p.name); name = strdup(p.name);I've found two common C++ compilers that recognize the strdup function, but it may not be compatible with all C++ compilers, making programs using it non-portable. Furthermore, it allocates memory on the heap, so programmers must remember to
delete
the duplicated C-string when appropriate to avoid a memory leak.The memcpy function copies all of an object's data, including the addresses saved in pointers, to another object. If the memcpy function call follows the name = new... statement, it overwrites the address just saved in name, introducing a memory leak and failing to copy the original object fully.