class C {
protected:
struct Impl;
std::unique_ptr<Impl> mImpl;
};
And then define C::Impl in the .cpp file. class C {
protected:
struct Impl;
std::unique_ptr<Impl> mImpl;
};
And then define C::Impl in the .cpp file. // Forward declaration of Impl. What does Impl do?
// You're not allowed to know.
class Impl;
class A {
protected:
Impl* mImpl;
};
I've been using this trick but for a different reason - to reduce the number of #include statements in header files that are included a lot themselves.These are the kinds of refactorings you often need to justify with hours of arguments and approvals and religious fights and code reviews at work, but can do in an afternoon in a private project just because it makes the code nicer.
These days I wish there was something similarly easy to speed up Webpack builds ...
A much better alternative than precompiled headers.
In practice you get a strong amount of compatibility between Clang and GCC at the compiler level (since Clang basically copies GCC's ABI). But you also get the same fudging at the stdlib level with std::unique_ptr because it's header-only and is a zero overhead abstraction for a raw pointer (i.e. at the ABI level a normal unique_ptr is the same as a pointer except you can't move it around in a register).
I don't have the spec handy but fairly certain that v-table implementation is compiler specific and while it may work it isn't guaranteed. However if you declare the function symbols as dynamic then you can leverage the linker to dynamically resolve the right symbols with the matching opaque data and achieve binary compatibility(assuming you use the same compiler or a compatible compiler ABI and all other caveats around C++ binary compatibility).
Maybe? I can still think of ways to have ABI problems in the implementation of class C.
It's true that there are fewer ABI problems to worry about, though.
Yes which is why GP is speaking in terms of allowance. Using this pattern you can retain ABI compatibility, that doesn’t mean you do and it’s otherwise a free for all.
i see you haven't heard of the occult powers of going to peek into the source file, copying the impl struct definition in your own source and going for a big bad reinterpret_cast
But yes, I don't think anybody claims is a free lunch. It is annoying, verbose, repetitive, but often necessary to keep the cose base complexity under check.
I might be misremembering the "for virtual functions" part, but the inability of C to call Impl's constructor directly in the presence of a derived class can sometimes force you to delay some of the initialization until after Impl is constructed. ("Necessary" might be too strong here, in that you could find some other workaround too.)
> often necessary to keep the cose base complexity under check.
Hmm... I'm not sure I agree. A pimpl-like idiom can be necessary for solving a few very specific problems, which are explained in [1] better than I can here: (a) ABI stability, (b) slow compilation, and (c) exception safety. There are other niche cases I can think of (e.g. "I need fast/atomic swapping like a reference type, but copying like a value type"), and even those might have better solutions, but none of them really has anything to do with code complexity... they're either domain requirements you either have or don't have, like in (a)/(c)/(d), or they're workarounds for slow toolchains, as in (b). But unless you have requirements/constraints like these, I have a hard time recalling any common situations where pimpl would be the best solution, especially if it's for taming complexity.
struct deleter_t { void operator()(impl_t *); };
using impl_unique_ptr_t = std::unique_ptr<impl_t, deleter_t>;
You can place the implementation of the deleter alongside the impl.Mostly, you just want to decouple the interface of the class from the implementation details.
In that case do just that - define an interface and implement it elsewhere.
You can always just static link all binaries.
Not always you can't. There are many reasons to use dynamically linked libraries all of which are applicable whether you're using PIMPL or not.
What's an example of this (assuming you don't care about binary size)?