A teammate of mine regularly uses a variation on pimpl, which he does like this:
Foo.h:
namespace { struct Impl; }
class Foo
{
public:
Foo();
~Foo();
void Bar(int n);
/* ... */
private:
std::unique_ptr<Impl> _impl;
};
What's happening here is that he's forward-declaring the implementation class to be in the anonymous namespace. Then he'll define the Impl
class within Foo.cpp .
So the definition of the structure ::Impl
will be available to Foo.cpp
's translation unit. Other code include Foo.h
will raise a warning, because they obviously can't access ::Impl
defined in Foo.cpp
. But then, we don't need them to - it's a class meant to be used in Foo.cpp
only; we don't want it visible or known elsewhere.
And while we can certainly have a situation where on .cpp
file is including multiple headers, each declaring their own ::Impl
structures, these don't actually clash, because the structures are never used outside their respective translation units.
tl;dr: This looks weird, raises warnings, and looks as though it can cause clashes, but seems to actually work.
All that being said, I'm not comfortable with having something that raises warnings baked so deeply into our code (the more header files this is in, the harder it's going to be to take out.) It's also just a ton of warnings.
My teammate stands by this because it's simple, keeps the code definitions simple, and lets us use the short, consistent classname Impl
across all our code.
I'm not a stickler for coding conventions; if this is good practice for our use-case, I don't mind. But I would like to feel comfortable that this is safe and maintainable, and isn't going to blow up in our faces at some point.
The class Foo
violates the ODR. Each cpp file thinks its unique ptr contains a different type.
ODR violation makes your program ill formed, no diagnostic required.
Your program may work, but its behaviour is completely unspecified by the C++ standard.
The practical issue that could result is the compiler may change under your feet, and the current undefined behaviour ("it seems to work") will change to something else ("casts fail unexpectedly", "corrupt type tables", "linker fails to link", "compiler proves class is can never be used outside of its implemented translation unit, and erases all code in your functions as if they ran it would be UB.") as examples, but there is no bound to how insane it could get.
There is sometimes a benefit to doing UB, worth the risk. I see zero benefit here.
Create a namespace FooImpl
or Foo_details
and stuff the Impl
in there.