# Memory in C++ — new/delete, RAII, Smart Pointers, Copy vs Move and Exceptions

Source: https://skills101.in/learn/cpp/cpp-memory-and-smart-pointers — Skills101 free tutorial (C++ Programming). Updated 2026-11-09.

In C you `malloc` and `free` by hand. Modern C++ makes objects **clean up after themselves**, so leaks and double frees mostly disappear.

## 1. new and delete

```cpp
int *p = new int(42);        // one int on the heap
int *arr = new int[100];     // an array
delete p;                    // free one
delete[] arr;                // free an array (note the [])
```

Forgetting `delete` leaks memory; using `delete` instead of `delete[]` is undefined behaviour. In modern C++ you should **rarely write new/delete yourself**.

## 2. RAII: tie resources to object lifetime

![Inside a scope, a unique_ptr owns a Node object on the heap; when the scope ends, its destructor frees the Node automatically](https://skills101.in/learn-img/cpp/raii.svg)

**RAII** (Resource Acquisition Is Initialisation): acquire a resource in a constructor, release it in the destructor. Since destructors run automatically — even when an exception is thrown — the resource is always released. `string`, `vector`, `fstream`, `lock_guard` and smart pointers all work this way.

## 3. Smart pointers

```cpp
#include <iostream>
#include <memory>
#include <string>
using namespace std;

struct Node {
    string name;
    explicit Node(string n) : name(n) { cout << "create " << name << "\n"; }
    ~Node() { cout << "destroy " << name << "\n"; }
};

int main() {
    {
        unique_ptr<Node> a = make_unique<Node>("A");     // single owner
        unique_ptr<Node> b = move(a);                     // ownership moves to b
        cout << (a == nullptr) << " " << b->name << "\n";
    }                                                     // b goes out of scope → A destroyed
    shared_ptr<Node> s1 = make_shared<Node>("S");
    {
        shared_ptr<Node> s2 = s1;                         // two owners
        cout << s1.use_count() << "\n";
    }
    cout << s1.use_count() << "\n";
    return 0;
}                                                         // last owner gone → S destroyed
```

```text
create A
1 A
destroy A
create S
2
1
destroy S
```

| Smart pointer | Owners | Use for |
|---|---|---|
| `unique_ptr` | exactly one (can be **moved**, not copied) | the default choice |
| `shared_ptr` | many (reference counted) | genuinely shared ownership |
| `weak_ptr` | observes a shared_ptr without owning | breaking reference cycles |

## 4. Copy vs move, and the rule of three/five

Copying a big object (a vector with a million items) is expensive. C++11 added **move**: transfer the internals instead of copying them.

```cpp
#include <iostream>
#include <vector>
#include <utility>
using namespace std;

int main() {
    vector<int> big(1000000, 7);
    vector<int> copyOf = big;              // copies a million ints
    vector<int> movedTo = move(big);       // steals the buffer — very fast
    cout << copyOf.size() << " " << movedTo.size() << " " << big.size() << "\n";
    return 0;
}
```

```text
1000000 1000000 0
```

(After a move, the source is valid but unspecified — usually empty. Don't rely on its contents.)

**Rule of three / five:** if your class manages a raw resource and needs a custom destructor, it probably also needs a custom copy constructor and copy assignment (three), plus move constructor and move assignment (five). **Rule of zero:** better still, use `vector`, `string` and smart pointers so you need none of them.

## 5. Exceptions

```cpp
#include <iostream>
#include <stdexcept>
using namespace std;

double divide(double a, double b) {
    if (b == 0) throw invalid_argument("division by zero");
    return a / b;
}

int main() {
    try {
        cout << divide(10, 4) << "\n";
        cout << divide(1, 0) << "\n";
        cout << "not reached\n";
    } catch (const invalid_argument &e) {
        cout << "Error: " << e.what() << "\n";
    }
    cout << "continues\n";
    return 0;
}
```

```text
2.5
Error: division by zero
continues
```

Catch by **const reference**. Standard exceptions include `invalid_argument`, `out_of_range` (e.g. `vector::at`), `runtime_error`, `bad_alloc`.

## Common mistakes

- Raw `new` without a matching `delete` (use `make_unique` instead).
- Copying `unique_ptr` (compile error — use `move`).
- `shared_ptr` cycles (A owns B, B owns A) → never freed; use `weak_ptr` for one side.

## Quick recap

- Avoid raw new/delete; RAII cleans up in destructors.
- unique_ptr (default), shared_ptr (shared), weak_ptr (no ownership).
- Move transfers resources cheaply; follow the rule of zero.
- throw / try / catch (by const reference) for errors.


Practice quiz and free certificate: https://skills101.in/learn/cpp/cpp-memory-and-smart-pointers