PIMPL is an elegant solution to multiple problems. Idk what you could possibly have against it besides the extra work involved. I don't think any language has solved the fundamental problem of hiding details better than PIMPL does.
I really like C#'s `partial` keyword as a solution to the problem of hiding implementation details. It lets you declare a class over several files, so you can have one file which is only the public interface, and another which has private implementation.
That is essentially the same idea as PIMPL. You put the private parts of the class (e.g., the data layout) in some file that is held privately. I guess you could argue that there is extra syntax involved with PIMPL because C++ is more low-level than C#, but it's not so bad. The actual implementation of a class can be spread over as many files as you want in C++.
Yes but pimpl is really just a hack and workaround for the fact that you can't separate the public interface from the implementation details in C++ due to needing to put everything in the class definition. imho `partial` is superior, as you don't need an additional allocation and indirection.
You actually don't need to do that at all. It's common style in C++, but the language does not require it.
With the right techniques, you can absolutely forward-declare basically all of a class's functionality. Then you can put it into its own translation unit.
Members and function signatures have to be declared in the header, but details about member values/initialization and function implementations can absolutely be placed in a single translation unit.
The data layout of a class must be part of its definition. So either you expose the layout (data members), add a layer of indirection with PIMPL, or resort to ugly hacks to otherwise hide the data layout such as having buffers as members. Another possibility is to not use exposed classes for member functions. Then you can just pass pointers around only and never use C++ features. Out of all of these, PIMPL solves the problem the best.
Yes, that's true. But if the concern is build-times, exposing the data layout is harmless. We don't necessarily need full PIMPL just to get improved build times. By keeping the data layout in the .hpp, you can guarantee that your class can still stack-allocate.
if the concern is build-times, exposing the data layout is harmless
Not at all! If your private member variables use any interesting types (and why shouldn't they?) you need to include all the headers for those types too. That's exactly why you get an explosion of header inclusion.
If you change the data layout of your exposed class, you must recompile anything that uses it. That increases build times and also breaks ABI. And as the other guy commented, the data itself has types that also need definitions and file inclusions. Without PIMPL, your data layout can change without touching your header, due to changes in a related header (even a 3rd party header).
You don't need any indirection with partial because in C# all classes already go through a layer of indirection.
However, consider that in C# if you add a field to a struct, which is a value type and hence no indirection, then you do need to recompile all assemblies that make use of that struct. It's no different than C++ in this regard.
And yet C, an even lower-level language, achieves the same effect without the duplication of PIMPL. You just forward-declare a struct, and declare functions that accept pointers to it: the header doesn't need to contain the struct fields, and you don't need to define any wrapper functions. Technically you can do the same in C++. But in C++ to make an idiomatic API you need methods instead of free functions, and you can't declare methods on forward-declared classes. Why not? Well, I can imagine some reasons… but they have more to do with C++'s idiosyncrasies than any fundamental limitation of a low-level language.
The C++ committee could address this, but instead they seem to want to pretend separate compilation doesn't exist. (Why are there no official headers to forward-declare STL types, except for whatever happens to be in <iosfwd>?) Then they complain about how annoying it is to preserve ABI stability for the standard library, blaming the very concept of a stable ABI [1] [2], all while there are simple language tweaks that could make it infinitely more tractable! But now I'm ranting.
First of all, comparing C to C++ in this way is silly, because C++ is a very different language. But there are some similarities.
> You just forward-declare a struct, and declare functions that accept pointers to it: the header doesn't need to contain the struct fields, and you don't need to define any wrapper functions.
Those functions would be more verbose because they must contain an explicit `this` equivalent pointer. This would have to be repeated at every single call site. So it's not really helping.
You don't need wrapper functions for PIMPL. You can have them if you think it's worthwhile, of course.
>Technically you can do the same in C++. But in C++ to make an idiomatic API you need methods instead of free functions, and you can't declare methods on forward-declared classes. Why not?
There are good technical reasons why you can't tack member functions into the interface of a forward-declared function. There would be nowhere for that information to go, if nothing else. I think I heard a talk about adding new metaprogramming features to C++ that might address this in like C++26, but anyway it's not a significant problem to simply avoid the problem
I think you can probably make some template-based thing that would automate implementing the wrappers for you. But it would be a convoluted solution to what I consider a non-problem.
>The C++ committee could address this, but instead they seem to want to pretend separate compilation doesn't exist. (Why are there no official headers to forward-declare STL types, except for whatever happens to be in <iosfwd>?)
Most of the STL types that people need are based on templates. It does not make sense to forward-declare those. I just don't see a use case for forward-declaring much besides io stuff and maybe strings.
>Then they complain about how annoying it is to preserve ABI stability for the standard library, blaming the very concept of a stable ABI [1] [2], all while there are simple language tweaks that could make it infinitely more tractable! But now I'm ranting.
There seems to be a faction of the C++ committee that does not share the traditional commitment to backward compatibility. They have gone so far as to lobby for a rolling release language, which is guaranteed to be a disaster if implemented. I think wanting to break ABI might be a sign of that. Let's hope they use good judgement and not turn the language into an ever-shifting code rot generator.
Keep in mind, there may be ABI breakage coming from your library provider anyway, on top of what the committee wants. So it's not necessarily such a cataclysmic surprise as something you're supposed to plan around anyway. ABI stability between language standards is mostly a concern for people who link code built with different C++ standards (probably, a lot of code). It wouldn't be the end of the world if you had to recompile old code to a newer standard, but it might generate significant work.
> There are good technical reasons why you can't tack member functions into the interface of a forward-declared function.
Are there? You could have a class decorator to mark a definition as incomplete and only allow member functions, types and constant definitions:
// in Foo.h
incomplete struct Foo {
public:
Foo();
Foo(const& Foo);
void frobnicate();
};
// in foo.cc
struct Foo { // redefinition of incomplete structs is allowed
public:
Foo(){...}
Foo(const& Foo) {...}
void frobnicate(){...}
private:
void bar() {...}
int baz;
string bax;
};
edit: there are also very good reasons to fwd declare templates. You might want to add support in your interface for an std template without imposing it to all users of your header. In most companies I have worked, we had technically illegal fwd headers for standard templates.
>Are there? You could have a class decorator to mark a definition as incomplete and only allow member functions, types and constant definitions:
C++ already has a way to do this via inheritance, even multiple inheritance. I suppose some of the same machinery used for inheritance could be repurposed for partial class definitions but it is unnecessary.
Edit: I think I overlooked something here at first glance. Yes it might be nice to have a public partial definition of a class and a private full definition. But the technical reason you can't have this is that using a class in C++ requires knowing its memory characteristics. If that information does not come from the code, then it must come from somewhere else like a binary. Maybe the partial definition could be shorthand for "use PIMPL" but I haven't thought through all the ways it could go wrong, such as with inheritance.
>edit: there are also very good reasons to fwd declare templates. You might want to add support in your interface for an std template without imposing it to all users of your header. In most companies I have worked, we had technically illegal fwd headers for standard templates.
I have never seen illegal forward declaration headers. Not at any company I've worked at, nor in any open-source project. I don't think there is a reasonable value proposition to doing that. What kind of speedup are you expecting from that?
>You might want to add support in your interface for an std template without imposing it to all users of your header.
This sounds good in theory but in practice, most interfaces I've seen use the same handful of types or std headers, so it can't be avoided and furthermore you'd be forcing everyone to bring their own std headers every time (and probably forget why they ever included them in their own code, in the first place). You'd be talking about a lot of trouble to maybe save one simple include, and introducing a lot of potential for unused and noisy includes elsewhere.
Yes, that's why I used the 'incomplete' keyword. Of course you can only pass around pointers and references to incomplete classes (although there might be ways around that).
Base classes almost work, but you either need all your functions to be virtual or you need to play nasty casts in your member functions.
re std fwds, typically the forwarding is needed when specializing traits while metaprogramming.
Ada had all of C++'s problems figured out in 1983. PIMPL as a means of boosting compiler performance is fundamentally braindead. We shouldn't be bending over backwards with broken tools to make them sort of work.
PIMPL doesn't only boost compiler performance. It provides code-hiding and ABI stability for everyone using it effectively. It's like killing 3 birds with one stone. PIMPL for sure isn't gonna be the thing to convince me that C++ is broken.
Ada has piqued my curiosity before but I think if it was as good as you make it sound, it might have at least 1% market share after 40 years. It doesn't. I can't justify the time investment to learn it unless I get a job that demands it.
It's not PIMPL per se that's the problem, it's that C++ needs it but makes it very awkward to write. It feels like the language is fighting against you rather than setting you up for success. At least that's been my angle in this discussion.
Unfortunely "bending over backwards with broken tools" is exactly to what made C++ a success in first place, an idea also adopted by Objective-C and TypeScript.
Trying to make the best out of an improved langauge, while not touching the broken tools of the existing kingdom they were trying to build upon.
Naturally such decision goes both ways, helps gain adoption, and becomes a huge weight to carry around when backwards compatibility matters for staying relevant.