Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 8 Next »

In C++, a copy constructor is a special constructor that is used to create a new object that is a copy of an existing object of the same class. The copy constructor is invoked whenever a copy of an object is made, either explicitly or implicitly.

The syntax for defining a copy constructor is similar to that of a regular constructor, but with a single argument that is a reference to the same class:

class MyClass {
public:
    MyClass();             // Default constructor
    MyClass(const MyClass& other); // Copy constructor
};

In the example above, the copy constructor takes a reference to another object of the same class as its argument. Inside the copy constructor, you typically create a new object that is a copy of the original object.

In C++, a copy constructor is a special type of constructor that initializes a new object as a copy of an existing object. It's a way to create an exact copy or "clone" of an object, with the same state (i.e., the same values for all its data members) as the original object.

The copy constructor is automatically called in three cases:

  1. When an object is created as a copy of another object (hence the name "copy constructor").

  2. When an object is passed by value as a parameter in a function.

  3. When an object is returned from a function by value.

If a copy constructor is not explicitly defined in a class, the compiler generates a default one. This default copy constructor performs a shallow copy, meaning it copies each member of the source object to the new object. This works fine for simple cases, but it can cause problems when the class involves dynamic memory management. If an object has pointers that are allocated memory and you use the default copy constructor, both the original and the copied object will point to the same memory location. This is a problem because when one of the objects is destroyed, it deallocates the memory, leaving the other object with a dangling pointer.

To solve this problem, you can define a custom copy constructor in which you allocate memory for the pointer in the new object and copy the actual value pointed to, instead of just copying the pointer. This is known as a deep copy.

Remember, the copy constructor function name is the same as the class name, and it takes only one argument: a reference to the object you want to copy. This is to avoid infinite recursion, as to copy the object you need to call the copy constructor, which needs to copy the object, and so on.

Here is an example of a class with a copy constructor:

#include <iostream>
#include <string>
using namespace std;
class Person {
public:
    Person(string name, int age);
    Person(const Person& other); // Copy constructor
    void introduce();
private:
    string name;
    int age;
};

Person::Person(string name, int age) {
    this->name = name;
    this->age = age;
}

Person::Person(const Person& other) {
    this->name = other.name;
    this->age = other.age;
}

void Person::introduce() {
    cout << "Hi, my name is " << name << " and I am " << age << " years old." << endl;
}

int main() {
    Person personOne("Alice", 25);
    // // Use copy constructor to create new object personTwo
    Person personTwo = personOne;
    
    personOne.introduce();
    personTwo.introduce();
    
    return 0;
}

In this example, the Person class has a copy constructor that takes a reference to another Person object as its argument. Inside the copy constructor, the new Person object is created by copying the name and age from the original object.

In the main function, two Person objects are created: p1 and p2. p2 is initialized using the copy constructor by passing p1 as an argument. Both objects are then introduced using the introduce function, and the output shows that they have the same name and age.

Using copy constructors is important for creating new objects that are exact copies of existing objects, especially when working with large or complex objects. It is also necessary when passing objects by value to functions or returning them from functions.

The provided C++ code demonstrates the use of a copy constructor in a Person class:

  1. The Person class:

    • It has a private name (string) and age (int) member variables.

    • It declares a public constructor that takes name and age as parameters and initializes the member variables.

    • It declares a copy constructor that takes another Person object as a parameter.

    • It declares a public introduce() method that prints the person's name and age.

  2. The constructor Person::Person(string name, int age):

    • It initializes the name and age member variables of the Person object using the provided arguments.

  3. The copy constructor Person::Person(const Person& other):

    • It creates a new Person object by copying the name and age values from another Person object (other).

  4. The introduce() method:

    • It prints the person's name and age using cout.

  5. In the main() function:

    • Two Person objects, personOne and personTwo, are created.

    • personOne is initialized with the name "Alice" and age 25 using the constructor.

    • personTwo is initialized with personOne using the copy constructor.

    • The introduce() method is called for both personOne and personTwo, printing their respective details.

By using the copy constructor, personTwo is created as a copy of personOne. This allows for the creation of a new object with the same values as an existing object.


In this case, we'll consider a class String that has a raw pointer char* as a member. This is a case where we need a custom copy constructor to perform a deep copy.

#include <iostream>
#include <cstring>

class String {
private:
    // buffer is a pointer to a character array.
    char* buffer;

public:
    // Constructor - takes a const char* argument and dynamically
    // allocates memory for buffer using new[] to store the string
    // passed as an argument.
    String(const char* str) {
        buffer = new char[strlen(str) + 1];
        strcpy(buffer, str);
    }

    // Copy Constructor - deep copy
    String(const String& pString) {
        buffer = new char[strlen(pString.buffer) + 1];
        strcpy(buffer, pString.buffer);
    }

    //desctructor
    ~String() {
        delete[] buffer;
    }

    // returns a constant pointer to the character buffer.
    const char* getValue() const { return buffer; }
};

int main() {
    // Create a string
    String strOne("Hello, World!");

    // Copy string strOne into strTwo
    String strTwo = strOne;

    std::cout << "strOne: " << strOne.getValue() << std::endl;
    std::cout << "strTwo: " << strTwo.getValue() << std::endl;

    return 0;
}

In the String class, we have a raw pointer buffer as a member. In the constructor, we allocate memory for buffer and copy the input string into buffer.

In the copy constructor, we also allocate new memory for buffer, ensuring it's a separate piece of memory from the original object's buffer. Then, we copy the content of the original object's buffer into the new buffer. This process is known as a "deep copy".

Finally, in the destructor, we free the memory that was allocated for buffer to prevent a memory leak.

The main function demonstrates the copy constructor. We create String strone and then use the copy constructor to create String strTwo as a copy of strOne. We then print out the values of strOne and strTwo to confirm they both contain the correct string.

The given C++ code demonstrates the implementation of a simplified String class with a dynamic character buffer:

  1. The String class:

    • It has a private member variable buffer, which is a pointer to a character array.

    • It includes a constructor that takes a const char* argument and dynamically allocates memory for buffer using new[] to store the string passed as an argument.

    • It includes a copy constructor that takes another String object as a parameter and performs a deep copy of the buffer by allocating memory and copying the contents from the source object.

    • It includes a destructor that frees the dynamically allocated memory using delete[].

    • It includes a getter function getValue() that returns a constant pointer to the character buffer.

  2. The constructor String::String(const char* str):

    • It determines the length of the input string using strlen().

    • It allocates memory for buffer with a size equal to the length of the input string plus one for the null terminator.

    • It copies the input string into buffer using strcpy().

  3. The copy constructor String::String(const String& s):

    • It determines the length of the source object's buffer using strlen().

    • It allocates memory for buffer with a size equal to the length of the source object's buffer plus one for the null terminator.

    • It copies the contents of the source object's buffer into buffer using strcpy().

  4. The destructor String::~String():

    • It frees the dynamically allocated memory for buffer using delete[].

  5. In the main() function:

    • An instance of String named strOne is created by passing the string "Hello, World!" to the constructor.

    • Another instance of String named strTwo is created by using the copy constructor and initializing it with strOne.

    • The values stored in strOne and strTwo are printed using the getValue() function.

This code demonstrates how to create a custom string class that manages dynamic memory allocation and provides the ability to copy objects correctly using the copy constructor and destructor.


Additional Example of the use of a Copy Constructor is in the Objects and Classes Section:

Aggregation in C++


Extended discussion on Copy constructors:

Copy constructors are an essential part of C++ because they help control how objects of a class get copied. There are several reasons why you might want to define a copy constructor:

  1. Deep copy: When you have a class that has raw pointers as data members, the default copy constructor performs a shallow copy, which can lead to problems. In a shallow copy, the pointer is copied, not the object it points to. Therefore, the source and copied objects end up sharing the same memory, leading to issues when one of them is modified or deleted. Defining a copy constructor allows you to perform a deep copy, where a new memory is allocated for the copied object, and the content is copied from the original object.

  2. Resource management: When you manage resources such as file handles or network connections, you may need to handle how these resources are shared or not shared between copies of objects.

  3. Reference counting: In some advanced cases, such as implementing a smart pointer, the copy constructor can be used to keep track of how many objects have been created as copies of a particular object. This is known as reference counting.

  4. Immutable objects: If you want to create an object that cannot be changed after it's created (an immutable object), you can use a copy constructor to ensure that each "change" actually creates a new object.

  5. Performance optimization: Sometimes, providing your own custom copy constructor can be more efficient than the default one provided by the compiler, especially when dealing with complex objects or large amounts of data.

However, remember that if you don't need to customize the copying behavior, you don't have to provide a copy constructor, and the compiler will generate one for you. With modern C++, best practices recommend using smart pointers and other techniques that avoid the need for explicit memory management and copy control.

  • No labels