使用预先存在的类的C ++ Pimpl Idiom

时间:2019-04-09 13:14:07

标签: c++ c++11

我们有一个客户希望访问的,带有大量模板的仅标头代码库。例如,假设它在标头Foo中包含foo.hpp类:

#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.hpp中的标头foo_wrapper.hpp。我们必须以某种方式将这些代码编译到一个库中,然后仅分发已编译的库和foo_wrapper头。

2 个答案:

答案 0 :(得分:3)

您可以在Foo中向前声明FooWrapper.h。这将允许您为其声明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;

答案 1 :(得分: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);
}