【问题标题】:Hide implementation by using a pointer (Pimpl idiom)使用指针隐藏实现(Pimpl idiom)
【发布时间】:2012-10-26 17:32:11
【问题描述】:

是否有可能完成以下操作:

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 idiom”。

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

PS。我不想使用 void* 并将其投射到周围..

【问题讨论】:

  • 为什么你不能让concrete_x继承自x_impl?
  • 你的问题到底是什么?
  • 你为什么不想在 中定义 class x_impl 本身?
  • concrete_x 是我试图隐藏的库类

标签: c++ dependencies pimpl-idiom


【解决方案1】:

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

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

或使用名称x_impl 来表示在标题&lt;concrete_x&gt; 中定义的类。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 这似乎没问题,只是我有一个虚拟类只是为了隐藏私有成员的细节; C++ 荒谬。
    • @elmes,如果您考虑使用不同的具体 x 的可能性,那么这是最自然的方法......
    【解决方案3】:

    作为@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_; }
    

    【讨论】:

    • 是的,这就是解决方案。但它在 C++11 中并不是通用的,因为 concrete_x 可能是最终的。最后一个问题:派生类只是为了隐藏它的性能/内存成本是多少?
    • 相比直接存储一个指向concrete_x的指针,没有性能/内存开销。只是维护人员需要了解它的(小)维护成本。
    • 您能解释一下concrete_x 的情况吗?缺少示例标头,我不清楚为什么包含在 x.cpp 中而不是将 concrete_x 的类定义添加到 x.cpp 的顶部。
    • @jww:在问题中,&lt;concrete_x&gt; 是一个现有的标头,它声明了 concrete_x 类,它不能包含/知道在 x.cpp 之外。这就是为什么我要包含标题,而不是直接在 x.cpp 文件中声明 concrete_x 类。
    【解决方案4】:

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

    // Foo.hpp
    class Foo {
    public:
    
        //...
    
    private:
        struct Impl;
        Impl* _impl;
    };
    
    // Foo.cpp
    struct Foo::Impl {
        // stuff
    };
    

    我只想提醒你:

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

    有一些方法可以使 PIMPL 自动化,但代价是一些黑魔法(类似于 std::shared_ptr 所做的)。

    【讨论】:

    • 请看@Bart van Ingen Schenau的想法。你的解决方案更擅长什么?
    • @elmes:名称Impl 完全隐藏,而Bart 的解决方案在封闭的命名空间中引入了x_impl 名称。使用private 嵌套结构,它更加隐蔽。
    • 如果结构位于 cpp 文件的命名空间内,这会起作用吗?
    • @ChaoSXDemon:任何类型的前向声明都有效,因为您不需要完整的类型来形成指针。将struct 声明为私有只是强化了其他人不应该关心它。
    猜你喜欢
    • 2012-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多