【问题标题】:C++ Pimpl Idiom using a pre-existing classC++ Pimpl Idiom 使用预先存在的类
【发布时间】:2019-04-09 13:14:07
【问题描述】:

我们有一个客户希望访问的高度模板化的仅标头代码库。例如,假设它在标题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 成语来包装这个核心代码。具体来说,我们可以创建一个带有标题foo_wrapper.hppFooWrapper 类:

#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-&gt;genie.bar(t); 形式的这种间接寻址。

如果我们能以某种方式告诉编译器“实际上Impl 是类Foo&lt;double&gt;”会更好,在这种情况下,我们可以改为说impl-&gt;bar(t);

具体来说,我正在考虑类似于typedefusing 的方法来让它发挥作用。像

using FooWrapper::Impl = Foo<double>;

但这不会编译。那么问题来了:

  1. 有什么好方法可以消除这种间接性吗?
  2. 我应该使用更好的成语吗?

我的目标是 C++11 解决方案,但 C++14 也可以工作。要记住的重要一点是,解决方案不能在foo_wrapper.hpp 中使用标头foo.hpp。不知何故,我们必须将该代码编译到一个库中,并仅分发编译后的库和 foo_wrapper 标头。

【问题讨论】:

  • 您是否担心间接的运行时成本或语法“开销”?
  • 只是语法开销。我不太确定是否存在任何重大的运行时开销

标签: c++ c++11


【解决方案1】:

您可以在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&lt;Foo&lt;double&gt;&gt; impl;。无需引入using 别名,也无需手动模板实例化。然后 ctor 实现可以只做FooWrapper::FooWrapper() : impl(std::make_unique&lt;Foo&lt;double&gt;&gt;()) {}
  • @NikosC。哎呀,对。我的测试代码中有一个“错误”
【解决方案2】:

只需使用Foo&lt;double&gt;:

// 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 代码。我们不想这样做,因为它包含专有代码。
  • 除非我理解错了,否则你的foo_wrapper.hpp 代码必须导入foo.hpp,这正是我想要避免的。
  • 相反,在我建议的版本中,我们只需要给客户端foo_wrapper.hpp和一个从foo_wrapper.cpp编译的库。这在一定程度上保护了专有代码(假设他们不会反编译代码)
猜你喜欢
  • 2015-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-19
  • 2012-10-26
  • 2015-07-16
  • 2011-01-21
  • 1970-01-01
相关资源
最近更新 更多