back
117 comments
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.
But if the pimpl size grows too big, you're forced to break ABI?

And before it grows too big, it wastes memory. For your use cases it may not matter, and the saved pointer indirection may be more important, but maybe the person who has a million item vector of objects doesn't appreciate a 300% "just in case" memory overhead. The overhead may also hurt cache hits.

If you're doing this to save the pointer indirection, you should benchmark it for every use case, since negative cache effects may dwarf that gain.

Then again, extra padding can also help performance, for some workloads (especially multi threaded read/write against a vector of objects).

So without further context, there's no way to say if your way hurts or helps. It's certainly not a general solution.

Do you have the code shared somewhere, via a blog post or code snippet somewhere?
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 of the example from the OP: https://godbolt.org/z/6EznxeG1n . In practice this is too much work, hard to read, and so nobody does it.

For the c++ standards committee: please add an "opaque class" feature where the class can only define non-virtual method prototypes. Then the full class declaration, in the associated cpp file, could include its parent classes, actual data layout, and function implementations.

A similar opaque pointer pattern with member functions is possible to do with inheritance.
Completely agreed, but in fairness to the c++ standards committee, this is solved from a standards perspective by c++ modules
> 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.

The C++ core guideline support library has it.

https://github.com/microsoft/GSL/blob/main/docs/headers.md#u...

What do you mean by move-out unique_ptr? That the not_null ptr type would ne null after it's been moved?

In that case that's just a plain usage error, same as how you could memset it to null.

I've been thinking about things like that a bit. I see a very useful and safe Rust pattern, and wonder if I can possibly implement it in C++. Mostly the answer is no, because C++ is too powerful.

I would love to proved wrong, but everything I can think of still leaves a footgun that's easy to trigger by accident, and thus negates the point of the solution.

I think the can't-reference-after-moved-from and objects-are-not-Copy-by-default are key to creating these types (at least enforced at compile time). And that would require major language changes, at least as big as the C++11 changes.

I think nothing prevents this, but this is just not the point of unique_ptr. unique_ptr is still ptr, so it consequently follows raw pointer semantics.
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.
It's often used in libraries where you need to guarantee ABI compatibility. Fixing a bug or implementing a feature may require adding a new member into the class, which would change its size (thus break ABI compatibility). PIMPL is the typical solution here, since the inner/impl class is not part of the public ABI.

I also like to use it sometimes to "hide" private methods and their documentation into PIMPL, so the public header is kept clean.

This std::indirect thingie looks more like a general helper for any data 'dangling off' an object, not limited to pimpl.

Not sure how much pimpl is used in reality, but it's a pretty ok solution to speed up build times (apart from unity builds), because it avoids having to include headers that are only needed for the private state into the public interface header.

It was used extensively at a former workplace of mine where each class that wasn't a message or data type was a pimpl. They had implemented their own private pointer class to handle it. It worked well enough to avoid pulling in lots of headers, but was still a PITA when you wanted to change methods as you'd always need to change the method signature in at least 3 places - header file declaration, source file definition, source file impl definition.
They didn't add PImpl support, they added std::indirect which can be used for PImpl among other things.
Yes, if you actually care compile times.
I remember using it all the time for the Windows headers because they pollutes the compilation unit like you wouldn't believe — the rule was to only include them in c/cpp files.
Back when I used to write C++ it was used all over the place. Admittedly that was a log time ago.
This looks great indeed - I wonder if there are any particular gotchas, though, as things often are in C++next land.

With many of the features coming into the language over time, I kinda wish that a bit more restricted subset of it eventually becomes a thing, but I know in practice it might as well be a completely different language. That, and I expect that still many other things have not been resolved as well as they are elsewhere, such as build system and dependency management (although I haven't touched this stack for a while now, so I would love to be surprised).

The gotcha is that this is a 90s C pattern, and software that actually needed this has been written for 3 decades by now
"Holds a value, except sometimes"
Small nitpick, but a post about C++26 should really not be using `const std::string&` parameters. We have had `std::string_view` for this exact purpose since C++17.
The example is problematic, in that:

1. click() should not be a member of the widget. A widget does not click; a user clicks a widget. A click can change a widget's state, but the state might change because of other effects, e.g. pressing a key when the widget is focused. But then, that's just one of the issues with treating UI widgets this way.

2. More to the point - clickCount. If this is a button, it shouldn't keep a record, or aggregate, of its clicks within it; and if it's a widget where this does really matter, like a range control where more clicks mean a value that goes farther along the range - you still would not keep the count of clicks, but the current position. Statistics about the interaction with an object should not be part of the object itself. At most it might be legitimate to have, say, a Widget class, a template like <class Stats> StatisticsTracker , and then class TrackedWidget which uses that as a mixin, i.e. inheriting both Widget and StatisticsTracker<ClickStats>. And that's already stretching it beyond what I would find reasonable.

3. Having something named is another aspect of objects which may be a good fit for a mixin class.

Anyway, an 'indirect' type for objects you don't know the definition of sounds nice.

A few more nitpickis about the example:

1. Instead of explicitly applying the rule-of-0 with `= default` for the copy&move ctor&assignment and the destructor - just _don't_ write anything:

    class Widget
    {
    public:
        void click();
        int  clickCount() const;
        std::string label() const;
    private:
        struct Impl;
        std::indirect<Impl> pimpl_;
    };
and that's the beauty of the rule of 0.

2. Why return an std::string for the label? The label() method should return an std::string_view

This is just a simple example, therefore nitpicking on the semantics of the Widget methods is a bit silly.

> 1. Instead of explicitly applying the rule-of-0 with `= default` for the copy&move ctor&assignment and the destructor - just _don't_ write anything:

The blog post explicitly explains why this doesn't work. You have to define these methods in the source file because they need to see the definition of the Impl struct.

> 2. Why return an std::string for the label? The label() method should return an std::string_view

This only works if it's always the same value. This doesn't work if the label is for example, set to `std::to_string(clickCount())`

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.

Even with modules, if you expose such types over the ABI, naturally the machine code memory layout will change, this is an issue regardless of the language.
The article explains why, indirect and unique_ptr are templated and require the complete definition of the type, and the default impls of those methods use methods of the templated types.
pimpl helps more since its trivially implementable in existing codebases while modules are a much bigger pain.
pimpl also makes it much easier to make changes without breaking ABI. E.g. shared libraries.
pimpl also helps to keep data structure layout stable, e.g. Qt's d-pointer convention
The only thing i do with pimpls is pop them :)
This is another great enhancement that makes me look forward to C++26.
Oh god, what monstrocity have we created?!?

All this complexity follows unique_ptr and copy constructor madness.

Anything with pointers with ownership should never be copied - period. Reference pointers - OK if scope/lifetime is known.

Can we have c++11 lite?

Yes, configure clang-tidy as such.
So data structures like std::vector should never be copied - period?
This is actually useful, but despite it is another extra thing you will have to remember when reading C++ code. I guess with LLMs things aren't so bad.
You need to remember _less_, rather than more, when you use this kind of vocabulary types. Think about std::optional. Before that (and if you didn't write something like it yourself), you had to, for each class, remember the bespoke semantics of when and how it represents the lack of some members, and you would have to have non-defaulted ctors, move assignments and dtors, and then whenever you used that class you would need to think about what those custom method do, which might be different than other classes which have optional members. Now you just tell yourself "oh, it just has an optional member, no biggie". Look at my comment above regarding how short the implementation of Widget becomes when you squeeze the juice from having the rule of 0.
Why? It’s still the good (bad) old pimpl pattern. It just got a bit shorter. When reading you dont even need to grok “std::indirect”, you see the word pimpl and you know what’s going on.
I don't really get why people keep repeating the "C++ is too big" complaint together with the implication that you need to remember the entirety of the standard library. In comparison Java has networking, GUI framework and even MIDI in its standard libraries. Is it because C++ is more closely related to C which library is so small that it barely contains anything useful? I much prefer code that uses a library feature rather than yet another poorly implemented and not documented hand rolled version of it.
I thought C++ is unnecessarily complex, and then I see Rust following the same pattern... I've just thought of a complexity metric that would calculate the ratio of alphanumeric characters to punctuation.
C++ is getting more and more complex. It used to be said that people use only a small percentage of it when writing C++, but I am beginning to think that the cake is a lie here.
Besides being a common idiom, for how many warts C++ might have, no one is rewriting LLVM, GCC, V8, JVM/ART and .NET runtimes, CUDA/Metal/DirectX, Unreal, Godot,.... into something else, RIR is not happening there.

People will contend themselves with "C++ the good parts", helped by clang-tidy, PVS, MSVC analyse, and move on.

where "more and more complex" do u see in this article? This is a basic C++ idiom, which constantly used by developers