Copy constructors are constructors used to create a copy of an existing object; it takes one parameter that is of the same type as the class. When do we use such a thing?

  • Student a(b); when b is an existing instance of Student.
  • Student a = b; in the same line, where we create an object a and initialise it with b. Note here that we’re not calling the operator= function. a=b is where it’s called.
  • Pass an object by value (because we make a copy!).
  • Return an object by value.

By default, a non-empty copy constructor is implemented in every class by the compiler; it does a shallow copy of all fields. If we were to implement it ourselves, we must pass the object by reference or we get a compile-time error. If we pass by value, then the copy constructor will recursively call itself infinitely many times. Like other constructors, it has no return statement.

class Student {
	private:
		string name;
		int id;
	public:
		Student(Student& x) {
			name = x.name;
			id = x.id;
		}
}

Pointers

The default copy constructor does a shallow copy, i.e., it will copy a pointer from object A to object B, but not dynamically allocate new space.

We run into a problem: what happens when data members are pointers? In this case, having two independent objects point to the same string (for example) in memory can cause problems. If the string of one changes, then the other will change too, so they’re not independent objects. So we need to work around this by implementing our own copy constructor that doesn’t do a shallow copy.

The below has a fix. We dynamically allocate a new string for the new object. That wasn’t complicated at all!

#include <cstring>
 
class MyString {
	private:
		int len;
		char *buf;
	public:
		MyString() { len = 0; buf = NULL; }
		MyString(char* src) {
			buf = new char [strlen(src) + 1];
			strcpy(buf, src);
			len = strlen(src);
		}
		MyString(MyString& x) {
			len = x.len;
			buf = x.buf;
		}
		void setString (char* src) {
			if (buf != NULL) {
				delete [] buf;
			}
			buf = new char[strlen(src) + 1];
			strcpy(buf, src);
			len = strlen(src);
		}
		MyString (const MyString& x) { // copy constructor
			len = x.len;
			buf = new char[len + 1];
			strcpy(buf, x);
		}
		~MyString() {
			if (buf != nullptr) {
				delete [] buf;
				buf = nullptr;
			}
		}
}
 
int main () {
	MyString a("Hello");
	MyString b(a);
}

We pass by reference because if we pass by value, we copy in a copy of the input, so we’d have to call the copy constructor again. The compiler spits out an error if we don’t do so.