Simplifying the PImpl Idiom with C++26's std::indirect Type
The PImpl idiom and the C++26 std:indirect type
I explore how the PImpl idiom separates interfaces from implementations to minimize compile-time dependencies. While raw pointers and std::unique_ptr have worked well, they struggle with const propagation and null checks. The new C++26 std::indirect type solves these issues by behaving like a value while managing heap allocation, offering deep copying and robust state management without manual resource handling.
The std::indirect type owns a single object allocated on the heap, but behaves like a value member, ensuring deep copies and proper const propagation without the risk of null pointers.
- amluto
I have a little class called EImpl that is kind of like std::indirect except that it embeds the impl instead of pointing to it. It takes three template parameters: an embedded struct, a size and an alignment. It static_asserts that the embedded struct fits in the size and alignment, and it embeds it with approximately zero overhead. It’s about as easy to use as any other pImpl technique.
- Panzerschrek
> Never null: it always holds a value, except in the moved-from state
I am wondering why C++ can't implement "non-null" unique_ptr version in the same way? As I know, that the main argument against implementing it is, that it's can't be done, since move-out unique_ptr still can be null.
- ryani
Sadly, you can't easily do the full pimpl idiom in C++.
The pimpl idiom is a C idiom where a header declares an opaque structure and prototypes of functions that take pointers to that structure. In C the OP example would look something like
// widget.h
typedef struct Widget_t Widget; /* opaque! */
Widget* Widget_Create(const string* pName);
Widget* Widget_Clone(Widget*);
void Widget_Destroy(Widget*);
void Widget_click(Widget*);
int Widget_clickCount(const Widget*);
const string* Widget_label(const Widget*);
// widget.c
struct Widget_t {
int clicks;
string *name;
};
// ... implementations of the functions from the .h ...
In particular, in C `Widget` directly has `clicks` and `name` as fields.
But in c++ we like to use methods on objects, and in order to do this, you need the class declaration in scope, which means your current compilation unit needs to have seen all of Widget's data members. In practice this means if you try to use "pimpl" in C++, you do something like the OP where there is a pointer to an opaque type inside your class.
However, this is not the same thing. Methods are called with a `this` pointer, which means every access to the internal structure adds a second pointer dereference. This is why this isn't the true pimpl -- it wastes an extra deref on every access.
You can get true pimpl in current C++ but it's a lot of boilerplate and heavily relies on compiler inlining. An implementation […]
- zabzonk
Hmm. Do people use PIMPL that much (I have used it, but rarely) that we need std library support (and testing, documentation, understanding)? Just asking.
- whizzter
Where are we with modules, isn't pimpl there largely to avoid costs related to including the world?
I was pondering on why he was putting the defaulted methods in the cpp, any particular reasons?
I did realize that the indirect version is required to be in the cpp since the header won't know how to copy without knowing the definition of the impl class.