我想使用 pimpl 习惯用法来避免我的库的用户需要我们的外部依赖项(如 boost 等),但是当我的类被模板化时,这似乎是不可能的,因为方法必须在标题中。有什么我可以做的吗?
3 回答
如果类是模板化的,您的用户基本上需要编译它(这在最广泛使用的 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 用于模板。
像这样的东西:
header.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*
直接使用指针或通过类型擦除),或者您仅支持特定且数量有限的类型。
第二种解决方案与 eg char
/相关的wchar_t
东西有关。
第一个解决方案在 C++ 模板的早期很常见,因为当时编译器不善于识别生成的机器代码中的共性,并且会引入所谓的“代码膨胀”。今天,令任何尝试过的新手感到惊讶的是,模板化解决方案通常比依赖运行时多态性的解决方案具有更小的机器代码占用空间。当然,YMMV。
干杯&hth.,