Embercode
✕
Pointers and Memory AddressesLesson 5 of 16

50 min read

Smart pointers, an introduction

Why this matters

The last lesson ended with a warning. Every new needs one matching delete, on every path, and missing one causes a leak, a double free or a use after free. Real programs have many paths. A function may return early, throw an exception or call another function that throws. Remembering every delete by hand is hard, and it gets harder as the program grows. Modern C++ solves this with smart pointers. A smart pointer is a small object that holds a heap address and deletes it for you when it goes away. You still allocate memory, but the cleanup is tied to the lifetime of a variable, so the compiler inserts the delete in the right place.

Interviewers ask about this directly. "Why would you use unique_ptr instead of new?" is a common opener, and a good answer says what ownership means and how the cleanup happens without you writing it. This lesson gives you the words and the working code.

The idea of ownership

Ownership is the most important word in this lesson. The owner of a heap block is the one piece of code responsible for freeing it. Every heap block should have exactly one owner, or a clear rule for sharing ownership. When the owner goes away, the block goes away.

Think of a rental car. One person signs the rental agreement, drives the car, and returns it when the trip ends. Nobody else can return it for them. This is single ownership, and it is what unique_ptr expresses.

Now think of a library book that a whole family uses. Each reader takes a copy of the borrow card. The book is returned to the shelf only when the last reader closes the card. Nobody can throw it away while someone still holds a card. This is shared ownership, and it is what shared_ptr expresses.

A third idea is a note that says "the book is over there". The note does not hold the book and does not stop anyone from returning it. You check the note before use and may find the book has gone. That is a weak reference, and weak_ptr expresses it.

These three tools cover nearly every heap ownership question you will meet.

RAII: cleanup tied to scope

The technique behind smart pointers has a name: RAII, which stands for Resource Acquisition Is Initialization. It is a long name for a simple idea. Acquire a resource, such as heap memory, in the constructor of an object. Release it in the destructor of that object. Then the resource lives exactly as long as the object does.

A destructor is a function that runs automatically when an object goes out of scope, which means when the block that declared it ends. You do not call it yourself. The compiler inserts the call. A unique_ptr is an object whose destructor deletes the pointer it holds. So when the unique_ptr goes out of scope, the memory goes back to the heap, whether the function returns normally or leaves early.

The rest of this lesson uses one example to show the rule in action. It defines a small class that prints when it is created and destroyed, so you can see the timing with your own eyes.

Worked example 1: unique_ptr for one value

unique_ptr is the default smart pointer. It owns one object, and it cannot be copied. Create one with make_unique, which is the safe way to allocate.

C++ · editable

Output:

value: 42
after change: 50
is empty? 0

The star and the arrow work the same way as for a raw pointer. *p reaches the int, and p-> reaches the members of a class. The difference is what happens at the end of main. The unique_ptr goes out of scope, and its destructor calls delete on the int. You never write the delete, so you cannot forget it.

The variable p holds an address, but it is no longer the same kind of thing as an int*. You cannot write p + 1. You cannot assign an int to it. This restriction is the point. A smart pointer is a pointer with rules that prevent most of the mistakes from the first two lessons.

Worked example 2: moving ownership

Because a unique_ptr has one owner, you can hand ownership to someone else but you cannot share it. The tool for handing it over is std::move.

C++ · editable

Output:

a is null: 1
b holds: 5

After move(a), the int belongs to b, and a is empty. The word move here is about ownership, not about copying the bytes. The int is not duplicated. The pointer inside a is transferred to b, and a is left holding nothing.

This is the rule that matters most. After a move, the moved-from unique_ptr is null. Dereferencing it crashes the program. Always check the state of a unique_ptr after a move, or simply do not use it again. The mistakes section shows the crash.

unique_ptr a owns an int before the move, and after the move b owns it while a is emptyunique_ptr a owns an int before the move, and after the move b owns it while a is empty

The statement unique_ptr<int> c = a; does not compile. A unique_ptr cannot be copied, because two copies would both try to delete the same block. The compiler stops you before the bug can happen. That single rule is why unique_ptr is safe.

Worked example 3: shared ownership with a counter

Sometimes two parts of a program really do need the same object, and neither is clearly in charge. shared_ptr handles that. It keeps a count of how many shared pointers point at the object. Each copy adds one to the count, and each one that goes out of scope subtracts one. When the count reaches zero, the object is deleted.

C++ · editable

Output:

count after first: 1
count inside block: 2
count after block: 1
value: 9

Trace the count. make_shared creates the int and one owner, so the count is 1. The inner block copies first into second, and the count rises to 2. The write through second changes the shared int to 9. When the block ends, second is destroyed and the count falls to 1. The int is still alive, because first still owns it, so the last line prints 9.

The method use_count() is useful for learning and debugging. Do not use it to make decisions in real code, because another thread might change the count between your check and your action. Use it in a test to confirm your understanding.

Worked example 4: a destructor that shows the timing

This example makes the cleanup visible. The class Tracker prints a message in its constructor and its destructor. A function returns a unique_ptr to a new tracker. The caller keeps it in a variable, and a nested block shows that the object dies when its owner's block ends.

C++ · editable

Output:

open file-a
working with file-a
open file-b
inner scope
close file-b
end of main
close file-a

Read the output in order. file-a opens in main. Then file-b opens inside the nested block. When the block ends, inner is destroyed, and its destructor prints close file-b. The message end of main appears next. t is destroyed last, when main returns, and that prints close file-a.

Notice the order of the closes. The object that opened last closed first, because scopes end in reverse order. This last-in, first-out order is the same as the stack discipline from the last lesson. Destructors run in the reverse of the order that constructors ran.

Notice too that the function open returned a unique_ptr by value. The return moved ownership to the caller, and no delete was written anywhere. This is the cleanest pattern for returning heap objects. Compare it with the raw pointer version from lesson 4, which needed the caller to remember a delete.

Worked example 5: a linked list whose nodes free themselves

Linked lists are the best place to see smart pointers at work. Each node owns the next node, so the head of the list owns the whole chain. Freeing the list is then automatic: when the head goes away, every node goes away with it.

C++ · editable

Output:

3 2 1 

The picture in the visualizer shows the head pointing at the newest node, and each node pointing at the next. Notice how pushFront works. It creates a new node, moves the old head into the new node's next, and then moves the new node into the head. The old head is never lost, and no node is ever owned by two places at once.

Step through the list below. Watch which pointer changes at each step, and check that every node keeps exactly one owner.

headnull

Each node holds a value and a pointer (dot and arrow) to the next node. An outlined node is where the walk is now. A filled node is new or found.

The list is empty. head points to nothing.

Step 1 of 16

Two details are worth knowing. The first is that the function takes the head by reference, unique_ptr<Node>&, because it must change the head. A plain unique_ptr by value would move ownership into the function, and the list would be empty when the function returned. The second is that the loop in main uses head.get() and cur->next.get(). The get method returns the raw pointer inside, so the loop can walk the list without taking ownership. Reading through a raw pointer is fine while the owner is still alive.

When the program ends, head goes out of scope, which destroys the first node, which destroys its next, and so on down the chain. Nothing is leaked, and nothing is deleted twice. The ownership structure guarantees both.

One caution. Destroying a very long list this way can use deep recursion, because each node's destructor calls the next one's. A list of a million nodes can exhaust the stack. Production code either keeps lists shorter, or writes a loop that moves each node out before it is destroyed. That is an advanced fix, and the module assignment returns to it.

What happens inside a smart pointer

A unique_ptr is a small class that holds one raw pointer. Its destructor calls delete on that pointer when the object goes out of scope. Its move constructor takes the raw pointer from the source and sets the source's pointer to nullptr. That is all. It adds almost no cost, because the compiler inlines the calls and the work is the same as writing the delete yourself. The gain is correctness, not speed.

A shared_ptr is a little larger. It holds the raw pointer and a second pointer to a control block, which stores the count of owners. make_shared allocates the object and the control block in one heap block, which is faster than two separate allocations. Each copy increments the count, and each destruction decrements it, and the last one deletes the object. The count is updated safely even when several threads share the pointer, which adds some cost. That is why unique_ptr is the default and shared_ptr is the exception.

The compiler decides when the destructor runs, from the scope rules you already know. You do not need to manage it. You do need to know the scope, because a smart pointer that lives in a long-running loop keeps its object alive for the whole loop.

Reading smart pointer code quickly

Smart pointer code is easier to read once you know what each piece tells you.

unique_ptr<T> p means that p owns one T and deletes it when p goes out of scope. A variable of this type is an owner.

shared_ptr<T> p means that p is one of several owners. The object lives as long as any of them does.

weak_ptr<T> w means that w observes an object without keeping it alive. Its lock() method gives a shared_ptr, or an empty result if the object is gone.

A parameter of type unique_ptr<T> by value means the function takes ownership. A parameter of type unique_ptr<T>& means the function may change the owner's pointer. A parameter of type T& or T* means the function borrows the object.

The methods get(), * and -> reach the object without changing ownership. The call move(p) transfers ownership, and afterwards p is null. When you read such code, find the owner of each object first, then check every move and every by-value parameter. Those three things show where ownership goes.

Choosing a pointer type

By now you have seen several ways to hold an object. Most programs need only a few. Use this table to decide, and read the reason in the last column before you pick one.

What you needUseWhy
A value that lives in one placea plain variable or a vectorno heap, no delete, no ownership question
One owner of one heap objectunique_ptr<T> made with make_uniquethe owner deletes it, and it cannot be copied by mistake
Several owners that share one objectshared_ptr<T> made with make_sharedthe object lives until the last owner goes away
A look at an object someone else ownsT& or T*a borrow that does not change ownership
A look that may outlive the ownerweak_ptr<T>it can check whether the object still exists
A list of many heap objectsvector<unique_ptr<T>>the vector deletes each element when it is destroyed

Two rows need attention. The first is the borrow row. Most functions should borrow, because a borrow says nothing about lifetime, and the caller keeps control. The second is the vector row. A vector of unique_ptr is the usual way to hold many heap objects, because no loop with delete is ever needed.

A common beginner habit is to reach for shared_ptr everywhere. It works, but it hides who owns what, and every copy costs a count update. Reach for it only when ownership really is shared.

Passing smart pointers to functions

The parameter type says what the function does with ownership, and this is a common interview question.

If a function takes unique_ptr<T> by value, it takes ownership. The caller must move the pointer in, and after the call the caller no longer owns it.

If a function takes unique_ptr<T>&, it may change which object the pointer owns, as pushFront did. The caller keeps ownership.

If a function only needs to use the object, it should take T& or T*. This does not transfer or share ownership. It only borrows. Most functions should take the object this way.

If a function takes shared_ptr<T> by value, it becomes an owner and adds to the count. If it takes const shared_ptr<T>&, it borrows the pointer without changing the count.

The simple rule is to pass the least power you need. A function that only reads an object should borrow it, and only a function that stores the object for later should take ownership.

Tracing reference counts on paper

The count from use_count() can be predicted, and predicting it is a good test of your understanding. Here is worked example 3 with one column for each owner.

Stepfirst ownssecond ownsuse_count()
make_shared<int>(7)the intnothing1
inside the block, second = firstthe intthe int2
*second = 9the int, now 9the int, now 92
block ends, second destroyedthe int, now 9gone1
*first readthe int, now 9gone1

The count tracks owners, not names. The name second ends at the closing brace, and the count falls by one at that moment. The int dies only when the count reaches zero, which here does not happen until first goes away at the end of main. Writing the count in a column makes it obvious when a copy still holds the object, which is the most common surprise with shared_ptr.

Why make_unique and make_shared are safer

Both make_unique and make_shared create the object and wrap it in one step. Using them is a habit to keep, and the reason is exception safety. That means keeping the program correct when an error is thrown partway through a statement.

Consider a call that takes two arguments, one a new smart pointer and one a function that might throw. Before C++17 the compiler was free to evaluate the arguments in any order. It could run the new, then the throwing function, and only then build the smart pointer. If the throw happened in between, the raw block would never be wrapped, so nothing would delete it. The block would leak.

make_unique and make_shared close that gap. The allocation and the wrapping happen inside one function call, so no moment exists when a raw pointer sits outside a smart pointer. C++17 tightened some ordering rules, but the function form is still the safe habit.

A second reason is speed. make_shared allocates the object and its control block in one heap block, which is one allocation instead of two. A third reason is clarity. The line auto p = make_unique<Node>(v); shows at once that a new object is being created and who owns it. A raw new hides the owner.

A case study: from raw new to unique_ptr

Take a small structure written in the style of lesson 4: a list of nodes, each made with new, and a function that frees the list by hand.

The raw version needs three pieces of discipline. Every node is made with new. The free function must walk the list and delete each node once, saving the next pointer before the delete. Every early return in the code that builds the list must free what it has built so far. Each piece is easy to get wrong, and the wrong version compiles without complaint.

The smart pointer version changes the question. Instead of asking "who frees this?" in every function, the code asks "who owns this?" once, in the type. Each node holds its next node in a unique_ptr, and the head holds the first node. The compiler now inserts the deletes, in the right order, on every path, because the destructors do the work.

Three things remain the programmer's job. The first is breaking cycles. A doubly linked list with unique_ptr in both directions would form a loop of owners that never frees, so one direction must be a raw pointer that borrows. The second is avoiding very deep recursion when a long list is destroyed, which the module assignment asks you to handle. The third is deciding which pointer is the owner in each place. A smart pointer makes that decision visible, but it does not make it for you.

The change is worth making in every new program. A reviewer reading the raw version must trace every path to confirm the frees. A reviewer reading the smart pointer version reads the types, and the types say who owns each node.

Common mistakes and the messages you see

The first mistake is copying a unique_ptr. Here is a program that tries:

#include <memory>
int main() {
    auto p = std::make_unique<int>(5);
    auto q = p;
    return 0;
}

The compiler refuses with a long message, and the key part reads:

error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&)'

The message says the copy constructor is deleted. In plain words, a unique_ptr cannot be copied. Use std::move(p) to hand it over, or pass a borrowing reference.

The second mistake is using a moved-from pointer. After b = move(a), the pointer a is null. Writing *a crashes the program with a segmentation fault, which is the same crash you met with a raw null pointer in lesson 1. The rule is simple: after a move, check for nullptr before you use the variable, or do not use it at all.

The third mistake is giving two shared_ptr objects the same raw pointer. Here is the bug:

int* raw = new int(1);
shared_ptr<int> a(raw);
shared_ptr<int> b(raw);

Each shared_ptr counts itself as the only owner, so each one deletes the block when it goes out of scope. The second delete is a double free, and the program usually aborts when it leaves the scope. The fix is to create the object once with make_shared<int>(1), or to copy one shared_ptr into the other. Never build two smart pointers from one raw pointer.

The fourth mistake is keeping a raw pointer from get() after the smart pointer is gone. The raw pointer is only a borrowed address. Once the owner is destroyed, the raw pointer dangles, which is the use-after-free bug from lesson 4. Use get() only to borrow for a short time, while the owner is alive.

The fifth mistake is a shared cycle. If node A holds a shared_ptr to B, and B holds a shared_ptr back to A, their counts never reach zero. Neither is deleted, and the memory leaks even though no code points at them from outside. The fix is to make one direction a weak_ptr.

Debugging walkthrough: the crash after a move

Suppose your program crashes with a segmentation fault right after you move a unique_ptr into a function, and the function then uses the original variable. Work through it in order.

Step 1: find the line that moved the pointer. Look for move or for passing by value.

Step 2: find the first use after that line. That use reads a null pointer. The segmentation fault tells you the crash is on that line, not in the move.

Step 3: decide whether the caller should still own the object. If the function was meant to borrow, change its parameter to T&, and the move disappears.

Step 4: if the function really takes ownership, do not use the variable after the move. Restructure the code so the owner is the new holder.

Step 5: add a nullptr check before the crash site while you investigate. The check shows you the state, and the program still runs during the search.

Variations and edge cases

A unique_ptr to an array is written unique_ptr<int[]>, and it is created with make_unique<int[]>(n). Its destructor calls delete[], so the mismatch from lesson 4 cannot happen. For most interview code, a vector is simpler still.

A unique_ptr can have a custom deleter, which is a function that runs instead of delete. This is useful for file handles and for memory from C libraries. You will rarely need one in an interview, but knowing that the deleter exists explains how the smart pointer can manage more than heap memory.

A weak_ptr observes a shared_ptr without adding to its count. To use the object, you call lock(), which returns a shared_ptr if the object still exists, or an empty one if it has been destroyed. Calling expired() tells you whether the object is gone. A weak_ptr is the standard way to break cycles and to keep a cache that does not keep its contents alive.

An empty unique_ptr is a valid value, and a function can return one to say "no result". Check for it with if (p) or if (p == nullptr).

Interview-style questions

Interviewers like to ask about ownership because it shows whether you understand memory without writing a single line of pointer code.

They ask the difference between unique_ptr and shared_ptr. The answer: unique_ptr has one owner, cannot be copied, costs almost nothing, and is the default. shared_ptr counts its owners, can be copied, costs a little more, and should be used only when ownership is really shared.

They ask why you should prefer make_unique to new. The answer: make_unique builds the object and the pointer in one step, so an exception thrown partway through cannot leave a raw block behind. It also reads more clearly, because the ownership is visible in the type.

They ask what RAII means. The answer: a resource is acquired in a constructor and released in a destructor, so the release happens automatically when the object leaves its scope. Smart pointers, file streams and lock guards all work this way.

They ask how a memory leak can happen with shared_ptr. The answer: a cycle of owners keeps every count above zero, so nothing is deleted even when no code outside the cycle uses it.

Interview follow-ups on smart pointers

After you explain the three smart pointers, an interviewer often asks one or more of these questions.

Why is unique_ptr the default? Answer that most objects have one clear owner, that a unique_ptr states this in its type, that it costs the same as a raw pointer, and that it cannot be copied by accident. Shared ownership is a later decision, made when two owners really exist.

What does shared_ptr cost? Answer that each copy updates a reference count, that the count is safe across threads and so costs more than a plain increment, and that the control block is an extra allocation unless make_shared merges it with the object.

How do you break a cycle? Answer with weak_ptr. Name the direction that should own, make the back link weak, and say that the code calls lock() before each use and checks for an empty result.

Why not use shared_ptr everywhere? Answer that it hides ownership, makes destruction order harder to predict, and can keep objects alive long after anyone needs them.

Keep each answer to the rule, one example and the cost. An interviewer who hears those three things knows you can use the tools and choose between them.

Real-world uses

Real C++ code uses smart pointers for almost every heap object. Game engines hold entities in unique_ptr containers, so each entity is deleted when it is removed from the world. Web servers use shared_ptr for configuration objects that many request handlers read at once. Graphics code uses unique_ptr for GPU resources, with custom deleters that release the device handle. The pattern is always the same: say who owns the object, and let the type do the cleanup.

When not to use a smart pointer

A smart pointer is a tool, and most code does not need one. Prefer the first option in this list that works.

Use a local variable when the object lives inside one function. It sits on the stack, it is freed automatically, and the compiler handles everything.

Use a vector, string or map when the data is a collection. These types manage their own heap memory, so you never write new for them.

Use a reference or a raw pointer when you only borrow an object. A raw pointer that owns nothing is still a valid tool, and the function's name should say that it borrows.

Use a smart pointer when the lifetime of an object must be controlled, when it lives in a structure whose size changes, or when several owners must agree on when it dies. A linked list, a tree or a cache of shared objects are typical cases.

If you find yourself wrapping every object in shared_ptr to avoid thinking about ownership, stop and redesign. The goal is one clear owner for each object. A smart pointer is the way to express that owner, not a replacement for deciding it.

Practice

Exercise 1. Create a unique_ptr<double> that holds 2.5, multiply it by 4 through the pointer, and print the result (10). Do not write any delete.

Solution
unique_ptr<double> p = make_unique<double>(2.5);
*p = *p * 4;
cout << *p << endl;

The output is 10. The unique_ptr deletes the double when p goes out of scope, so no delete is written.

Exercise 2. Create a unique_ptr<int>, move it into a second unique_ptr, then print whether the first is null. Predict the output first.

Solution
unique_ptr<int> a = make_unique<int>(1);
unique_ptr<int> b = move(a);
cout << (a == nullptr) << endl;

It prints 1. After the move, b owns the int and a is null.

Exercise 3. Write a function void show(const int& x) and call it with the value inside a unique_ptr<int>. Explain why the function borrows and does not take ownership.

Solution
void show(const int& x) {
    cout << x << endl;
}

The call is show(*p);. The parameter is a reference, so the function sees the int without owning it. The caller's unique_ptr keeps ownership, and the int is still deleted when the caller's pointer goes out of scope.

Exercise 4. Write a class Logger that prints start in its constructor and stop in its destructor. Create one inside a block, and show with the output that stop comes before the line after the block.

Solution
C++ · editable

The output is start, working, stop, after block. The destructor runs at the closing brace, before the next statement.

Exercise 5. Create two shared_ptr<int> objects that point at the same value. Print use_count() before, during and after a block that holds a third copy.

Solution
shared_ptr<int> a = make_shared<int>(3);
shared_ptr<int> b = a;
cout << a.use_count() << endl;
{
    shared_ptr<int> c = a;
    cout << a.use_count() << endl;
}
cout << a.use_count() << endl;

The three lines print 2, 3 and 2. The count rises by one for the copy inside the block and falls when the copy is destroyed.

Exercise 6. Write a function that takes unique_ptr<int> by value and returns nothing. Call it with a unique_ptr created in main, then check whether main's pointer is null afterwards. Explain why.

Solution
void consume(unique_ptr<int> p) {
    cout << *p << endl;
}

The call is consume(move(q));, where q was made with make_unique<int>(5). The function prints 5. Afterwards q is null, which prints 1 for (q == nullptr). The parameter took ownership through the move, and it was destroyed when the function returned, which freed the int. A by-value unique_ptr parameter always means the caller gives up ownership.

Exercise 7. Build a linked list with three nodes using unique_ptr<Node> and print the values front to back. Then add a pushBack function that walks to the end and attaches a new node. Test it with two values.

Solution
void pushBack(unique_ptr<Node>& head, int v) {
    auto node = make_unique<Node>(v);
    if (head == nullptr) {
        head = move(node);
        return;
    }
    Node* cur = head.get();
    while (cur->next != nullptr) {
        cur = cur->next.get();
    }
    cur->next = move(node);
}

Starting from the list 1, 2, 3, the calls pushBack(head, 4) and pushBack(head, 5) make the list print 1 2 3 4 5. The walk uses a borrowed raw pointer, cur, which is safe because the list still owns every node during the walk. The empty-list branch is needed because head.get() would be null there.

Exercise 8. Explain why shared_ptr<int> a(raw); shared_ptr<int> b(raw); is a bug, and write the correct version using make_shared.

Solution

Each shared_ptr counts itself as the only owner of raw. Both will delete the block when they go out of scope, so the second delete is a double free. The correct version creates the object once and shares it:

shared_ptr<int> a = make_shared<int>(1);
shared_ptr<int> b = a;

Now both names share one control block and one int, and the count is 2 until both are gone.

Exercise 9. Explain what weak_ptr is for and what lock() returns after the owner has been destroyed.

Solution

A weak_ptr observes a shared_ptr's object without keeping it alive. It is used for caches, and for links that would otherwise form a cycle of owners. After the owner has been destroyed, lock() returns an empty shared_ptr, which compares equal to nullptr. The caller checks that result before using the object.

Exercise 10. Explain in a short paragraph why a unique_ptr costs about the same as writing the delete yourself, but is safer. Use the words owner and scope.

Solution

A unique_ptr holds one raw pointer and calls delete on it when it goes out of scope. That is the same work you would write by hand, so the cost is about the same. The difference is that the owner is the variable itself, and the delete happens at the end of its scope on every path, including early returns and exceptions. You cannot forget it, and the compiler stops you from making a second owner by copying it.

What you now know

  • Ownership means one piece of code is responsible for freeing each heap block.
  • RAII ties a resource's release to the destructor of an object, so cleanup runs when the object leaves scope.
  • unique_ptr has one owner, cannot be copied, can be moved, and is the default choice.
  • After a move, the moved-from unique_ptr is null, and using it crashes.
  • shared_ptr counts its owners and deletes the object when the count reaches zero.
  • A cycle of shared_ptr objects leaks, and weak_ptr breaks the cycle.
  • Pass a smart pointer by value to transfer ownership, by reference to change the pointer, and by plain reference or raw pointer to borrow the object.
  • Use make_unique and make_shared, and never build two smart pointers from one raw pointer.

Lesson 6 is the module assignment. It asks you to use everything in this module, from raw pointers and arithmetic to smart pointers, in one small program that reads input, builds a list and checks its own memory.

Practice this topic: coding problems for Pointers and Memory Addresses →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Stack, heap, new and delete