使用匿名命名空间结构的pimpl习语:这样安全吗?

Ziv*_*Ziv 2 c++ namespaces pimpl-idiom incomplete-type

我的队友经常使用pimpl的变体,他喜欢这样:

foo.h中:

namespace { struct Impl; }

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

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

private:
  std::unique_ptr<Impl> _impl;
};
Run Code Online (Sandbox Code Playgroud)

这里发生的事情是他正在声明实现类在匿名命名空间中.然后他将Impl在Foo.cpp中定义类.

因此,结构的定义::Impl将可供Foo.cpp翻译单位使用.其他代码包含Foo.h会引发警告,因为它们显然无法访问::Impl定义的内容Foo.cpp.但是,我们不需要它们 - 它只是一个Foo.cpp只用于它的类; 我们不希望它在其他地方可见或已知.

虽然我们当然可以在.cpp文件中包含多个标题,每个标题都声明自己的::Impl结构,但实际上并没有发生冲突,因为结构永远不会在各自的翻译单元之外使用.

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


所有这一切,我不满意我的代码中提出了一些警告,这些警告已经深入到我们的代码中(这个文件越多,它就越难以取出.)这也只是一大堆警告.

我的队友坚持这一点,因为它很简单,保持代码定义简单,并允许我们Impl在所有代码中使用简短,一致的类名.

我不是编码惯例的坚持者; 如果这是我们用例的好习惯,我不介意.但是我觉得这是安全和可维护的,并且在某些时候不会在我们的脸上爆炸.

Yak*_*ont 6

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

ODR违规会使您的程序生成错误,无需诊断.

您的程序可能有效,但C++标准完全没有指定它的行为.

可能导致的实际问题是编译器可能会在您的脚下发生变化,并且当前未定义的行为("它似乎工作")将更改为其他内容("强制转换失败","损坏类型表","链接器未能链接","编译器证明类是永远不能在其实现的转换单元之外使用,并且擦除函数中的所有代码,就好像它们运行它将是UB.")作为示例,但是没有必要如此疯狂它可以得到.

做UB有时会带来好处,值得冒险.我看到这里没有任何好处.

在那里创建一个namespace FooImpl或者Foo_details填充内容Impl.