使用 PIMPL 习惯用法,实现是否应该始终是 class 的私有成员?

Using the PIMPL idiom, should the implementation always be a private member of the class?

我见过 PIMPL idiom 以两种不同的方式实现。一种方法是始终使实现成为 class 的私有成员。这确保无论如何都无法从 class 外部访问该实现。

例如:

Example.h

#ifndef EXAMPLE_H
#define EXAMPLE_H

#include <memory>

class Example {
public:
    void doSomething();
private:
    struct Impl;
    std::shared_ptr<Impl> pimpl_;
};

#endif /* EXAMPLE_H */

Example.cpp

#include "Example.h"
#include <iostream>

struct Example::Impl {
    void doSomething() {
        std::cout << "Hello World!" << std::endl;
    }
};

void Example::doSomething() {
    pimpl_->doSomething();
}

main.cpp

#include "Example.h"

int main() {
    Example ex;
    ex.doSomething();
    system("pause");
}

我看到的另一种方法是使实现成为一个完全独立的 class,它有自己的 .h 文件和自己的 .cpp 文件。

例如:

Example.h

#ifndef EXAMPLE_H
#define EXAMPLE_H

#include <memory>

struct ExampleImpl;

class Example {
public:
    void doSomething();
private:
    std::shared_ptr<ExampleImpl> pimpl_;
};

#endif /* EXAMPLE_H */

ExampleImpl.h

#ifndef EXAMPLE_IMPL_H
#define EXAMPLE_IMPL_H

struct ExampleImpl {
public:
    void doSomething();
};

#endif /* EXAMPLE_IMPL_H */

Example.cpp

#include "Example.h"
#include "ExampleImpl.h"
#include <iostream>

void Example::doSomething() {
    pimpl_->doSomething();
}

ExampleImpl.cpp

#include "ExampleImpl.h"
#include <iostream>

void ExampleImpl::doSomething() {
    std::cout << "Hello World!" << std::endl;
}

main.cpp

#include "Example.h"

int main() {
    Example ex;
    ex.doSomething();
    system("pause");
}

我能看到的唯一显着区别是使用第二种方法,您无需通过 Example class 即可访问实现。不过,我个人认为没有任何理由需要这样做。

这两种方法似乎都有效并满足了最终目标,但是选择一种方法比另一种方法有什么真正的优势吗?

这显然是一个见仁见智的问题。

我认为使用第二种方法违背了 Pimpl 习语的精神。

  1. 现在外部可以看到实施细节。
  2. 如果对 Example 的界面进行任何更改,很可能会影响四个文件而不是两个。
  3. 如果对 ExampleImpl 的实施方式进行任何更改,很可能会影响三个文件,而不是一个。

鉴于以上几点,我建议使用嵌套 class 方法。

我实际上发现了第二种方法明显胜出的情况。如果您需要从使用 PIMPL 惯用语的 class 派生来创建也使用 PIMPL 惯用语的派生 class,则可以 运行 解决继承问题。

如果派生 class 的实现 class 没有继承自基础 class 的实现 class,您将无法在其中使用任何继承的函数派生的执行class这可以是一个总的表演塞子!

除非有人能找到解决这个问题的简单方法,否则我将被迫使用第二种方法。

编辑:

原来不用走第二种方法也能解决这个问题!您可以简单地使派生 class 的实现 class 继承自基础 class 的非实现 class!这为您提供了 none 的所有功能!