模板化类的Pimpl

pimpl for a templated class

本文关键字:Pimpl      更新时间:2023-10-16

我想使用pimpl习惯用法来避免我的库的用户需要我们的外部依赖(如boost等),但是当我的类被模板化时,这似乎是不可能的,因为方法必须在头文件中。有什么我可以代替的吗?

如果类是模板化的,你的用户本质上需要编译它(在最广泛使用的c++实现中确实如此),因此他们需要你的外部依赖项。

最简单的解决方案是将类的大部分实现放在非模板基类(或某些类的封装成员对象)中。解决模块隐藏问题。

然后编写模板派生(或封闭)类,为其添加类型安全。

例如,假设您有一个模板,它提供了在首次访问时进行分配的惊人能力(省略了必要的复制构造函数、赋值函数、析构函数):
template <class T>
class MyContainer
{
    T *instance_;
public:
    MyContainer() : instance_(0) {}
    T &access()
    {
        if (instance_ == 0)
            instance_ = new T();
        return *instance_;
    }
};

如果你想把"逻辑"分离成一个非模板基类,你必须以非模板的方式参数化行为,也就是说,使用虚函数:

class MyBase
{
    void *instance_;
    virtual void *allocate() = 0;
public:
    MyBase() : instance_(0) {}
    void *access()
    {
        if (instance_ == 0)
            instance_ = allocate();
        return instance_;
    }
};
然后你可以在外层添加类型感知:
template <class T>
class MyContainer : MyBase
{
    virtual void *allocate()
        { return new T(); }
public:
    T &access()
        { return *(reinterpret_cast<T *>(MyBase::access())); }
};

。使用虚函数允许模板"填充"与类型相关的操作。显然,只有当您有一些值得隐藏的业务逻辑时,这种模式才真正有意义。

您可以在源文件中显式地实例化模板,但这只有在您知道模板类型的情况下才有可能。否则,不要在模板中使用pimpl习语。

像这样:

header.hpp:

#ifndef HEADER_HPP
#define HEADER_HPP
template< typename T >
class A
{
  // constructor+methods + pimpl
};
#endif

source.cpp:

#include "header.hpp"
// implementation
// explicitly instantiate for types that will be used
template class A< int >;
template class A< float >;
// etc...

一般有两种解决方案:

  • 虽然接口依赖于某些类型T,但它遵循更弱类型的实现(例如直接使用void*指针或通过类型擦除),或者

  • 你只支持一种特定的、相当有限的类型

第二个解决方案适用于例如char/wchar_t依赖性物质。

第一种解决方案在c++模板的早期是相当普遍的,因为那时编译器不善于识别生成的机器代码中的共性,并且会引入所谓的"代码膨胀"。今天,让任何尝试过模板化解决方案的新手感到惊讶的是,模板化解决方案通常比依赖于运行时多态性的解决方案占用更小的机器代码。当然,YMMV。

干杯,hth。