簡體   English   中英

使用PIMPL習語時有沒有辦法限制重復的樣板?

[英]Is there any way to limit repetitive boilerplate when using the 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 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;
}

然后:

// foo.cpp:
class foo::impl {
public:
    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:
    // lots of private state, helper functions, etc. 
};

void foo::impl::bar(a b, c d) { ... }
void foo::impl::baz(a b, c d, e f, g h, i j) { ... }
void foo::impl::quux(a b, c d, e f, g h, i j) { ... }

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

這里有很多重復的barbazquux

  • 曾經在foo的聲明中
  • 一旦進入foo::impl的聲明
  • 一旦進入foo::impl的定義
  • foo的定義中有兩次:一次用於foo的函數參數列表,再次調用相應的foo::impl函數。

對於除了最后一個之外的每一個,我必須寫出整個參數列表,其類型可以更多地涉及。

如果有的話,減少重復的最佳方法是什么? 一個簡單的方法是內聯foo::impl公共函數的定義,但是除了設計類以外的任何東西以外還有更少的公共函數嗎?

使用簽名,我們可以將其減少到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;}

如果我們的pimpl是純虛擬類,我們可以將它降低到2 +前進。 decltype(&foo::method) - > signature寫一個映射,並讓純虛擬接口使用該(和decltype)來創建純虛擬類的簽名。

foo寫入一次簽名,decltype-在foo_impl_interface確定它,然后在cpp文件中的foo_impl內實現它。 再加上一組前鋒。

 template<class MethodPtrType>
 struct method_sig;
 template<class MethodPtrType>
 using method_sig_t = typename method_sig<MethodPtrType>::type;
 template<class T, class R, class...Args>
 struct method_sig< R(T::*)(Args...) > {
   using type=R(Args...);
 };

加上另外11個奇數專長(嘆氣)來獲得所有案例。 const& const const&& const volatile const volatile& const volatile&& & && 據我所知, volatile volatile& volatile&& qualifiers都是必需的)。

現在method_sig_t<decltype(&foo::x)>int(int) ,我們可以使用它:

struct foo_impl_interface {
  virtual method_sig_t<decltype(&foo::x)> x = 0;
  virtual ~foo_impl_interface() {}
};

假設我們使用pimpl來規范我們的類型而不是隱藏狀態。


最后,不是在pimpl中實現大部分代碼,而只是將STATE存儲在pimpl中,而是將代碼保留在類本身中。

這給你“其他人不依賴於我的大小”:誰關心代碼是在foo還是foo_impl中讀取foo_impl的狀態? 所以如果你沒有做foo_impl_interface技術,為什么要前進呢?

如果您的公共類只是代理調用實現,請考慮使用interface - > implementation model而不是pimpl。

您只能通過用戶代碼中的接口來引用您的實現,其中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背后的想法是將私有數據和函數存儲在單獨的對象指針中,這樣您就不會在數據存儲和處理更改時破壞公共類接口。 但它並不意味着所有代碼都移動到私有對象。 因此,您通常會在適當的位置實施公共功能。 當公共函數實現發生更改時,您不會破壞用戶代碼,因為您的公共函數實現將與私有類定義一起隱藏。

您可以這樣做的一種方法是定義一個接口類,這樣您就不需要重新聲明所有內容並使用傳遞指針語義(重載operator->)

這是一個例子: 是否可以在c ++中編寫敏捷的Pimpl?

要結束這個問題:最終我認為Adrian的評論最好地解決了這個問題:“我傾向於在類定義中實現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 ...
}

現在它更合理 - 只有一個用於聲明,一個用於定義。 在轉換類以使用m_pimpl->添加m_pimpl-> s時會有很小的開銷,但是IMO這比沒有重復會更煩人。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM