问题 使用指针隐藏实现(Pimpl idiom)


是否有可能,以实现以下目标:

x.hpp  - 许多其他类都包含此文件

class x_impl; //forward declare
class x {
    public:
        //methods...
    private:
        x_impl* impl_;
};

x.cpp  - 实施

#include <conrete_x>
typedef concrete_x x_impl;    //obviously this doesn't work
//implementation of methods...

基本上,我希望用户包含该文件 x.hpp,但不知道 conrete_x.hpp 头。

既然我可以使用 concrete_x 只有指针并且它只作为私有数据成员出现,前向声明应该足以让编译器知道准备多少空间。它看起来很像着名的“pimpl成语”。

你能帮我解决这个问题吗?

PS。我不想用 void* 把它扔到...


2269
2017-11-06 17:51


起源

为什么你不能从x_impl继承concrete_x? - StoryTeller
你的问题究竟是什么? - Grizzly
你为什么不想定义 class x_impl 本身在<concrete_x>? - Igor R.
concrete_x 是一个我试图隐藏的库类 - emesx
强制阅读 - Kerrek SB


答案:


实际上,甚至可以完全隐藏用户:

// Foo.hpp
class Foo {
public:

    //...

private:
    struct Impl;
    Impl* _impl;
};

// Foo.cpp
struct Foo::Impl {
    // stuff
};

我想提醒你的是:

  • 你需要写一个适当的析构函数
  • 因此,您还需要一个适当的复制构造函数,复制赋值运算符,移动构造函数和移动赋值运算符

有一些方法可以自动化PIMPL,代价是一些黑魔法(类似于什么 std::shared_ptr 一样)。


9
2017-11-06 18:53



请看一下 @Bart van Ingen Schenau的想法。你的解决方案更好的是什么? - emesx
@elmes:这个名字 Impl 是完全隐藏的,而巴特的解决方案引入了 x_impl 封闭命名空间中的名称。它更隐藏着一个 private 嵌套结构。 - Matthieu M.
如果struct在cpp文件中的命名空间内,这会工作吗? - ChaoSXDemon
@ChaoSXDemon:任何类型的前向声明都有效,因为你不需要一个完整的类型来形成一个指针。宣布 struct 私人只是强化,没有其他人应该关心它。 - Matthieu M.


作为@Angew的答案的替代,如果名称 concrete_x 不应该让x类用户知道,你可以这样做:

x.hpp

class x_impl;
class x {
  public:
    x();
    ~x();
    //methods...
  private:
    x_impl* impl_;
};

x.cpp

#include <concrete_x>
class x_impl : public concrete_x { };

x:x() : impl_(new x_impl) {}
x:~x() { delete impl_; }

5
2017-11-06 18:20



是的,这是解决方案。但是,它在C ++ 11中并不普及 concrete_x 可能是最终的。最后一个问题:为了隐藏它而导出类的性能/内存成本是多少? - emesx
与直接存储指向concrete_x的指针相比,没有性能/内存成本。只需(维护者)需要了解它的(小)维护成本。 - Bart van Ingen Schenau
你能解释一下发生了什么吗? concrete_x?缺少示例性标题,并且我不清楚为什么包含存在 x.cpp 与添加类的定义 concrete_x 到顶部 x.cpp。 - jww
@jww:在这个问题上, <concrete_x> 是一个声明该类的现有标头 concrete_x,不得包含/知晓 x.cpp。这就是为什么我包括标题,而不是声明 concrete_x 直接上课 x.cpp 文件。 - Bart van Ingen Schenau


这仅在前向声明声明类的实际名称时才有效。所以要么改变 x.hpp 至:

class concrete_x;
class x {
    public:
        //methods...
    private:
        concrete_x* impl_;
};

或使用名称 x_impl 对于标头中定义的类 <concrete_x>


2
2017-11-06 17:55





这就是接口的用途。在共享头文件中定义接口(纯虚拟类)并将其提供给用户。从接口继承您的具体类并将其放在非共享头文件中。在cpp文件中实现具体类(甚至可以在cpp中定义具体类)。


0
2017-11-06 17:57



这似乎没关系,只是我只有一个虚拟类来隐藏私人成员的细节; C ++荒谬。 - emesx
@elmes,如果你在考虑不同具体的可能性而不是最自然的方法...... - StoryTeller