|
|
|
|
Pointers in classes
What happens if the object is not so
simple? For example, what if the object contains pointers to other objects?
Simply copying a pointer means that you’ll end up
with two objects pointing to the same storage location. In situations like
these, you need to do bookkeeping of your own.
There are two common approaches to this
problem. The simplest technique is to copy whatever the pointer refers to when
you do an assignment or a copy-construction. This is
straightforward:
//: C12:CopyingWithPointers.cpp
// Solving the pointer aliasing problem by
// duplicating what is pointed to during
// assignment and copy-construction.
#include "../require.h"
#include <string>
#include <iostream>
using namespace std;
class Dog {
string nm;
public:
Dog(const string& name) : nm(name) {
cout << "Creating Dog: " << *this << endl;
}
// Synthesized copy-constructor & operator=
// are correct.
// Create a Dog from a Dog pointer:
Dog(const Dog* dp, const string& msg)
: nm(dp->nm + msg) {
cout << "Copied dog " << *this << " from "
<< *dp << endl;
}
~Dog() {
cout << "Deleting Dog: " << *this << endl;
}
void rename(const string& newName) {
nm = newName;
cout << "Dog renamed to: " << *this << endl;
}
friend ostream&
operator<<(ostream& os, const Dog& d) {
return os << "[" << d.nm << "]";
}
};
class DogHouse {
Dog* p;
string houseName;
public:
DogHouse(Dog* dog, const string& house)
: p(dog), houseName(house) {}
DogHouse(const DogHouse& dh)
: p(new Dog(dh.p, " copy-constructed")),
houseName(dh.houseName
+ " copy-constructed") {}
DogHouse& operator=(const DogHouse& dh) {
// Check for self-assignment:
if(&dh != this) {
p = new Dog(dh.p, " assigned");
houseName = dh.houseName + " assigned";
}
return *this;
}
void renameHouse(const string& newName) {
houseName = newName;
}
Dog* getDog() const { return p; }
~DogHouse() { delete p; }
friend ostream&
operator<<(ostream& os, const DogHouse& dh) {
return os << "[" << dh.houseName
<< "] contains " << *dh.p;
}
};
int main() {
DogHouse fidos(new Dog("Fido"), "FidoHouse");
cout << fidos << endl;
DogHouse fidos2 = fidos; // Copy construction
cout << fidos2 << endl;
fidos2.getDog()->rename("Spot");
fidos2.renameHouse("SpotHouse");
cout << fidos2 << endl;
fidos = fidos2; // Assignment
cout << fidos << endl;
fidos.getDog()->rename("Max");
fidos2.renameHouse("MaxHouse");
} ///:~
Dog is a simple class that
contains only a string that holds the name of the dog. However,
you’ll generally know when something happens to a Dog because the
constructors and destructors print information when they are called. Notice that
the second constructor is a bit like a copy-constructor except that it takes a
pointer to a Dog instead of a reference, and it has a second argument
that is a message that’s concatenated to the argument Dog’s
name. This is used to help trace the behavior of the program.
You can see that whenever a member
function prints information, it doesn’t access that information directly
but instead sends *this to cout. This in turn calls the
ostream operator<<. It’s valuable to do it this way
because if you want to reformat the way that Dog information is displayed
(as I did by adding the ‘[’ and ‘]’) you only need to do
it in one place.
A DogHouse contains a Dog*
and demonstrates the four functions you will always need to define when your
class contains pointers: all necessary ordinary constructors, the
copy-constructor, operator= (either define it or disallow it), and a
destructor. The operator= checks for self-assignment as a matter of
course, even though it’s not strictly necessary here. This virtually
eliminates the possibility that you’ll forget to check for self-assignment
if you do change the code so that it matters.
|
|
|