Stupid RCU Tricks: RCU API in C++26
RCU was accepted into C++26 a few years back, and C++26 should be coming out soon, this being 2026 and all.
The specification may be found in the draft C++ standard, but the last RCU working paper has background information and code examples that, though quite useful, are not appropriate for a standards-body specification.
But for the benefit of people who are familiar with Linux-kernel RCU, let's start by comparing the C++26 RCU API to that of the Linux kernel. Others might be better served by the aforementioned RCU working paper.
The following table maps the relevant portion of the Linux-kernel RCU (and the userspace-RCU library) API to the C++26 RCU API. The C++ in this table is more verbose than necessary, and this is intended to keep C++ novices from getting quite as many compiler diagnostics as they might otherwise. For example, you can use C++ using statement to avoid having to type std:: quite so many times.
| Linux Kernel | C++26 |
|---|---|
guard(rcu)() |
std::scoped_lock l(std::rcu_default_domain()) |
rcu_read_lock() |
std::rcu_default_domain().lock() |
rcu_read_unlock() |
std::rcu_default_domain().unlock() |
synchronize_rcu() |
std::rcu_synchronize() |
struct rcu_head |
std::rcu_obj_base<T> * |
call_rcu() |
std::rcu_obj_base<T> .retire() member function * |
kfree_rcu_mightsleep() |
std::rcu_retire() * |
rcu_barrier() |
std::rcu_barrier() |
The two entries marked with an asterisk (”*“) are approximate.
First, where you would include a struct rcu_head into your RCU-portected C-language structure, in C++26 you would instead make your RCU-protected structure (or class) inherit from the rcu_obj_base<T> class. For example, the following C-language structure:
struct foo {
struct rcu_head rh;
int data;
struct foo *next;
};
Might translate to C++26 as follows:
struct foo : std::rcu_obj_base<foo> {
int data;
struct foo *next;
};
Then given a pointer fp, your C-language call_rcu(&fp->rh, my_cb) would become in C++26 fp->retire(my_cb) or just fp->retire() if your my_cb() function was a simple wrapper around kfree(). Or, perhaps better, you can think of an argument-free call to fp->retire() as the C++26 counterpart to the Linux kernel's kfree_rcu(fp, rh).
And yes, raw pointers such as struct foo *next are frowned upon by many C++ developers. However, it does make the comparison more straightforward, and C++ experts will have no problem converting to other more-favored facilities.
Second, C++ needs to run in minimal environments, including those that cannot tolerate the background threads that both the Linux kernel (using the term “thread” rather loosely) and the userspace RCU library use to invoke callbacks. This means that a C++26 RCU implementation is permitted to invoke RCU callbacks/deleters within the invocation of rcu_retire() or the .retire() member function. The benefit of this permission is greater portability, but the corresponding drawback is that you are not permitted to hold a mutex across any call to rcu_retire() or .retire() if that mutex is unconditionally acquired by any deleter passed to any rcu_retire() or .retire() anywhere in your program. To do so would deadlock, given that any call to rcu_retire() or .retire() might invoke any deleter whatsoever!
If this restriction becomes a serious problem, and it might well, perhaps there will be a function call identifying C++ RCU implementations that are immune to this deadlock situation. Or maybe there will be a C++ type trait added to rcu_obj_base<T> that forces either deadlock-free retirement on one hand or a compiler diagnostic on the other, depending on whether the underlying C++ implementation is capable of deadlock-free operation.
Third, if your C-language structure does not have a struct rcu_head:
struct foo {
int data;
struct foo *next;
};
Then this exact same structure definition works in C++26. And your C-language kfree_rcu_mightsleep(fp) map to C++26 rcu_retire(fp). In this case, C++26 also allows a callback function (deleter in C++26) to be specified, for example, rcu_retire(fp, my_cb).
The C++26 RCU API is way smaller than that of Linux-kernel RCU. The point is not to needlessly restrict C++26 RCU users, but to track userspace implementation experience. Section 1.1 (“Proposed Entry to C++26 IS”) of the last RCU working paper lists some possible directions for the future.
Implementations for production C++ standard libraries are in progress, but in the meantime the reference C++ RCU implementation may be used for experimentation. This prototype implementation creates a thin C++ wrapper around the userspace RCU library.