使用模板类的pimpl惯用法有什么优势吗?

4

据我所知,pimpl惯用法的主要优点是将数据成员隐藏在实现文件中,而不是头文件中。然而,模板需要在头文件中完全定义,以便编译器能够按需实例化它们。在这种情况下,对于一个带有模板的类来说,使用pimpl惯用法是否有任何优势呢?

5个回答

5

虽然在模板类中使用pimpl技巧并不能真正隐藏任何内容,但它确实可以让你轻松编写非抛出式交换函数(尽管在C++11移动语义下,这已经不是一个问题了)。


1
+1 完全忘记了这一点。除了非抛出式的清洁度外,对于交换而言,这也是一种效率优势。对于移动语义来说,这也同样适用,因为一个不透明的类并不像一个有粉刺的类那样容易被移动。 - Christian Rau
1
接受,因为这似乎是唯一的真正好处。 - Oscar Korz

1
在大型项目中,仅解耦翻译单元就足以成为pimpl的充分理由。即使使用模板也可以实现这一点:
// main interface

template <typename> struct MyImpl;

class TheInterface
{
  MyImpl<int> * pimpl;
};

// implementation

#include "MyImpl.hpp" // heavy-weight template library

// TheInterface implementation

1
虽然不太好看,但你可以将主模板的实现与类定义分开,并在该TU中使用显式实例化。如果您只有少量固定数量的模板参数,则这只有意义。但是,否则整个pimpl思想可能并不那么合适。 - Kerrek SB
在一个大型项目中,pimpl惯用语是使项目变得更大的好方法。结果是一种用C++编写的C#程序,在这种情况下,直接移植到C#可能会比使用pimpl更好。请注意,当预编译头已经处理时,pimpl惯用语的编译速度改进将是无用的。pimpl是反C++的 - Patrick Fromberg
真遗憾,使用std::unique_ptr<MyImpl<int>>* pimpl不起作用,必须使用裸指针。 - Ghita

1

有一种情况可能并不严格属于pimpl习惯用法,但足够类似以值得了解。那就是在非类型安全的版本上使用类型安全模板包装器。

class MapBase
{
   public:
     void* getForKey(const std::string & k);
     void setForKey(const std::string & k, void * v);
     ...
};

template<typename T>
class MyMap
{
  public:
    T* getForKey(const std::string &k) { return (T*)base_.getForKey(k); }
    void setForKey( const std::string &k, const T* v) { base_.setForKey(k, T*v); }
  private:
   MapBase base_;
};

现在任何对 MyMap<T> 的使用都不需要暴露给 MapBase 的内部,而且你只会得到这些函数实现的一个版本。我还考虑将 MapBase 设为抽象基类,以使解耦更加强大。

正如我所说的,它并不完全是 pimpl,但它以类似的方式解决了许多相同的问题。


0

我认为,如果你稍微扩展一下这个习语,在某些情况下你至少可以从中获得一些好处。在模板中,并不是每个操作都必须依赖于模板参数。因此,你可以从一个Impl类继承,它本身使用了pimpl习惯用法,就像这样:

struct FooPimpl;

class FooImpl {
  protected:
    FooPimpl* pimpl;
  public:
    void myCommonInterfaceMethod();
};

template <typename T> class Foo : public FooImpl {
  // stuff that depends on T
};

当然,这大大取决于情况。但我看到pimpl惯用语在模板类环境中发挥作用。

0

它仍然非常可用 - 考虑使用自动或共享指针,这是一个模板,非常适用于解决和实现PIMPL。是否是最佳解决方案取决于问题。

为了让编译器能够按需实例化模板,需要在头文件中完全定义模板。

您可以声明模板的成员和接口,然后在类型的cpp文件中,您可以#include所需的特化定义并在那里使用它们。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接