C++ 在使用PIMPL习惯用法时,有没有办法限制重复的样板文件?

C++ 在使用PIMPL习惯用法时,有没有办法限制重复的样板文件?,c++,pimpl-idiom,C++,Pimpl Idiom,我有如下几点: // foo.h: class foo { public: foo(); ~foo(); // note: the param type repetition here is only incidental, assume the // functions can't easily be made to share type signatures void bar(a b, c d); void ba

我有如下几点:

// foo.h:
class foo {
public:
    foo(); 
    ~foo();
    // note: the param type repetition here is only incidental, assume the
    // functions can't easily be made to share type signatures
    void bar(a b, c d);               
    void baz(a b, c d, e f, g h, i j);
    void quux(a b, c d, e f, g h, i j);
private:
    class impl;
    impl *m_pimpl;
}
然后:

这里有很多重复的
bar
baz
quox

  • foo
    的声明中
  • 一旦进入
    foo::impl
    的声明
  • 一旦进入
    foo::impl
    的定义
  • foo
    的定义中两次:一次用于
    foo
    的函数参数列表,另一次用于调用相应的
    foo::impl
    函数
对于除最后一个之外的每一个参数,我必须写出整个参数列表,其类型可能更复杂


如果有的话,减少重复的最好方法是什么?一个简单的方法是在代码中定义“代码> Fo::IMPL/COD>公共函数,但除了设计不同的类之外,除了公共功能之外,还有什么别的吗?”

< P>如果您的公共类只代理调用实现,请考虑使用接口->实现模型,而不是PIMLL。 您将仅通过用户代码中的接口引用您的实现,其中
class foo
是接口

class foo {
public:
    virtual void bar(a b, c d) = 0;               
    virtual void baz(a b, c d, e f, g h, i j) = 0;
    virtual void quux(a b, c d, e f, g h, i j) = 0;
    virtual ~foo(){}
}

pimpl背后的思想是将私有数据和函数存储在单独的对象指针中,以便在数据存储和处理更改时不会破坏公共类接口。但这并不意味着所有代码都移动到私有对象。因此,您通常只在适当的位置执行公共功能。当您的公共函数实现发生更改时,您不会破坏用户代码,因为您的公共函数实现将与私有类定义一起对用户隐藏。

使用签名,我们可以将其减少为3次提及,再加上1组转发:

using signature_1 = int(int);

struct foo {
  signature_1 x;
  foo();
  ~foo();
private:
  struct fooimpl;
  std::unique_ptr<fooimpl> pimpl;
};

int main() {
  foo f;
  std::cout << f.x(1) << '\n';
}
struct foo::fooimpl {
  signature_1 x;
  int v = 3;
};

foo::~foo() = default;
foo::foo():pimpl(new foo::fooimpl()){}

int foo::x(int y){return pimpl->x(y);}
int foo::fooimpl::x(int y){return y+v;}
再加上另外11个专业(叹气)来获得所有病例。(
const&
const&
const&
const volatile&
const volatile&
const volatile&
volatile&
volatile&
volatile&
就我所知,限定符都是必需的)

现在
method\u sig\t
int(int)
,我们可以使用:

struct foo_impl_interface {
  virtual method_sig_t<decltype(&foo::x)> x = 0;
  virtual ~foo_impl_interface() {}
};
struct foo\u impl\u接口{
虚拟方法_sig_t x=0;
虚拟~foo_impl_接口(){}
};
假设我们使用pimpl规范化我们的类型,而不是隐藏状态


最后,与其在pimpl中实现大部分代码,不如将状态存储在pimpl中,将代码留在类本身中


这就给了你“其他人不依赖于我的尺寸”:谁在乎代码是处于
foo
还是
foo\u impl
读取
foo\u impl
的状态?因此,如果您不使用
foo\u impl\u接口
技术,为什么要前进?

一种方法是定义接口类,这样您就不需要重新声明所有内容并使用传递指针语义(重载运算符->)

以下是一个例子:

结束这个问题:最终我认为解决这个问题最好:“我倾向于在类定义中实现impl类的方法,这减少了一些重复。”

我上面的代码将变成:

// foo.cpp:
class foo::impl {
public:
    // lots of private state, helper functions, etc. 
};

foo::foo() : m_pimpl(new impl()) { }
foo::~foo() { delete m_pimpl; m_pimpl = NULL; }
void foo::bar(a b, c d) {
    ... code using m_pimpl-> when necessary ...
}
void foo::baz(a b, c d, e f, g h, i j) {
    ... code using m_pimpl-> when necessary ...
}
void foo::quux(a b, c d, e f, g h, i j) {
    ... code using m_pimpl-> when necessary ...
}

现在它更合理了——一个用于声明,一个用于定义。在将类转换为使用pimpl时,添加
m\u pimpl->
s会带来很小的开销,但在我看来,这比所有重复操作都不那么烦人

定义一个类型签名,并使用它,而不是在其中几个点中列出参数?也许可以使用decltype和一些特性来帮助?@Yakk:Hmm你能举个例子吗?关于处理样板构造函数和析构函数,看看,如果汇编代码只是将
ECX
替换为
m_pimpl
并将
jmp
替换为适当的函数,这不是很好吗……我倾向于在类定义中实现impl类的方法,这减少了一些重复。有趣的是,我必须仔细看看。关于最后一个问题:我还想要
impl
中的所有私有助手函数,不是吗?@Claudiu当然,但这些函数不会创建转发样板文件。如果pimpl来自头文件中已知的接口,则可以使用智能指针而不是pimpl。可能会在上面增加资源管理功能,比如复制和非空默认构造;但是用户可以通过
operator->
@dyp直接使用接口,作为此模式的示例,
std::function
:实现不仅公开pimpl接口,而且在头文件中公开pimpl实现!
std::function
是否应该公开其pimpl,并通过
->
实现非调用非复制操作?我不太确定我是否完全理解您的意思,也不太确定您在谈论哪些操作。我猜
std::function
需要在报头中“公开”其pimpl impl,因为调用签名是依赖的。它也没有很多不直接涉及指针的操作(
target
target\u type
可能?),您现在不需要工厂吗?pimpl方法允许客户端将foo实例化为自动(本地)变量。当然,实现仍然在freestore中,但这是一个对客户端隐藏的实现细节。对于虚拟接口,客户端需要调用工厂函数来显式实例化对象。@AdrianMcCarthy当然是这样。但是请注意,我还提出了另一种解决方案,类似于您自己对该问题的评论,作为另一种选择。有时候你喜欢一个,有时候又喜欢另一个。啊,这就是工厂的优点。起初,这个解决方案似乎很理想,但后来我没有意识到
foo
s的创建者必须导入
foo\u impl.h
,并调用另一个co
struct foo_impl_interface {
  virtual method_sig_t<decltype(&foo::x)> x = 0;
  virtual ~foo_impl_interface() {}
};
// foo.cpp:
class foo::impl {
public:
    // lots of private state, helper functions, etc. 
};

foo::foo() : m_pimpl(new impl()) { }
foo::~foo() { delete m_pimpl; m_pimpl = NULL; }
void foo::bar(a b, c d) {
    ... code using m_pimpl-> when necessary ...
}
void foo::baz(a b, c d, e f, g h, i j) {
    ... code using m_pimpl-> when necessary ...
}
void foo::quux(a b, c d, e f, g h, i j) {
    ... code using m_pimpl-> when necessary ...
}