使用 anonymous-namespace 结构的 pimpl 成语:这安全吗?

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

我的一个队友经常使用 pimpl 的变体,他是这样使用的:

Foo.h:

namespace { struct Impl; }

class Foo
{
public:
  Foo();
  ~Foo();

  void Bar(int n);
  /* ... */

private:
  std::unique_ptr<Impl> _impl;
};

这里发生的事情是他forward-declaring实现class在匿名命名空间中。然后他将在 Foo.cpp .

中定义 Impl class

因此结构 ::Impl 的定义将可用于 Foo.cpp 的翻译单元。其他代码包括 Foo.h 将引发警告,因为它们显然无法访问 Foo.cpp 中定义的 ::Impl。但是,我们不需要它们 - 它是一个 class,仅用于 Foo.cpp;我们不希望它在其他地方可见或为人所知。

虽然我们肯定会遇到 .cpp 文件包含多个 header 的情况,每个都声明自己的 ::Impl 结构,但这些实际上并不冲突,因为这些结构从未在各自的翻译单元之外使用。

tl;dr: 这看起来很奇怪,会发出警告,看起来好像会导致冲突,但似乎确实有效。


综上所述,我不喜欢在我们的代码中嵌入如此深的警告(header 文件越多,删除它就越难。 ) 它也只是 ton 的警告。

我的队友支持这一点,因为它很简单,保持代码定义简单,并让我们在所有代码中使用简短、一致的 class名称 Impl

我不是编码约定的坚持者;如果这对我们的 use-case 来说是个好习惯,我不介意。但我想感到舒服的是,这是安全和可维护的,并且不会在某些时候在我们面前爆炸。

class Foo 违反了 ODR。每个 cpp 文件都认为其唯一的 ptr 包含不同的类型。

ODR 违规会使您的程序格式错误,无需诊断。

你的程序可能会工作,但它的行为完全没有被 C++ 标准指定。

可能导致的实际问题是编译器可能会在您的脚下发生变化,并且当前未定义的行为 ("it seems to work") 将更改为其他内容 ("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.") 作为例子,但它可以变得多么疯狂是没有限制的。

有时做 UB 是有好处的,值得冒险。我在这里看到零收益。

创建一个 namespace FooImplFoo_details 并在其中填充 Impl