Why do we need pointer?
I can think of two reasons:
dynamic storage.... but why new returns pointer?
function calls ... for the function to change parameters with call-by-pointer ... but there is an alternative way (call-by-reference) to accomplish the same thing. In fact, modern programming usually do away from call by pointer. So, the dominate reason for pointer is really the dynamic storage.
Pointer is an address
Pointer is an address, the location of what we're pointing to.
void pointerBasic() {
int x=3; // storage x is assigned during compile time, x is an int with value 3
cout << "x=" << x << '\t' << "&x=" << &x << endl; // &x is address of x
int *y = new int(5); // storage y is assigned during compile time,
cout << "y=" << y << '\t' << "&y=" << &y << "\t*y=" << *y << endl;
// y is not an int, y is a pointer (i.e. address) to int, y has an address too ..... hmm, what is the address of y?
// address of y is just like address of x above... they are variables locations
Array name is also a pointer
It is the starting address of the array. However, array name can not be changed. In other words, it is a "constant pointer".
int A[5] = {10,20,30,40,50}; // static array with initialization
cout << "A=" << A << " &A=" << &A << " *A=" << *A << endl;
cout << "A0=" << A[0] << endl;
A++ will generate a compiler error because A is "constant"; but A+1 is valid.
cout << "A+1=" << A+1 << '\t' << "*(A+1)=" << *(A+1) << '\t' << "*A+1=" << *A+1 << endl;
// note pointer+1 is actually increment by 4 (the sizeof int), so the meaning is increment by one element.
// A+2 is the same A[2], points to the 3rd element
int B[]= {10,20,30,40,50}; // static array with initialization
cout << "B[0]=" << B[0] << endl;
cout << "B=" << B << " &B=" << &B << "*B=" << *B << endl;
cout << "B+1=" << B+1 << '\t' << "*(B+1)=" << *(B+1) << '\t' << "*B+1=" << *B+1 << endl;
// note pointer+1 is actually increment by 4 (the sizeof int)
// B+2 is the same B[2], points to the 2nd element
// cout << B++; // it won't compile, because B is a "constant pointer"
cout << "pointer has no boundary A-5 is " << A-5 << endl;
cout << "B is " << B << endl;
cout << *(A-5) << '\t' << A[-5] << endl;
void pointerMath(){
int *p = new int[5]; // how does p differ from y above?
cout << "p=" << p << " &p=" << &p << endl;
int *tp = p;
for (int i=0; i<5; tp++)
*tp=(++i)*111;
// pointer math is element based, sizeof(element)
// pointer could go beyond its original intended space
int x=3;
x = *(--tp);
x= *(p+2);
tp = p; // two pointers to one object
delete(p); // Fall 16 update: this needs to be delete[] (p); since it is an array.
x=*tp; // dangling pointer is dangerous
}
Suppose a simple customer class has one pointer to a dynamic object (id in this case)
class Customer {
public:
Customer(string last, string first);
double *idp;
private:
string lname;
string fname;
};
void pointerInObj() {
Customer c1("lin","frank"),c2("obama","barack");
cout << sizeof(c1)<<endl;
id is a double (8 bytes), but idp field has only 4 bytes, because the 8 bytes are dynamically allocated somewhere else, not in the object. The object only has a pointer to it.
*c2.idp = 222; // did you notice the . has higher precedence than *
cout << *c1.idp << '\t' << *c2.idp <<endl;
c2=c1; // assignment
*c1.idp=9999;
cout << *c1.idp << '\t' << *c2.idp <<endl;
c1 is copied to c2. Then we change c1, c2 is also changed. That's not the intent, right?
}
Needs "deep copy" for dynamic storage to preserve data integrity
"Shallow copy" could also cause dangling pointer or memory leak
did you notice these two issues are twins?
Assignment operator peculiarity
Now we know we need to do deep copy for constructor and assignment. There is one more little implication. Assignment operator creates new dynamic storage for the left hand side object. The left hand side object has its original storage. The assignment gives him a brand new storage and replace its pointer. What happens to its old storage? If we do not take care of it, every assignment will leave a block of memory unused and uncollected. Make sure we delete those.
Shall we do the same for the constructor?
One nice-to-have code in assignment operator implementation is to check if it is assigned to itself, i.e. obj = obj. It saves execution time of copying itself.
Why not just copy right hand side dynamic storage to the left hand side?
So we do deep copy in the constructor and assignment operator. The process is to create a new storage, then copy right hand side storage to it, then delete the old one. One excellent question was asked, "why not copy the right hand side directly to the left hand side?" "Why do we go through the trouble of creating new, copy, then delete the old?"
Khalil has an excellent answer: the right hand side storage may not be the same as the left hand side. This is particularly true because many dynamically created storage could have different "length". For example, a "student rosters" class can have a dynamic array of "students". A "shopping cart" class can have a link list of "items".
Another reason is that the left hand side may not have a storage to begin with. This is true in some copy constructor situations.
Need to delete dynamic data in copy constructor to avoid memory leak
Do we really need to delete dynamic data in copy constructor to avoid memory leak? Frank thought so, but Brandon doesn't agree because
constructor of the form Customer c2(c1) creates "new" obj.
constructor of the form Customer c3 = c1 is covered by the assignment operator.
It is a convincing argument. For #1, yes, I think so too. For #2, I did a little googling to validate it. http://en.wikipedia.org/wiki/Copy_constructor has good info on copy constructor. In section 2.2 operation, it indicates
Object b = a; // translates as Object::Object(const Object&) (invoke copy constructor)
If that's true, then #2 is not really covered by the assignment operator.
The lessons from this exercise:
be careful (or mindful) about the dynamic storage
leave no unreachable dynamic memory (memory leak)
dangling pointer
when class has dynamic storage
is it a good/necessary tradeoff for static storage
deep copy in assignment operator
deep copy in copy constructor
release memory in destructor