Why this matters
A program uses resources that the operating system counts and limits. A file handle, a block of memory, a network connection and a lock are all resources. Each one must be given back when the program is done with it. If it is not given back, the program leaks it. A leaked file stays open, a leaked lock stays locked, and a leaked memory block stays reserved until the program exits. In a long-running server, these leaks slowly break the program.
The usual problem is not the normal path. The normal path ends with a line like close(file); or delete[] data;. The trouble starts when the function leaves early, through a return in the middle, or through an exception. The cleanup line at the bottom never runs. Both C++ and Java have a way to make the cleanup happen on every exit path, without you writing it in each place. In C++ the technique is called RAII, which is covered below. In Java it is the try statement with resources.
By the end of this lesson you will write a C++ class that releases its resource in its destructor, explain the order in which C++ destroys objects, use the standard library types that already do this, write a Java try-with-resources block with more than one resource, and read the suppressed exceptions that Java keeps. The lesson does not repeat the class basics from the earlier lessons in this module, such as constructors and destructors. It also does not cover the Java finally keyword in depth.
The problem with manual cleanup
Here is a small C++ function that allocates a block of memory, uses it, and frees it.
int sum_first(int n) {
int* data = new int[n + 1]{};
if (n == 0) {
return 0;
}
int total = data[0];
delete[] data;
return total;
}The keyword new asks for memory on the heap. The heap is the part of memory that lives until the program deletes it, unlike a local variable that disappears when its function ends. The matching delete[] gives the memory back. In this function, the return 0; on the third line leaves before delete[] runs. The block of memory is lost.
You can see the loss with AddressSanitizer, a checking tool built into g++. Put the function in a file with a main that calls sum_first(0), compile with g++ -std=c++20 -g -fsanitize=address early.cpp -o early, and run it. The leak report looks like this:
==694531==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 4 byte(s) in 1 object(s) allocated from:
#0 0x7a9dae32c8cf in operator new[](unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:111
#1 0x55c98cc6e580 in sum_first(int) /home/chiranjeet/dev/anisha/scratch-offtmp/claude-lessons-enum-raii/l16/early.cpp:7
#2 0x55c98cc6e895 in main /home/chiranjeet/dev/anisha/scratch-offtmp/claude-lessons-enum-raii/l16/early.cpp:28
SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).The report names the line where the memory was allocated, which is line 7 of the file, the new line.
The addresses, process ID, folder names and line numbers depend on your machine and file, so yours will differ. The fix is to make the cleanup happen whatever the exit path. The two languages do this in different ways, so the lesson takes them one at a time, starting with C++.
C++: the scope owns the cleanup
RAII stands for "Resource Acquisition Is Initialization". The name is hard to remember, so think of it as one rule: get the resource in a constructor, and give it back in the destructor. A constructor is the function that runs when an object is created. A destructor is the function that runs when an object is destroyed. In C++ a destructor has the class name with a tilde in front, such as ~Guard.
The important part is when a destructor runs. For a local object, the destructor runs when the object goes out of its scope. A scope is a pair of braces { }, or the body of a function. The destructor runs when the program leaves that scope, for any reason. That includes the end of the block, a return, and an exception that moves control to a catch further up. So the cleanup sits in one place, the destructor, and runs on every exit.
A first example: the order of destruction
The first example prints a message when each object is created and destroyed, so you can see the order.
#include <iostream>
#include <string>
class Noisy {
public:
explicit Noisy(std::string name) : name_(std::move(name)) {
std::cout << "open " << name_ << '\n';
}
~Noisy() {
std::cout << "close " << name_ << '\n';
}
private:
std::string name_;
};
int main() {
{
Noisy a("a");
Noisy b("b");
std::cout << "end of block\n";
}
}The explicit keyword on the constructor stops the compiler from turning a plain string into a Noisy on its own. The member name_ holds the text, and it has a trailing underscore so the reader can tell it apart from a parameter. The code compiles cleanly with g++ -std=c++20 -Wall -Wextra -pedantic noisy.cpp -o noisy. The output is:
open a
open b
end of block
close b
close aObjects are destroyed in the reverse order of their creation. b was created second, so it is destroyed first. This is called last in, first out, and it matters when one object depends on another. An object created later may use an object created earlier, so it must be cleaned up first.
A second example: an exception still runs the destructors
The next example adds a function that throws an exception halfway through. An exception is a signal that something went wrong. It stops normal flow and jumps to the nearest catch block that accepts it. Here is the function with two local objects and an exception.
#include <iostream>
#include <stdexcept>
#include <string>
class Noisy {
public:
explicit Noisy(std::string name) : name_(std::move(name)) {
std::cout << "open " << name_ << '\n';
}
~Noisy() {
std::cout << "close " << name_ << '\n';
}
private:
std::string name_;
};
void work(bool fail) {
Noisy first("first");
Noisy second("second");
if (fail) {
throw std::runtime_error("boom");
}
std::cout << "work done\n";
}
int main() {
try {
work(true);
} catch (const std::exception& e) {
std::cout << "caught " << e.what() << '\n';
}
std::cout << "---\n";
work(false);
}Calling work(true) throws before work done is printed. The output shows that both destructors ran before the catch in main printed its message:
open first
open second
close second
close first
caught boom
---
open first
open second
work done
close second
close firstThis is the main benefit of RAII. The function has no cleanup code at all. It does not need a try block around its body, and it does not need a close line before each return. The destructors do the work on the normal path and on the exception path.
Two details are worth noting. The catch takes const std::exception&, a reference to the exception. A reference lets the code read the exception without copying it. The e.what() call returns the message text. The second detail is the order: the destructors run while the exception is still moving up the stack, before the catch block starts.
A third example: a class that owns heap memory
A class that owns a resource has three jobs. It must acquire the resource in the constructor, release it in the destructor, and decide what happens when an object is copied or moved. Copying matters because two copies of an object that both release the same memory would release it twice, and that is a serious bug. The example below stores a block of integers on the heap.
#include <cstddef>
#include <iostream>
#include <utility>
class IntBuffer {
public:
explicit IntBuffer(std::size_t size) : size_(size), data_(new int[size]{}) {
std::cout << "allocated " << size_ << " ints\n";
}
~IntBuffer() {
delete[] data_;
std::cout << "freed " << size_ << " ints\n";
}
IntBuffer(const IntBuffer&) = delete;
IntBuffer& operator=(const IntBuffer&) = delete;
IntBuffer(IntBuffer&& other) noexcept
: size_(std::exchange(other.size_, 0)), data_(std::exchange(other.data_, nullptr)) {}
int& at(std::size_t i) { return data_[i]; }
std::size_t size() const { return size_; }
private:
std::size_t size_;
int* data_;
};
IntBuffer make(std::size_t n) {
IntBuffer b(n);
b.at(0) = 42;
return b;
}
int main() {
IntBuffer a = make(3);
std::cout << "first = " << a.at(0) << '\n';
IntBuffer b = std::move(a);
std::cout << "moved size = " << b.size() << '\n';
}The constructor allocates the memory with new int[size]{}. The braces at the end set every integer to zero. The destructor frees it with delete[], which is the matching form for an array. The copy constructor and the copy assignment operator are marked = delete. That tells the compiler to refuse any code that copies an IntBuffer. The move constructor is different. It takes the memory from other and leaves other empty. A move hands over the resource instead of duplicating it.
The function std::exchange(other.data_, nullptr) does two things at once. It returns the old pointer and sets other.data_ to nullptr, which means "points to nothing". Because other is now empty, its destructor frees nothing. Compiled and run, the program prints:
allocated 3 ints
first = 42
moved size = 3
freed 3 ints
freed 0 intsThe line freed 0 ints comes from the object a, which was moved from. Its destructor still runs and still prints a message, but it has no memory to free. The allocated line appears only once. The compiler usually builds the local b in make directly as a, so there is no second object to move from or destroy. Since copying is forbidden, the code needs the move constructor for the case where that shortcut does not apply, and g++ rejects make without it.
Standard library types that already do RAII
You rarely need to write a class like IntBuffer yourself. The standard library provides types that release their resource automatically. Use them before you reach for new and delete.
| Type | What it owns | Released when |
|---|---|---|
std::vector and std::string | A growing block of memory | The object is destroyed |
std::unique_ptr | One object on the heap | The pointer is destroyed |
std::ofstream and std::ifstream | A file | The stream is destroyed |
std::lock_guard | A lock on a mutex | The guard is destroyed |
A std::unique_ptr holds one heap object and deletes it when the pointer goes out of scope. Creating one with std::make_unique avoids writing new at all. Here is a short program that moves a pointer from one variable to another.
#include <iostream>
#include <memory>
struct Node {
explicit Node(int v) : value(v) { std::cout << "make " << v << '\n'; }
~Node() { std::cout << "drop " << value << '\n'; }
int value;
};
int main() {
auto a = std::make_unique<Node>(1);
auto b = std::move(a);
std::cout << (a == nullptr) << ' ' << b->value << '\n';
std::unique_ptr<Node> c;
std::cout << (c ? "has" : "empty") << '\n';
}After the move, a holds nothing, and b owns the Node. The output is:
make 1
1 1
empty
drop 1Only b deletes the Node, so drop 1 is printed once. The pointer c never held anything, so it has nothing to drop.
A std::lock_guard takes a lock when it is created and releases it when it is destroyed. A mutex is a lock that only one thread can hold at a time. The guard makes sure the lock is released even if the function returns early. Here is a function that uses a guard, followed by a file write that closes itself.
#include <fstream>
#include <iostream>
#include <mutex>
#include <string>
std::mutex m;
int counter = 0;
void bump(bool stop_early) {
std::lock_guard<std::mutex> lock(m);
if (stop_early) {
return;
}
++counter;
}
int main() {
bump(true);
bump(false);
std::cout << "counter " << counter << '\n';
{
std::ofstream out("notes.txt");
out << "line one\n";
}
std::ifstream in("notes.txt");
std::string line;
std::getline(in, line);
std::cout << "read back: " << line << '\n';
}The call bump(true) returns early, before ++counter. The guard still releases the mutex, so the next call bump(false) can take it again. Without the guard, the second call would wait for a lock that is never released. The output is:
counter 1
read back: line oneThe std::ofstream in the inner block is closed when the block ends. Closing a file flushes the text it holds to disk, so the later ifstream can read line one. The closing happens in the destructor, so the code does not need a close() call.
Writing your own guard
You can build a small RAII class to run any cleanup code. The class below takes a function in its constructor and calls it in its destructor.
#include <functional>
#include <iostream>
#include <utility>
class Guard {
public:
explicit Guard(std::function<void()> on_exit) : on_exit_(std::move(on_exit)) {}
~Guard() { on_exit_(); }
Guard(const Guard&) = delete;
Guard& operator=(const Guard&) = delete;
private:
std::function<void()> on_exit_;
};
int main() {
{
Guard g([] { std::cout << "cleanup ran\n"; });
std::cout << "in scope\n";
}
std::cout << "after scope\n";
}The output is:
in scope
cleanup ran
after scopeThe std::function<void()> type holds any function that takes no arguments and returns nothing. A lambda, which is a short unnamed function written inline, fits. Copying is deleted for the same reason as in IntBuffer: two guards that both run the cleanup would run it twice.
Java: try-with-resources
Java has garbage collection, so you never free memory yourself. A garbage collector finds the objects the program can no longer reach and frees their memory. Garbage collection does not close files, network connections or database connections, because those are outside the program's memory. Those resources need an explicit close() call, and that call must happen on every path.
Java's answer is the try-with-resources statement. A class that holds a resource implements the interface AutoCloseable, which requires a method close(). An interface is a list of methods a class promises to provide. Put the resource in parentheses after the word try, and Java calls close() when the block ends. The example below defines a class that prints when it opens and closes.
public class Res implements AutoCloseable {
private final String name;
private final boolean failOnClose;
Res(String name, boolean failOnClose) {
this.name = name;
this.failOnClose = failOnClose;
System.out.println("open " + name);
}
String name() {
return name;
}
@Override
public void close() {
System.out.println("close " + name);
if (failOnClose) {
throw new IllegalStateException("close failed: " + name);
}
}
}The @Override annotation asks the compiler to check that close really replaces a method from the interface. A misspelt name would then be an error, not a silent new method.
A first example: two resources and their order
The next program uses two Res objects in one try, then uses one Res in a block that throws.
public class TwrMain {
public static void main(String[] args) {
try (Res a = new Res("a", false); Res b = new Res("b", false)) {
System.out.println("body uses " + a.name() + " and " + b.name());
}
System.out.println("---");
try (Res a = new Res("a", false)) {
System.out.println("using " + a.name());
throw new RuntimeException("body failed");
} catch (RuntimeException e) {
System.out.println("caught " + e.getMessage());
}
System.out.println("---");
try (Res c = new Res("c", true)) {
System.out.println("using " + c.name());
throw new RuntimeException("body failed");
} catch (RuntimeException e) {
System.out.println("caught " + e.getMessage());
for (Throwable s : e.getSuppressed()) {
System.out.println("suppressed " + s.getMessage());
}
}
}
}Save Res.java and TwrMain.java in the same folder, then run javac Res.java TwrMain.java and java TwrMain. The output on JDK 25 is:
open a
open b
body uses a and b
close b
close a
---
open a
using a
close a
caught body failed
---
open c
using c
close c
caught body failed
suppressed close failed: cThe resources are closed in the reverse order they were opened, the same last in, first out rule as in C++. The first block ends normally, so both are closed before the program moves on. In the second block the body throws, and a is still closed before the catch runs. The third block shows what happens when closing also fails.
Suppressed exceptions
In the third block, the body throws body failed. Then close() for c throws close failed: c. Java cannot throw two exceptions at once, so it keeps the first one, body failed, as the main exception. The second one is attached to it as a suppressed exception. The getSuppressed() method returns the list of attached exceptions. Without this rule, a failing close would hide the real cause of the error. The lesson here is to read getSuppressed() when you debug a failure that involves a resource.
A second example: reading and writing files
The next program is more practical. It writes two lines to a file and reads them back. Each stream is opened in its own try-with-resources, so each is closed before the next part runs.
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class CopyFile {
public static void main(String[] args) throws IOException {
Path path = Path.of("notes.txt");
try (BufferedWriter out = Files.newBufferedWriter(path)) {
out.write("first line");
out.newLine();
out.write("second line");
out.newLine();
}
try (BufferedReader in = Files.newBufferedReader(path)) {
String line;
while ((line = in.readLine()) != null) {
System.out.println("read: " + line);
}
}
System.out.println("bytes: " + Files.size(path));
}
}Run it in an empty folder, because it writes notes.txt in the current folder. The output is:
read: first line
read: second line
bytes: 23The file holds 23 bytes: first line is 10 characters, second line is 11, and each newLine() adds one more byte for the line break on Linux. The method readLine() returns null at the end of the file, which ends the loop. The throws IOException on main tells the caller that a file operation can fail. The try-with-resources does not remove that need. It only makes sure the streams are closed.
The older way with finally
Before Java 7, a program had to write the cleanup by hand, using a finally block. A finally block runs whether the try block finishes normally or throws. The next program is the old form of the file read.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class OldStyle {
public static void main(String[] args) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new StringReader("one\n"));
System.out.println(reader.readLine());
} finally {
if (reader != null) {
reader.close();
}
}
}
}It prints one. The reader variable has to be declared before the try, set to null, and checked for null before closing. Every extra step is a place for a slip. The try-with-resources form does the same job in fewer lines, and it gives you the suppressed exception rule for free. Prefer it whenever the resource is an object that implements AutoCloseable.
Common mistakes
Each mistake below comes with the message the tools printed.
C++: a raw new with no matching delete
The sum_first function near the start of the lesson is the usual form of this mistake. A new on one line needs a delete on every path out of the function. Early return, throw, and a break out of a loop all skip code that sits after them. The rule is simple: if you find yourself writing new, ask whether a std::unique_ptr, a std::vector or a std::string can own the memory instead. In most code they can.
C++: copying an object that owns a resource
If you copy an IntBuffer, the two copies would both free the same memory. The class prevents this by deleting the copy constructor. The file below copies the object.
#include <cstddef>
class IntBuffer {
public:
explicit IntBuffer(std::size_t size) : data_(new int[size]{}) {}
~IntBuffer() { delete[] data_; }
IntBuffer(const IntBuffer&) = delete;
private:
int* data_;
};
int main() {
IntBuffer a(3);
IntBuffer b = a;
}The compiler refuses it with this message:
buffer_copy.cpp:12:19: error: use of deleted function 'IntBuffer::IntBuffer(const IntBuffer&)'
buffer_copy.cpp:6:5: note: declared hereThe first line points to line 12, where a is copied into b. The second line points to line 6, where the copy constructor was deleted. To fix the code, either move the object with std::move(a), or pass it by reference so no copy is made.
Java: a resource that is not AutoCloseable
Only a class that implements AutoCloseable can go in the parentheses. A String cannot, because it has no close() method. The program below is wrong.
public class NotCloseable {
public static void main(String[] args) {
try (String s = "text") {
System.out.println(s);
}
}
}javac prints this:
NotCloseable.java:3: error: incompatible types: try-with-resources not applicable to variable type
try (String s = "text") {
^
(String cannot be converted to AutoCloseable)
1 errorThe last line of the message says what is missing. Either use a type that can be closed, such as a BufferedReader, or do not put the variable in the parentheses.
Java: using a resource after the try block
Once the try-with-resources block ends, the resource is closed. Using it after the block is a runtime error, not a compile error, so the compiler does not catch it. The program below shows this mistake with the Java 9 form, which lets you name an existing variable in the parentheses.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class Closed {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new StringReader("one\ntwo\n"));
try (reader) {
System.out.println(reader.readLine());
}
System.out.println(reader.readLine());
}
}The output is:
one
Exception in thread "main" java.io.IOException: Stream closed
at java.base/java.io.BufferedReader.ensureOpen(BufferedReader.java:127)
at java.base/java.io.BufferedReader.readLine(BufferedReader.java:325)
at java.base/java.io.BufferedReader.readLine(BufferedReader.java:400)
at Closed.main(Closed.java:11)The first readLine works, and the block closes the reader. The second call fails with Stream closed. The fix is to keep every use of the resource inside its try block. Stack trace lines are shortened here.
Practice
-
In C++, a function creates two local objects
xandy, then returns early before the end of the function. Which destructor runs first, and why?Show the answer:
yis destroyed first. Destruction runs in reverse order of creation, so the object created last goes first. The earlyreturndoes not change this. Every local object still in scope is destroyed when the function leaves. -
Write a C++ class
Guardthat takes astd::function<void()>in its constructor and calls it in its destructor. Delete the copy constructor. Test it with a block that printsin scope.Show the answer: The
Guardclass in the section "Writing your own guard" does this. Running it printsin scope, thencleanup ran, thenafter scope. The copy constructor is deleted so two guards cannot run the same cleanup. -
Write a Java method
static String firstLine(Path path) throws IOExceptionthat opens the file withFiles.newBufferedReader, returns its first line, and closes the reader in a try-with-resources. Test it on a file callednotes.txtthat containsline one.Show the answer: The method prints
line onewhen the file starts with that line.static String firstLine(Path path) throws IOException { try (BufferedReader in = Files.newBufferedReader(path)) { return in.readLine(); } }The
returninside the block still closes the reader first, because the close runs on every exit from the block. -
A Java program has
try (A a = new A(); B b = new B(); C c = new C()) { ... }. In what order isclose()called on the three objects, when the body finishes normally?Show the answer:
c, thenb, thena. Java closes resources in the reverse order of their declaration. -
Rewrite the
OldStyleprogram from thefinallysection so that it uses try-with-resources. Keep the output the same.Show the answer: The program below prints
one, the same as before. The reader is declared and closed in one place, and the variable no longer needs anullcheck.import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; public class OldStyle { public static void main(String[] args) throws IOException { try (BufferedReader reader = new BufferedReader(new StringReader("one\n"))) { System.out.println(reader.readLine()); } } }
Summary
- A resource such as a file, a block of heap memory or a lock must be released on every exit path, including early returns and exceptions.
- In C++, put the acquire step in a constructor and the release step in the destructor. A local object is destroyed when its scope ends, in the reverse order of creation.
- Delete the copy constructor of a class that owns a resource, so two copies cannot release the same thing twice. Use a move to hand the resource over.
- Prefer
std::unique_ptr,std::vector,std::string,std::ofstreamandstd::lock_guardover rawnewanddelete. - In Java, a class that implements
AutoCloseablecan go in the parentheses of a try-with-resources statement. The resources are closed in reverse order, before anycatchblock runs. - If the body and a
close()both throw, Java keeps the body's exception and attaches the other one as a suppressed exception, whichgetSuppressed()returns.