我想使用 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 idiom。
类似这样的事情:
标题.hpp:
#ifndef HEADER_HPP
#define HEADER_HPP
template< typename T >
class A
{
// constructor+methods + pimpl
};
#endif
源.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。