Copying and assigning a data structure

Reading instruction

The beginning part of this section contains background information about the topic that you will learn more precisely on the later programming courses. If desired you can skip this part and move directly to the subsection Disabling the default copy constructor and assignment at the end of the section.

Revision: initializing means that you set an initial value to a variable at the same time as you declare it. Assigning means setting the variable value with the operator =.

Until now, there has been no immediate reason to pay attention to initializing and assigning, because C++ automatically creates so called copy constructor and assignment operator, even for data types defined by the programmer themself.

In practice, this means that the following code will work even though the programmer has not specified what needs to happen at initialization and assignment.

class My_class {
    ...
};
...
My_class object;
...
My_class object2{object};  // The copy constructor is called
...
object = object2;  // The assignment operator works as well

If we leave the assignment and initialization to the automated functionality of C++, by default, the initializing value and assigned value are copied into the target value, bit by bit.

With simple data structures (such as the object Player in counting points in Mölkky game), this is the desired result. However, if the data you want to copy includes pointers, and especially, if those pointers point to a dynamically allocated data structure, we encounter problems almost every time.

Let us imagine a situation which is not complicated in itself, but where we use a task list implemented with normal pointers:

List list;
...
if ( ... ) {
    List temp_list{ list };  // NOTE!!!
    ...
}
// This is where we went wrong.

Another situation just like this would be:

List list;
...
if ( ... ) {
    List temp_list;
    ...
    temp_list = list;  // NOTE!!!
    ...
}
// This is where we went wrong, again.

If you look at this on the surface, there is nothing wrong with it, but consider drawing a picture about our situation after executing the line where we added the word ”NOTE!!!”:

Figure on the execution at NOTE point in the above code

The problem comes to life the instant we leave the block where we defined temp_list as a local variable: The lifetime of temp_list comes to an end, and then its destructor is called.

The destructor deallocates all the memory that was allocated to temp_list, which happens to be the same memory that we allocated to list. The member variables of the variable list remain pointing to the memory that we already deallocated (dangling pointers).

In the end, the point of the example above is this: It is easy to get into trouble if you use the default copy constructor and assignment operators of C++ when the value you want to copy includes pointers.

By the way, it is worth noting that we will mess up in the following example as well (why?):

void function(My_class formal_parameter) {
    ...
}
...
int main() {
    My_class actual_parameter;
    ...
    function(actual_parameter);
    ...
}

The possible solutions to our problem about assignment and initializing are:

  • We disable the assigning and initializing of data types with self-made pointers altogether. This can be done such that the result of trying either of them will cause a compilation error.

  • For these data types, we implement versions of the assignment operator and copy constructors that really copy the elements from one structure to another. That is, they allocate new memory for each copied element with the command new, and by doing so, construct a new copy of the original structure from scratch. This approach is called deep copy.

    (A mechanism where you only copy the memory address (which often leads to trouble), is called shallow copy.)

On this course, we will only get to know the easier way where you disable the copying altogether. The deep copy will be postponed for the later courses, because it will require explaining and understanding parts of C++ that are, for now, irrelevant.

Disabling the default copy constructor and assignment

You can easily disable the use of C++’s default copy constructor and assignment operation by writing the following additions into the public interface of a class:

class List {
public:
    List(const List& initial_value) = delete;
    List& operator=(const List& assignable_value) = delete;
    ...
};

The types of the parameters must be const-references to the object of the class, and the assignment operator has to pass a reference to the object of the class.

On this course, it is good practice to do the disabling we discussed above every time you create a data structure that includes a dynamic data structure in its private part. When you do that, you avoid many errors and difficult debugging sessions.

Does this mean that it is not possible to define functions with self-made dynamic data structures given as their parameters? No, because it is still possible to define functions that do not need a copy constructor in their call: if the parameter type is reference or a const-reference. For example:

bool read_task_file(List& tasks);
bool save_task_file(const List& tasks);

Neither of them has any need for using a disabled copy constructor.

You need a copy constructor only when you are working with a value parameter, because the value parameter is initialized from an actual parameter by using a copy constructor.