简体   繁体   中英

pimpl idiom using anonymous-namespace structure: is this safe?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM