pimpl 用于模板化类

发布于 2024-12-11 17:36:29 字数 95 浏览 0 评论 0原文

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

I want to use the pimpl idiom to avoid having users of my library need our external dependencies (like boost, etc) however when my class is templated that seems to be impossible because the methods must be in the header. Is there something I can do instead?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

尝蛊 2024-12-18 17:36:30

如果该类是模板化的,那么您的用户本质上需要编译它(在最广泛使用的 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())); }
};

即你使用虚函数来允许模板来“填写”依赖于类型的内容 运营。显然,只有当您有一些值得努力隐藏的业务逻辑时,这种模式才真正有意义。

If the class is templated, your users essentially need to compile it (and this is literally true in the most widely-used C++ implementations) and so they need your external dependencies.

The simplest solution is to put the bulk of your class's implementation in a non-template base class (or encapsulated member object of some class). Solve the module-hiding problem there.

And then write the template derived (or enclosing) class to add type safety to it.

For example, suppose you have a template that provides the amazing ability to allocate on first access (omitting the necessary copy constructor, assignment, destructor):

template <class T>
class MyContainer
{
    T *instance_;

public:
    MyContainer() : instance_(0) {}

    T &access()
    {
        if (instance_ == 0)
            instance_ = new T();

        return *instance_;
    }
};

If you wanted the "logic" to be separated into a non-template base class, you'd have to parameterise the behaviour in the non-template way, which is to say, use virtual functions:

class MyBase
{
    void *instance_;

    virtual void *allocate() = 0;

public:
    MyBase() : instance_(0) {}

    void *access()
    {
        if (instance_ == 0)
            instance_ = allocate();

        return instance_;
    }
};

Then you can add the type-awareness in the outer layer:

template <class T>
class MyContainer : MyBase
{
    virtual void *allocate()
        { return new T(); }

public:
    T &access()
        { return *(reinterpret_cast<T *>(MyBase::access())); }
};

i.e. You use virtual functions to allow the template to "fill in" the type-dependent operations. Obviously this pattern would only really make sense if you have some business logic that is worth the effort of hiding.

凉世弥音 2024-12-18 17:36:30

您可以在源文件中显式实例化模板,但只有当您知道模板类型是什么时才可能实现。否则,不要对模板使用 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...

You can explicitly instantiate templates in the source file, but that is possible only if you know what the template type is going to be. Otherwise, do not use pimpl idiom for templates.

Something like this :

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...
撑一把青伞 2024-12-18 17:36:30

有两种通用的解决方案:

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

  • 您仅支持特定且数量相当有限的类型。

第二个解决方案与例如 char/wchar_t 相关的内容相关。

第一种解决方案在 C++ 模板的早期相当常见,因为当时编译器不善于识别生成的机器代码中的共性,并且会引入所谓的“代码膨胀”。如今,令任何尝试它的新手大吃一惊的是,模板化解决方案通常比依赖运行时多态性的解决方案具有更小的机器代码占用空间。当然,YMMV。

There are two general solutions:

  • while the interface depends on some type T, it defers to a more weakly typed implementation (e.g. one using void* pointers directly or trough type erasure), or

  • you support only a specific and quite limited number of types.

The second solution is relevant for e.g. char/wchar_t-dependent stuff.

The first solution was quite common in the early days of C++ templates, because at that time compilers were not good at recognizing commonalities in the generated machine code, and would introduce so called "code bloat". Today, much to the surprise of any novice who tries it out, a templated solution can often have smaller machine code footprint than a solution relying on runtime polymorphism. Of course, YMMV.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文