指向 C++ 中类的指针的 PIMPL 习语

Posted

技术标签:

【中文标题】指向 C++ 中类的指针的 PIMPL 习语【英文标题】:PIMPL idiom for a pointer to a class in C++ 【发布时间】:2018-11-13 19:26:58 【问题描述】:

我有两个程序(ProgramAProgramB)的工作界面,我想尽可能改进这两个程序的解耦。我要介绍的情况是从ProgramA 调用来自ProgramB (Compute_Prop) 的类,该类只能使用一些我现在没有提前进行的参数进行初始化。因此,我在标题中使用了一个指针。目前,我有这样的事情:

interface.h

#include "programB.h" // loads Compute_Prop

class Compute 
  public:
    Compute();
    Compute(targ1 arg1, targ2 arg2);
    ~Compute();
    // some methods ...
  private:
    Compute_Prop* compute;
;

interface.cpp

#include "programB.h"
#include "interface.h"

#include "programA.h"

Compute::Compute() = default;

Compute::~Compute()                                                                                                      
    delete compute;                                                                                                                                                                                                                                                                                                                                                                   


Compute::Compute(arg1, arg2) 

  // do something ... to get data

  compute = new Compute_Prop( &data, arg2 );

然后,我尝试用以下方式模仿 PIMPL 成语

interface.h

#include "programB.h" // loads Compute_Prop

class Compute 
  public:
    Compute();
    Compute(targ1 arg1, targ2 arg2);
    ~Compute();
    // some methods ...
  private:
    class PIMPL;
    PIMPL* compute;
;

interface.cpp

#include "programB.h"
#include "interface.h"

#include "programA.h"

Compute::PIMPL = Compute_Prop;

Compute::Compute() = default;

Compute::~Compute()                                                                              
    delete compute;                                                                                                                                                                     


Compute::Compute(arg1, arg2) 

  // do something ... to get data

  compute = new Compute_Prop( &data, arg2 );

但是编译器说:

error: expected unqualified-id
  Compute::PIMPL = Compute_Prop;
                 ^

我猜这与 Compute_Prop 没有 一个空的构造函数。我想不出有用的东西。我该怎么办?可能是指向指针的指针之类的东西?作为限制,我不能修改programB

注意:正如上面可能已经清楚的那样,我对低级 C++/C 的了解很少。

编辑:我介绍了@n.m 建议的更正。和@Matthieu Brucher

【问题讨论】:

您的成员声明语法自始至终都是错误的。您不应该在类中重复类名。所以没有Compute::Compute(),只有Compute()。我不知道为什么有些编译器会接受你使用的语法,这是完全非法的。 您不能在 C++ 中分配类型。一旦你声明了class PIMPL;,它期望会有一个名为PIMPL的类。声明为PIMPL* ptr; 的指针将始终具有该承诺类的PIMPL 类型。 Compute::PIMPL = Compute_Prop; 是不可能的,因为 PIMPLCompute_Prop 都是不可变类型。它不会将所有PIMPLs 转换为Compute_Props。 如果(计算!= nullptr)删除计算;计算 = nullptr; 不!!删除它,或者更好的是,使用 std::unique_ptr!! Compute::PIMPL 是一种类型,您不能使用这种 type = value 语法。你需要一个名字。也许像Compute::compute 这样的东西。 Compute::PIMPL Compute::compute = .... 是的,删除已经做了检查,这是一个无用的冗余。如果您在使用 unique_ptr 时遇到问题,则意味着您还有其他问题需要解决,因为这就是应该处理 pimlp 的方式。 【参考方案1】:

你的实现应该使用一个接口(或者实际上是一个只有抽象方法的类)作为基类。 您不能在 C++ 中分配类型。你只能像这样创建 typedef 和别名:

using PIMPLType = Compute_Prop;

但是,这不适用于您的情况。 这就是它的实现方式(也有多种实现的可能性):

class IImplementation

public:
    virtual void saySomething() = 0;
;

class ImplementationA : public IImplementation

public:
    virtual void saySomething() override 
        std::cout << "A";
    
;
class ImplementationB : public IImplementation

public:
    virtual void saySomething() override 
        std::cout << "B";
    
;

class Foo 
    IImplementation *pimpl;
public:
    Foo()
        : pimpl(new ImplementationA)
    

    ~Foo()  delete pimpl; 

    void saySomething() 
         pimpl->saySomething();
    
;

【讨论】:

我将研究如何实现它。此外,需要关注的一点是速度,Compute 的方法被调用了数百万次,可能需要几毫秒或几分钟、几小时。【参考方案2】:

我可能遇到了一个简单的解决方案。我把它贴在这里,这样你就可以判断它是否足够,或者即使它可以改进——当然。我确信不需要运行时多态,甚至不需要多态。无论如何,成员变量compute 将是一个指向Compute_Prop 类型的指针。那么,鉴于性能在这里很关键:为什么要运行虚拟成员函数的额外开销?

这里的重点是实现一个隐藏 Compute_Prop 的包含而不损失性能的实现。如何?这个特定的解决方案使用模板类,然后显式实例化。关键是可以在实现中进行实例化。从a Fluent C++ blog post 得到它。此外,this post 提供了如何实现的提示。原型是:

interface.h

template <typename T>
class Compute 
  public:
    Compute();
    Compute(targ1 arg1, targ2 arg2);
    ~Compute();
    // some methods ...
  private:
    T* compute; // No need to state that is going to be T:=Compute_Prop
;

interface_impl.h

#include "interface.h"    
#include "programA.h"

template <typename T>
Compute::Compute() = default;

template <typename T>
Compute::~Compute()                                                                                                               
    delete compute;                                                                                                                                                                                                                                                                                                                                                                


template <typename T>
Compute::Compute(arg1, arg2) 

  // do something ... to get data

  compute = new T( &data, arg2 );

interface.cpp

 #include "interface.h"
 #include "interface_impl.h"
 #include "programA.h"
 #include "programB.h" // loads Compute_Prop

 int main(int argc, char** argv) 

   template class Compute<Compute_Prop>;

 

另一个相关的question 可能对那些有同样困境的人有用。

【讨论】:

以上是关于指向 C++ 中类的指针的 PIMPL 习语的主要内容,如果未能解决你的问题,请参考以下文章

如何将字符串保存到 C++ 中类的 string* 成员?

模板类的智能指针?

使用智能指针进行继承的 pimpl

如何在 C++ 中获取抽象(?)pimpl 的调试信息?

C++ const 正确性漏洞或意外使用?

关于C++基类、派生类的引用和指针