---
title: 'Copying and Assignment'
source: 'https://academia.sh/en/courses/cpp-fundamentals/copying-and-assignment'
course: 'C++ Fundamentals and Object Design'
language: en
updated: '2026-09-26T03:02:45+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Distinguishes when the copy constructor is called from when copy assignment is called'
    - 'Measures that a class taking its own array produces a deep copy'
    - 'Applies why the self-assignment check is necessary'
---

# Copying and Assignment

Measures that the copy constructor and copy assignment operator produce a deep copy that takes its own array, and that the depth of the copy depends on the design of the class.

The previous lesson claimed that a guard holds its resource by value, so when the guard is
copied, its resource is copied along with it. This lesson measures that claim: when an
object is copied, does the copy share the same memory as the original, or does it take its
own memory? The measurement kind is **copy**: `b`, constructed from `a` with
`Buffer b(a)`, is a newly constructed object, different from `a` — but how deep that
"different" runs depends on how the class's copy constructor is written.

In C, copying an array was always an **explicit** operation: `memcpy` is called by hand,
the destination being large enough is checked by hand, and the source and destination not
overlapping is guaranteed by hand. In C++, when a class object is copied via `=` or passed
by value as an argument, the copying is **implicit** — the compiler decides on its own which
function to call, no explicit call is written by hand. This makes copying easier, but it
also hides it: seeing a line like `Buffer b(a);` and understanding how much work it does
(allocating new memory, copying element by element) requires looking at the class's
definition — the line itself does not say.

## Copy Constructor and Copy Assignment

A class's **copy constructor** is a constructor that takes a `const&` of the same class —
`Buffer(const Buffer& other)`. This constructor is called during a **construction**, as in
`Buffer b(a);` or `Buffer b = a;`: `b` does not yet exist, and is constructed from `a`.
**Copy assignment**, on the other hand, is an overload of `operator=` —
`Buffer& operator=(const Buffer& other)`. This is called during an **assignment**, as in
`b = a;`: `b` already exists (it was constructed earlier), and only its value is replaced
with `a`'s. Both take `const Buffer&` as their argument type, but one constructs a new
object while the other changes the contents of an existing object — this difference is what
determines which one is called: whether the object on the left is new or already existing.

This distinction is not merely syntactic. While the copy constructor is running, the
object's data members are not yet **constructed** — the same rule from the third lesson's
member initializer list applies here too: `data_(new int[other.size_])` initializes the
member for the first time. While copy assignment is running, on the other hand, the object
is already constructed, and `data_` already points at a valid array — this is why
`operator=` **must** first release the old array (`delete[] data_`); otherwise the old array
is never freed while a new one is assigned over it, which is a memory leak. The copy
constructor has no such prior state, because the object does not yet exist — this is why the
copy constructor never calls `delete[]`, it only allocates.

```cpp
// buffer.hpp — a class holding its own array, copying deeply
#ifndef BUFFER_HPP
#define BUFFER_HPP

#include <cstdio>

class Buffer {
public:
    explicit Buffer(int size) : size_(size), data_(new int[size]) {
        for (int i = 0; i < size_; ++i) {
            data_[i] = i;
        }
        std::printf("constructor ran size=%d\n", size_);
    }

    Buffer(const Buffer& other) : size_(other.size_), data_(new int[other.size_]) {
        for (int i = 0; i < size_; ++i) {
            data_[i] = other.data_[i];
        }
        std::printf("copy constructor ran size=%d\n", size_);
    }

    Buffer& operator=(const Buffer& other) {
        if (this == &other) {
            return *this;
        }
        delete[] data_;
        size_ = other.size_;
        data_ = new int[size_];
        for (int i = 0; i < size_; ++i) {
            data_[i] = other.data_[i];
        }
        std::printf("copy assignment ran size=%d\n", size_);
        return *this;
    }

    ~Buffer() {
        delete[] data_;
    }

    int first_element() const { return data_[0]; }
    void set_first_element(int value) { data_[0] = value; }
    const int* address() const { return data_; }

private:
    int size_;
    int* data_;
};

#endif
```

## A Deep Copy Takes Its Own Array

```cpp
// copy-measurement.cpp — is b, freshly constructed from a, a copy that takes its own array
#include <cstdio>

#include "buffer.hpp"

int main() {
    Buffer a(3);
    Buffer b(a);

    std::printf("do a and b share an array %s\n", a.address() == b.address() ? "yes" : "no");

    b.set_first_element(99);
    std::printf("a first element %d\n", a.first_element());
    std::printf("b first element %d\n", b.first_element());
    return 0;
}
```

```text
constructor ran size=3
copy constructor ran size=3
do a and b share an array no
a first element 0
b first element 99
```

**CR10.** `Buffer b(a);` constructs `b` from `a` with the copy constructor; the copy
constructor reads `other.size_` and allocates a separate array with its **own** call to
`new int[other.size_]`, then copies `other.data_`'s elements into this new array one by
one. This is a **deep copy**: `b.data_` and `a.data_` are different addresses,
`a.address() == b.address()` prints `no`. This is why `b.set_first_element(99)` changes
only `b`'s own array; `a.first_element()` is still `0`. `b` is of kind **copy** relative to
`a`: newly constructed, different from `a` in address, but equal to it in the value it held
at the moment of construction.

## Copy Assignment and the Self-Assignment Check

```cpp
// assignment-measurement.cpp — does b = a run copy assignment
#include <cstdio>

#include "buffer.hpp"

int main() {
    Buffer a(2);
    Buffer b(5);

    b.set_first_element(-1);
    a.set_first_element(42);
    b = a;
    std::printf("after assignment is b's first element the same as a's %s\n",
                b.first_element() == a.first_element() ? "yes" : "no");
    std::printf("do a and b share an array %s\n", a.address() == b.address() ? "yes" : "no");

    b = b;
    std::printf("after self-assignment is b still valid %s\n", b.first_element() == a.first_element() ? "yes" : "no");
    return 0;
}
```

```text
constructor ran size=2
constructor ran size=5
copy assignment ran size=2
after assignment is b's first element the same as a's yes
do a and b share an array no
after self-assignment is b still valid yes
```

**CR11.** In the line `b = a;`, `b` is already a constructed object, so copy assignment is
called, not the copy constructor. `operator=` first releases `b`'s **old** array with
`delete[] data_`, then allocates a new array of the same length as `a`'s and copies
`a.data_`'s contents into it — this is why, after the assignment, `b`'s first element
equals `a`'s (the contents were copied), but `b.address()` is **not** equal to
`a.address()` (a new array was allocated, not shared). `b`'s address before the assignment
is not separately printed and compared here: whether the `new` that immediately follows the
`delete[]` returns the same address depends on the allocator, and the C++ standard does not
guarantee this — so such a comparison would not count as evidence, only the contents and
the not-shared-with-`a` state are measured. The line `b = b;` triggers the
`if (this == &other) return *this;` check at the start of `operator=`: without this check,
the function would first release `b`'s own array with `delete[]`, then try to read
`other.data_` (which is now the same, already-released, invalid address) — this would be
undefined behavior. Thanks to the check, `b = b;` safely returns early, and `b` remains
unchanged.

## The Risk of a Shallow Copy, and the Rule of Three

If `Buffer`'s copy constructor and copy assignment operator were not written by hand, the
compiler would use its own generated default versions — these copy every data member
**member by member**. Because `size_` is an `int`, this causes no problem, but `data_` is an
`int*`; member-by-member copying copies the pointer itself, not the array it points at.
This is a **shallow copy**: two `Buffer`s would share the same `data_` address. The result of
a shallow copy is this — when one is destroyed, it runs `delete[] data_`, while the other
still points at the same (now released) array; when that object is also destroyed, it tries
to release the same array again with `delete[]`. This **double free** is undefined
behavior, and this lesson does not run it — it is only mentioned in prose; `Buffer` is
always run with the hand-written deep copy constructor and assignment.

This observation leads to the **rule of three**: if a class has to write its destructor,
copy constructor, or copy assignment operator by hand (usually because it holds a resource
through a raw pointer), it must write all three by hand — if one is written by hand and the
other two are left to the compiler, the risk of a shallow copy arises. `Buffer` fully
applies this rule: the destructor, the copy constructor, and copy assignment, all three
manage `data_` correctly.

There is a reason the rule is limited to "three": all three are different faces of the same
problem — ownership of the resource. The destructor defines when the resource is released,
the copy constructor defines how the resource is duplicated, and copy assignment defines
how an existing resource is changed. If one is forgotten (for example, only the destructor
is written and the copy constructor is left to the compiler), the compiler-generated
shallow copy constructor shares `data_` — then two objects each try to release the same
array with `delete[]`, which is a double free. The mere existence of a destructor is itself
a sign that the other two must also be written by hand: if a class does something special in
its destructor (something beyond just `delete[]`), this usually means the class is the
**owner** of a resource, and ownership must also be thought through during copying.

## Preventing Copying with `= delete`

Not every class needs to be copyable. If a resource is meant to have a single owner — for
example, a file handle or a network connection should not be shared with a second object —
the copy constructor and copy assignment can be explicitly **deleted** with `= delete`:
`Buffer(const Buffer&) = delete;`. This does not tell the compiler to never generate that
function, but to give a compile error when it is used — code that tries to write
`Buffer b(a);` does not compile. `Buffer` remains copyable for this lesson's measurement,
`= delete` is only mentioned here; which classes should be designed as non-copyable, and how
that decision relates to the ownership model, is K05's topic.

## Why the Copy Constructor's Argument Is `const&`

The copy constructor's argument is taken as `const Buffer&`, not as `Buffer`. If the
argument were taken by value (`Buffer other`), calling the function would itself require a
copy of `other` — and the function that would construct that copy would be the copy
constructor itself, an infinite loop. Taking it by reference breaks this loop: the original
object is accessed directly, with no extra copy produced. `const`, meanwhile, guarantees
that `other` will only be read, not modified during the copy — the copy constructor must
not modify its source, only read from it.

## A Copy Does Not Reveal the Depth of the Source

Like the `Guard` from the previous lesson, `Buffer` also holds its resource by value — but
while `Guard`'s `resource_` member is a `Resource` **value**, `Buffer`'s `data_` member is
an `int*`; the resource (the array) lives on the heap, and the member is only a pointer to
it. This lesson closes that gap by writing `Buffer` **by hand** to copy deeply, but the
depth of a copy is never automatic — it depends on what the class's copy constructor
writes. If a class is tied to a record shared through a raw pointer (for example, if the
`int*` only points at an array owned by another object, and ownership is not with
`Buffer`), member-by-member copying also copies this pointer, and the copy shares the same
record as the original — a deep copy only happens when the class is the **owner** of its
own resource, and the copy constructor explicitly reallocates it.

This means that different members of the same class can call for different copying
behavior. If `Buffer` also had a member named `const int* reference_`, pointing at a
resource it does not own, it would be correct for the copy constructor to **deliberately**
copy this member shallowly — copying the pointer as it is, without allocating new memory —
because `Buffer` is not the owner of that resource, it only looks at it. The question that
must be asked when writing a copy constructor is this: is this member a resource this
object owns, or only a resource it looks at? The answer can differ from member to member,
and the copy constructor's body is the sum of these decisions — the compiler-generated
default member-by-member copy never makes this distinction, it copies every member the same
way (shallowly).

Move — a resource being transferred by **taking it over** rather than by copying it —
solves this problem in a different way, and is outside this course's scope (K05). M11/K01
covered a struct being copied whole, field by field, through assignment, and memory
layout — member order, alignment, and padding; this lesson combines the two: copying
conforms both to value semantics (a copy is independent of the original) and to memory
management (a copy takes its own memory) at the same time.

## The Bounding Measurement

The expression this lesson measures is of kind **copy**: `b` is newly constructed from `a`,
an object separate from `a` in address. But a copy does not, by itself, say how "deep" it
is — this information is not in the result of the copy, it is hidden in how the class's
copy constructor behaves with its source. `Buffer` copies deeply because its copy
constructor allocates a new array with `new int[...]`; a class tied to a resource through a
raw pointer, one that has not written its own copy constructor correctly, copies shallowly,
and the copy shares the same resource as the original. To read a class and answer the
question "can this be copied, and what happens when it is copied," you have to look at the
body of the copy constructor — the class's name or the way it is used does not say.

## Summary

- The expression this lesson measures is of kind **copy**: `Buffer b(a)` is newly
  constructed from `a`, an object separate from it in address.
- The copy constructor is called during a **construction**, copy assignment during an
  **assignment**; whether the object on the left is new or already existing determines
  which one runs.
- `Buffer` produces a **deep copy** by allocating its own array with `new`; member-by-member
  copying would only copy the pointer, which would be a **shallow copy**.
- Copy assignment safely handles **self-assignment** with the `this == &other` check.
- If one of the destructor, the copy constructor, or copy assignment is written by hand, all
  three must be written (the rule of three).

## Next Step

This lesson measured that an object takes its own resource when it is copied; the next
lesson moves away from copying and measures how a **reference** directly modifies the
caller's object — producing no copy at all.
