.. Copyright (C) Dave Parillo. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with Invariant Sections being Forward, and Preface, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". .. index:: pair: copy; constructor Copying objects =============== C++ is one of the few languages that provides precise control over how memory is managed. In C++11, every class is created with 5 special functions: - destructor - copy constructor - move constructor - copy assignment operator - move assignment operator In many classes, such as most of those written so far, the compiler generated default versions work fine. As we will see, sometimes you have to write them yourself. Programmers have choices on how (or if) objects are copied and moved. Whenever an object is passed by value to a function, or returned by value from a function, a copy is implicitly performed: .. code-block:: cpp std::vector scores; auto avg = average (scores); // a copy of scores is passed to average Copy operations also occur in range-for loops: .. code-block:: cpp for (const int value: scores) Each member of ``scores`` is copied into ``value`` on each iteration. Explicit copying can also be performed. Whenever you have an existing object and use it to initialize a new or existing object, the copy constructor is called: .. code-block:: cpp std::vector words; std::vector w2 = words; // copy words into w2 Copy constructors ----------------- Both explicit and implicit copies are controlled by a special constructor called the *copy constructor*. Like other constructors, the copy constructor is a member function with the same name as the class name. The signature **must** be able to evaluate to this: .. code-block:: cpp class_name ( const class_name & ); A copy constructor *may* take other parameters, but that is uncommon. If there are other parameters, they **must all have defaults values defined**. In general, the default copy constructor generated by the compiler will suffice. If the default creation is inhibited for any reason, it is acceptable to explicitly declare the default constructor: .. code-block:: cpp class_name ( const class_name & ) = default; However the default copy constructor is created, the behavior is the same: each class member is copied, in initialization order. .. tb-group:: :name: copy_construct_tab .. tb-tab:: Copy constructor A simple class with a default and a copy constructor. .. code-block:: cpp struct A { int n; double d; // user defined default constructor A(int n = 0, double d = 1) : n{n} , d{d} { } // user defined copy constructor A(const A& other) : n{other.n} , d{other.d} { } }; In this case, the user defined copy constructor does what the default constructors would do. When that is the case, it's best not to redo the work of the compiler. .. tb-tab:: Run It .. tb-code:: cpp :name: ac_class_copy_and_default_constructor #include struct A { int n; double d; // user defined default constructor A(int n = 0, double d = 1) : n{n} , d{d} { std::cout << "default A\n"; } // user defined copy constructor A(const A& other) : n{other.n} , d{other.d} { std::cout << "copy into A\n"; } }; int main() { A a; A b = a; return b.n; } When objects manage their own resources, simple member-wise assignment cannot be used. Consider a small ``mesa::string`` class. It owns a dynamically allocated character array, but it does not define its own copy constructor. That means the compiler-generated copy constructor copies each data member directly, including the pointer. .. tb-code:: cpp :name: ac_shallow_copy_string_class :caption: A shallow copy problem :show-tutor: #include #include #include #include #include #include namespace mesa { class string { public: string() = default; explicit string(std::string_view source) : size_{source.size() + 1}, data_{new char[size_]} { std::copy(source.begin(), source.end(), data_); data_[source.size()] = '\0'; } void upper_case() { for (std::size_t i = 0; i + 1 < size_; ++i) { data_[i] = std::toupper(data_[i], std::locale()); } } ~string() { // Uncomment this line after running the program once. // A correct string class must release its memory. // What happens if we cleanup the string memory? // delete[] data_; } const char* c_str() const { return data_ == nullptr ? "" : data_; } private: std::size_t size_ = 0; char* data_ = nullptr; }; } // namespace mesa int main() { mesa::string hello{"Hello, world!"}; mesa::string copy = hello; copy.upper_case(); std::cout << "hello: " << hello.c_str() << '\n'; std::cout << "copy: " << copy.c_str() << '\n'; } Even though we copied ``hello``, changing the case of ``copy`` also changed the original. The compiler-generated copy constructor copied the pointer value, not the character array pointed to by the pointer. After the copy, both objects point to the same memory. When we copy a value, we expect a *cloned object*: an object that has the same value, but is separate and distinct. We do **not** want changes in one object to affect the other. The destructor body is commented out so the program can run by default. If you uncomment ``delete[] data_;`` and run the program again, the shallow copy has an even more serious consequence: both objects will try to release the same memory. Because there are two pointers to the same data on the free store, when either is deleted, the free-store memory is recovered. Consider this sequence: .. code-block:: cpp mesa::string hello{"Hello, world!"}; // create a new scope { mesa::string copy = hello; } // local variable copy destroyed std::cout << hello.c_str() << '\n'; What does the last line print if the destructor deletes ``data_``? .. tb-reveal:: :name: reveal_str_copy_ube There is no way to know for sure. A modern compiler should detect the double delete and fail to compile. But that behavior is not perfect. When ``copy`` goes out of scope and its destructor is called, it deletes the memory ``copy::data`` points to, but this is the same array ``hello`` is using. When ``hello.c_str()`` is called, undefined behavior is the result. Fixing these problems requires writing a custom copy constructor. Each class member needs to be copied correctly. The member ``size_`` can simply be copied. The pointer member needs special treatment: - Initialize a new memory block large enough to hold the copy - Copy each element of the source array into the destination. In contrast to a *shallow* copy, this copy is a **deep copy**. It does not copy the pointer value. It creates an entirely new pointer and copies all of the data pointed to by the source pointer to the destination. .. admonition:: Try This! Add a copy constructor to ``mesa::string``. The copy should preserve the value of the source string, but the copy and the original should not share the same character array. After the copy constructor works, uncomment the destructor body and confirm that the program still runs. Copy assignment --------------- The copy assignment operator is similar to the copy constructor. The key difference to remember is that a copy **constructor** is only called when the left hand side object does not yet exist: it is in the process of being constructed. .. code-block:: cpp X& X::operator=(const X& other) { // copy other content into this return *this; } .. tb-group:: :name: copy_assign_tab .. tb-tab:: Copy assignment Copy **assignment** is called when both *already exist* and you want to copy the right hand side object into the left hand side object. .. code-block:: cpp struct A { int n; double d; // user defined default constructor A(int n = 0, double d = 1) : n{n} , d{d} { } // user defined copy constructor A(const A& other) : n{other.n} , d{other.d} { } A& operator=(const A& other) { if (this == &other) { return * this; } n = other.n; d = other.d; return * this; } }; .. tb-tab:: Run It .. tb-code:: cpp :name: ac_class_copy_and_copy_assignment #include struct A { int n; double d; // user defined default constructor A(int n = 0, double d = 1) : n{n} , d{d} { std::cout << "default A\n"; } // user defined copy constructor A(const A& other) : n{other.n} , d{other.d} { std::cout << "copy into A\n"; } A& operator=(const A& other) { if (this == &other) { return * this; } std::cout << "copy assign A\n"; n = other.n; d = other.d; return * this; } }; int main() { A a; A b = a; a = b; return b.n; } .. admonition:: Try This! Write a copy assignment function for the ``mesa::string`` class. ----- .. admonition:: More to Explore - From cppreference.com: - :lang:`Copy constructors ` - :cpp:`Null-terminated byte strings `