Smart Pointers in Chromium: A Beginner's Guide

When you start reading Chromium's C++ code, one of the first things you notice is that pointers are everywhere.
But they don't always look like the pointers you learned in basic C++.
You might see:
raw_ptr<Foo> foo_;
or:
std::unique_ptr<Foo> foo_;
or:
base::scoped_refptr<Foo> foo_;
If you're new to Chromium, it's easy to wonder:
Why are there so many different pointer types?
The answer has a lot to do with ownership and object lifetime.
Once you understand that, these pointer types become much easier to read.
Let's start with a normal pointer
Suppose we have a simple class:
class Browser {
public:
void OpenTab();
};
We can create an object:
Browser browser;
And we can point to it:
Browser* browser_ptr = &browser;
Now we can use the pointer:
browser_ptr->OpenTab();
So far, nothing unusual.
But there is something important missing from this code.
Who owns browser?
The type Browser* doesn't tell us.
It only tells us that we have a pointer to a Browser.
This distinction becomes very important in a project as large as Chromium.
The real problem is object lifetime
Consider this code:
Foo* foo = new Foo();
foo->DoSomething();
delete foo;
We create the object with new and later destroy it with delete.
That's manageable in a small program.
But Chromium has a huge number of objects interacting with each other. One object may create another object, another object may keep a pointer to it, and some completely different object may eventually destroy it.
For example, imagine something like:
Browser
|
+---- TabManager
|
+---- Tab
|
+---- WebContents
Now suppose another class has a pointer to Tab.
What happens if Tab is destroyed while that other class still has its pointer?
That's where things get dangerous.
Dangling pointers
A dangling pointer is a pointer that refers to an object that no longer exists.
For example:
Foo* foo = new Foo();
delete foo;
// foo is now dangling
foo->DoSomething(); // BAD
foo still contains an address, but the object at that address has already been destroyed.
Using it is undefined behavior.
One particularly serious example of this is a use-after-free (UaF).
Use-after-free bugs are especially important in a browser because they can sometimes become security vulnerabilities.
This is one of the reasons memory safety is such an important topic in Chromium.
So, what is a smart pointer?
A smart pointer is a C++ object that behaves like a pointer while also providing some kind of lifetime or ownership management.
You may already know these from modern C++:
std::unique_ptr<T>
std::shared_ptr<T>
Chromium also has its own pointer types and memory-management patterns.
Some of the ones you'll commonly encounter are:
std::unique_ptr<T>
base::scoped_refptr<T>
raw_ptr<T>
raw_ref<T>
The important thing is that these types don't all mean the same thing.
In fact, the first question you should ask when you see one is:
Who owns the object?
std::unique_ptr
Let's start with the easiest one.
std::unique_ptr<Foo> foo;
A unique_ptr represents exclusive ownership.
In other words:
This pointer owns the object.
For example:
auto foo = std::make_unique<Foo>();
When foo goes out of scope, the Foo object is automatically destroyed.
So instead of manually doing:
Foo* foo = new Foo();
// ...
delete foo;
we can write:
auto foo = std::make_unique<Foo>();
and let C++ manage the lifetime for us.
This is part of a broader C++ idea called RAII.
Why is unique_ptr called unique?
Because there is only one owner.
For example:
auto a = std::make_unique<Foo>();
We can't simply copy it:
auto b = a; // ERROR
Instead, we have to explicitly transfer ownership:
auto b = std::move(a);
After the move, ownership belongs to b.
You can think about it like this:
Before:
a ─────> Foo
After std::move(a):
a ─────> nothing
b ─────> Foo
This is useful because ownership is explicit.
unique_ptr in a Chromium class
You might see something like:
class Browser {
private:
std::unique_ptr<TabManager> tab_manager_;
};
This tells us something useful just by looking at the declaration:
Browser owns TabManager.
When the Browser is destroyed, its TabManager will also be destroyed.
We don't need a separate:
delete tab_manager_;
That is handled by unique_ptr.
base::scoped_refptr
Now let's look at a pointer type that you'll see quite often in Chromium:
base::scoped_refptr<Foo>
This is used with reference-counted objects.
The basic idea is different from unique_ptr.
With unique_ptr:
one owner
With scoped_refptr:
multiple references can keep the object alive
For example:
base::scoped_refptr<Foo> a = base::MakeRefCounted<Foo>();
Now we can copy the reference:
base::scoped_refptr<Foo> b = a;
Both refer to the same object:
+---------+
a ────>| |
| Foo |
b ────>| |
+---------+
The object keeps track of its references.
When a goes away, Foo is still alive because b still holds a reference.
When the last reference goes away, the object can be destroyed.
When will I see scoped_refptr?
You'll generally see it with classes that use Chromium's reference-counting infrastructure.
For example, you may come across classes based on things such as:
base::RefCounted<Foo>
and then see:
base::scoped_refptr<Foo>
used to hold a reference to them.
For now, the important thing to remember is simply:
unique_ptr
↓
one owner
scoped_refptr
↓
reference-counted ownership
You don't need to understand all of Chromium's reference-counting machinery before you can read code using scoped_refptr.
raw_ptr<T>
Now we get to one of the pointer types that you'll see a lot in Chromium:
raw_ptr<Foo>
This one is slightly different.
raw_ptr is non-owning.
That means:
raw_ptr<Foo> foo_;
doesn't mean:
"I own Foo."
It means:
"I have a pointer to Foo, but something else owns it."
This distinction is very important.
Chromium's documentation explicitly describes raw_ptr<T> as a non-owning smart pointer. It does not manage the lifetime of the object it points to.
raw_ptr is not unique_ptr
Consider:
class Browser {
private:
raw_ptr<TabManager> tab_manager_ = nullptr;
};
The Browser has a pointer to TabManager.
But Browser doesn't own it just because it has a raw_ptr.
Some other object is responsible for keeping TabManager alive and eventually destroying it.
This is why you should not think of:
raw_ptr<T>
as:
"A safer
unique_ptr."
It isn't.
A better mental model is:
raw_ptr<T>
↓
I can access the object
I don't own the object
Why does Chromium use raw_ptr?
This is where raw_ptr gets interesting.
Chromium is a very large C++ project, and dangling pointers are a serious memory-safety problem.
Chromium's raw_ptr is part of the MiraclePtr project and currently uses the BackupRefPtr implementation. Its goal is to make certain use-after-free bugs harder to exploit.
Very roughly, when the protection is active, memory associated with a freed object can be kept in quarantine while a raw_ptr still points to it.
The memory can also be poisoned.
This can make some use-after-free situations easier to detect and can prevent some of them from becoming exploitable.
But there is an important point here:
raw_ptrdoes not make dangling pointers safe.
If you have:
raw_ptr<Foo> foo;
and Foo is destroyed, you cannot assume this is safe:
foo->DoSomething();
Dereferencing a dangling pointer is still undefined behavior. Chromium's documentation is very explicit about this.
So raw_ptr should not be thought of as a license to ignore object lifetime.
Why not just use Foo*?
You might now be wondering:
If
raw_ptrdoesn't own the object, why not just useFoo*?
That's a reasonable question.
Chromium's C++ style guide generally prefers raw_ptr<T> for non-owning pointer fields instead of raw T* fields, with some exceptions.
For example:
class Browser {
private:
raw_ptr<WebContents> web_contents_ = nullptr;
};
The type tells the reader that this is a non-owning pointer.
It also gives Chromium's memory-safety infrastructure an opportunity to provide additional protection.
One detail that is easy to miss: Chromium generally doesn't require raw_ptr for every pointer everywhere. Local variables, function parameters, and return values can still use ordinary C++ pointers.
So you may see both:
Foo* foo
and:
raw_ptr<Foo> foo_;
in the same codebase.
That's normal.
raw_ptr vs raw_ref
You may also encounter:
raw_ref<Foo>
This is similar to raw_ptr, but the important difference is that it represents a non-owning reference that is expected to be non-null.
A simple way to remember it:
raw_ptr<T>
↓
non-owning
can be null
raw_ref<T>
↓
non-owning
should not be null
You don't need to focus too much on raw_ref when you're first learning Chromium.
Understanding ownership is much more important.
What about std::shared_ptr?
If you've learned modern C++, you might ask:
Why does Chromium use
scoped_refptrinstead ofstd::shared_ptr?
At a high level, both involve shared/reference-counted ownership.
But they are different implementations and belong to different ownership systems.
Chromium has its own reference-counting infrastructure, so when you see:
base::scoped_refptr<Foo>
you shouldn't automatically replace it mentally with:
std::shared_ptr<Foo>
The concepts are similar, but the types and surrounding APIs are different.
For someone learning Chromium, the useful thing to remember is:
unique_ptr
→ exclusive ownership
scoped_refptr
→ reference-counted ownership
raw_ptr
→ non-owning pointer
How do I decide which one I'm looking at?
When reading Chromium code, I find it useful to start with the ownership question rather than the pointer syntax.
std::unique_ptr<T>
Ask:
Does this class own the object?
If yes, and there is one clear owner, unique_ptr is a common choice.
base::scoped_refptr<T>
Ask:
Is this object reference-counted?
If multiple references can keep the object alive, you may see scoped_refptr.
raw_ptr<T>
Ask:
Does this class only need to point at the object without owning it?
If yes, a raw_ptr field is often what you'll see in Chromium.
A small example
Suppose we have:
class Browser {
private:
std::unique_ptr<TabManager> tab_manager_;
base::scoped_refptr<SomeSharedObject> shared_object_;
raw_ptr<WebContents> web_contents_ = nullptr;
};
You can read this almost like English:
Browser owns TabManager.
Browser holds a reference to SomeSharedObject.
Browser points to WebContents,
but does not own it.
That's the real benefit of these pointer types.
The type itself gives you information about the relationship between objects.
One common beginner mistake
A common mistake is seeing:
raw_ptr<Foo> foo_;
and assuming that Foo will somehow be destroyed when foo_ goes away.
It won't.
For example:
class Bar {
private:
raw_ptr<Foo> foo_ = nullptr;
};
When Bar is destroyed:
Bar
|
+---- foo_ disappears
That doesn't mean:
Foo
|
+---- destroyed
Foo has an owner somewhere else.
This is probably the most important thing to understand about raw_ptr.
A useful way to read Chromium code
When you see something like:
raw_ptr<WebContents> web_contents_;
don't stop at:
"Okay, this is a
raw_ptr."
Instead, ask:
Who owns
WebContents?
Then search the surrounding code.
Look at:
- where
web_contents_is assigned - where the
WebContentsis created - who destroys it
- whether the pointer can become
nullptr - how long the containing object lives
This will teach you much more about Chromium than memorizing pointer definitions.
The three questions I usually ask
When you're new to Chromium, these three questions are enough to get started:
1. Who owns the object?
Find the object responsible for its lifetime.
2. Can there be multiple owners?
If yes, look for reference-counting or another shared-lifetime mechanism.
3. Is this pointer just observing the object?
If yes, it may be a non-owning pointer such as raw_ptr.
Once you start thinking this way, Chromium's C++ code becomes much easier to follow.
Quick cheat sheet
Here's the short version:
std::unique_ptr<T>
↓
Exclusive ownership
↓
"I own this object."
base::scoped_refptr<T>
↓
Reference-counted ownership
↓
"I hold a reference to this object."
raw_ptr<T>
↓
Non-owning pointer
↓
"I point to this object, but I don't own it."
raw_ref<T>
↓
Non-owning, non-null reference
↓
"I point to this object, and it should not be null."
Final thoughts
When I first started looking at Chromium code, pointer types were one of those things that looked more complicated than they really were.
There are a lot of types, and each one has its own rules.
But you don't need to memorize all of them.
Start with one idea:
Who owns the object?
From there, the pointer type starts to make sense.
If you see:
std::unique_ptr<T>
think:
I own it.
If you see:
base::scoped_refptr<T>
think:
I hold a reference to it.
If you see:
raw_ptr<T>
think:
I don't own it.
That's enough to get started.
And the next time you see something like:
raw_ptr<WebContents> web_contents_;
in Chromium, instead of wondering "What is this weird pointer?", try asking:
"Who owns this
WebContents?"
That question will usually lead you to something interesting.
Further reading
If you want to go deeper, Chromium's own documentation is the best place to start:
Happy Chromium hacking. 🚀