使用现有类的C++ Pimpl惯用法

3
我们有一个大量使用模板的头文件代码库,客户想要访问该代码库。例如,假设它在头文件foo.hpp中包含了Foo类。
#ifndef FOO_HEADER
#define FOO_HEADER

#include <iostream>

template <typename T>
struct Foo {

    Foo(){
       // Do a bunch of expensive initialization
    }

    void bar(T t){
        std::cout << t; 
    }

    // Members to initialize go here... 
};

#endif /* FOO_HEADER */

现在我们希望客户端尝试一组缩减的功能,而不需要暴露核心代码或重写整个代码库。一个想法是使用 PIMPL 惯用语法来包装这个核心代码。具体来说,我们可以创建一个名为 FooWrapper 的类,并编写头文件 foo_wrapper.hpp:
#ifndef FOO_WRAPPER_HEADER
#define FOO_WRAPPER_HEADER

#include <memory>

struct FooWrapper {

    FooWrapper();
    ~FooWrapper();

    void bar(double t);

    private: 
        struct Impl;
        std::unique_ptr<Impl> impl;
};

#endif /* FOO_WRAPPER_HEADER */

实现 foo_wrapper.cpp

#include "foo.hpp"
#include "foo_wrapper.hpp"

struct FooWrapper::Impl {
    Foo<double> genie;
};

void FooWrapper::bar(double t){
    impl->genie.bar(t);
}

FooWrapper::FooWrapper() : impl(new Impl){
}

FooWrapper::~FooWrapper() = default;

这段代码按照我的期望工作:https://wandbox.org/permlink/gso7mbe0UEOOPG7j 然而,有一件小事情困扰着我。具体来说,这个实现需要额外的间接层级...我们必须定义Impl类来持有Foo类的成员。因此,所有操作都需要以这种形式的间接方式进行:impl->genie.bar(t);
如果我们能够告诉编译器,“实际上Impl就是Foo<double>类”,那将更好,这样,我们可以改为使用impl->bar(t);
具体来说,我想的是使用typedefusing来实现这一点。类似于:
using FooWrapper::Impl = Foo<double>;

但是这段代码无法编译。所以接下来是问题:

  1. 有没有一种好的方法可以摆脱这种间接性?
  2. 是否有更好的习惯用法?

我正在寻找一个C++11的解决方案,但C++14也可能适用。重要的是要记住,该解决方案不能使用foo_wrapper.hpp中的头文件foo.hpp。不知何故,我们必须将该代码编译成库并仅分发编译好的库和foo_wrapper头文件。


你是否担心间接引用的运行时成本或语法“开销”? - Martin Ba
1
只是语法繁琐而已,我并不确定是否有任何显著的运行时开销。 - bremen_matt
2个回答

4

您可以在 FooWrapper.h 中使用前向声明来声明 Foo。这样,您就可以为其声明一个 std::unique_ptr

#ifndef FOO_WRAPPER_HEADER
#define FOO_WRAPPER_HEADER

#include <memory>

// Forward declaration
template <typename T>
class Foo;

struct FooWrapper {
  FooWrapper();
  ~FooWrapper();

  void bar(double t);

 private:
  std::unique_ptr<Foo<double>> impl;
};

#endif /* FOO_WRAPPER_HEADER */

foo_wrapper.cc:

#include "foo_wrapper.h"
#include "foo.h"

void FooWrapper::bar(double t) {
  impl->bar(t);
}

FooWrapper::FooWrapper() : impl(std::make_unique<Foo<double>>()) {}

FooWrapper::~FooWrapper() = default;

您可以直接使用std::unique_ptr<Foo<double>> impl;。无需引入using别名,也无需手动模板实例化。构造函数的实现只需执行FooWrapper::FooWrapper() : impl(std::make_unique<Foo<double>>()) {}即可。 - Nikos C.
@NikosC。糟糕,对了。我的测试代码有一个“bug”。 - Mike van Dyke

1
只需使用 Foo<double>:
// forward declaration so that you don't need to include "Foo.hpp"
template class Foo<double>;

struct FooWrapper {
    //...
    std::unique_ptr<Foo<double>> impl;
};


// explicit template instantiation so that Foo<double> exists without distributing "Foo.hpp"
template class Foo<double>;

void FooWrapper::bar(double t){
    impl->bar(t);
}

这将需要向客户提供 foo.hpp 代码。我们不想这样做,因为其中包含专有代码。 - bremen_matt
除非我误解了,你的 foo_wrapper.hpp 代码将不得不导入 foo.hpp,而这正是我试图避免的。 - bremen_matt
相反地,在我提出的版本中,我们只需要给客户端foo_wrapper.hpp和从foo_wrapper.cpp编译的库。这在一定程度上保护了专有代码(假设他们不会反编译代码)。 - bremen_matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接