2017-11-25 91 views
2

考慮一個封閉類層次結構如下述:減少對基礎樣板衍生委託在非虛擬多態類

class B {...}; 
class D1 final : public B {...}; 
class D2 final : public B {...}; 

B是一個抽象的基類和D1D2是它的派生類。

由於實現約束或設計,沒有這些類的具有任何virtual方法,但成員函數上BD1具有不同的實現並D2簡單地通過使衍生的運行時檢查委託給實際最派生的類型類型,如下所示:

class B { 
    bool isD1; 
protected: 
    B(bool isD1) : isD1{isD1} {} 
public: 
    std::string to_string() { 
    return isD1 ? static_cast<D1*>(this)->to_string() : static_cast<D2*>(this)->to_string(); 
    } 
} 

class D1 final : public B { 
public: 
    D1() : B(true) {} 
    std::string to_string() { // D1 specific implementation ... } 
} 

class D2 final : public B { 
public: 
    D2() : B(false) {} 
    std::string to_string() { // D2 specific implementation ... } 
} 

上。這裏Bto_string方法只檢查的B最派生類型是D1D2或並調用適當的方法(也使用C稱爲to_string ASES)。

很酷。

現在想象有另外10種方法,如B::to_string。我可以在C++ 11中做什麼來減少B中的委託樣板文件,而不使用宏?

在C++ 14它似乎是一個合理的做法是一個通用的代理機制,如:

class B { 
    ... 

    template <typename F> 
    auto delegate(F&& f) -> decltype(f(D1{})) { 
    return isD1 : f(*static_cast<D1*>(this)) : f(*static_cast<D2*>(this)); 
    } 

    std::string to_string() { 
    return delegate([](auto&& b){ return b.to_string(); }); 
    } 
} 

這裏[](auto&& b){ return b.to_string(); }通用拉姆達作品是否最終通過了D1D2(因爲兩者具有to_string方法) 。在C++ 11中,我沒有看到用同樣簡潔的方式表達這一點。

任何想法?當然,你可以使用宏來複制一個非泛型的宏,並將它傳遞給一個雙參數的delegate方法(對D1D2採用不同的函子),但我想避免使用宏。


這裏關閉意味着該組B派生類的固定和在運行時是已知的。

摘要在概念但不是在 「純virtual」 感。那就是這個類不應該直接實例化 - 唯一有意義的整個對象是它的派生類。各種構造函數都製作爲protected來執行此操作。

+0

我是否正確拒絕了基於預處理器宏的解決方案? – lockcmpxchg8b

+0

@ lockcmpxchg8b - 正確。事實上,我已經在使用這樣的解決方案,因爲編寫'DELEGATE(x)'宏很容易,所以'DELEGATE(std :: string,to_string)'簡單地擴展到'B :: to_string()'實現如上所示,然後一些其他的宏來處理帶參數的函數等。 – BeeOnRope

+0

@BeeOnRope - 我完全誤解了這個問題;抱歉。 – max66

回答

1

這個怎麼樣?

template <typename F1, typename F2> 
    auto delegate(F1 f1, F2 f2) -> decltype((D1{}.*f1)()) { 
    return isD1 ? (static_cast<D1*>(this)->*f1)() : (static_cast<D2*>(this)->*f2)(); 
    } 

    std::string to_string() { 
    return delegate(&D1::to_string, &D2::to_string); 
    } 

你也可以把它更強類型:

template <typename Result> 
    Result delegate(Result (D1::*f1)(), Result (D2::*f2)()) { 
    return isD1 ? (static_cast<D1*>(this)->*f1)() : (static_cast<D2*>(this)->*f2)(); 
    } 
+1

看起來不錯。 'delegate'也可以被擴展,以便將其他參數完美地轉發給派生函數,假設其中一些可能有參數。 – aschepler

1

這不是一個答案,這是一個可憎的。但我認爲,因爲

  • 的OP暗示他/她用一個更簡單的基於宏的計劃我會分享,並
  • 接近「零樣板soluton」。

以下是一個工作示例。

#include <string> 
#include <sstream> 
#include <iostream> 
#include <delegate_macros> 

#define FOREACH_DELEGATE(A) \ 
    A(std::string, to_string,(),  ())\ 
    A(void,  setInt, (int a), (a))\ 

class B 
{ 
    DECLARE_VTAB_MEMBERS 
public: 
    B(DELEGATE_ARGS) : INITIALIZER_LIST { } 

    DEFINE_DELEGATORS 
}; 

class D1 : public B 
{ 
    int m_i; 
public: 
    D1() : B(PASS_DELEGATES) {} 
    void setInt(int i) {m_i = i;} 
    std::string to_string() {std::stringstream ss; ss << "D1:" << m_i; return ss.str();} 
}; 

class D2 : public B 
{ 
    int m_i; 
public: 
    D2() : B(PASS_DELEGATES) {} 
    void setInt(int i) {m_i = i * 5;} 
    std::string to_string() {std::stringstream ss; ss << "D2:" << m_i; return ss.str();} 
}; 

int main(int argc, char *argv[]) 
{ 
    D1 d1; 
    D2 d2; 

    B *ref = &d1; 

    ref->setInt(2); 
    std::cout << "((B*)&d1)->toString: " << ref->to_string() << std::endl; 

    ref = &d2; 
    ref->setInt(2); 
    std::cout << "((B*)&d2)->toString: " << ref->to_string() << std::endl; 
} 

息率

$ ./a.out 
((B*)&d1)->toString: D1:2 
((B*)&d2)->toString: D2:10 

的宏在delegate_macros是獨立的B的結構和它的子類:

#define MAKE_DELEGATOR(ret, name, params, args)\ 
    ret name params\ 
    {\ 
    return (this ->* m_##name) args;\ 
    } 

#define MAKE_DELEGATE_REF(ret, name, params, args) (ret (B::*) params)&name, 

#define DECLARE_VTAB_MEMBER(t,n,p,a) t (B::*m_##n)p; 

#define MAKE_CTOR_INITIALIZER(t,n,p,a) m_##n(n), 
#define MAKE_CTOR_ARG(t,n,p,a) t (B::*n) p, 
#define MAKE_CTOR_PARAMS(t,n,p,a) t (B::*m_##n)p, 

#define DECLARE_VTAB_MEMBERS FOREACH_DELEGATE(DECLARE_VTAB_MEMBER) char dummy; 
#define INITIALIZER_LIST  FOREACH_DELEGATE(MAKE_CTOR_INITIALIZER) dummy() 
#define DEFINE_DELEGATORS FOREACH_DELEGATE(MAKE_DELEGATOR) 
#define DELEGATE_ARGS  FOREACH_DELEGATE(MAKE_CTOR_ARG) void * 
#define PASS_DELEGATES  FOREACH_DELEGATE(MAKE_DELEGATE_REF) NULL 

有AR我有幾個理由稱它爲可憎的:

  • 它手動使VTABLE ...,如果你想要一個VTABLE,只需使用虛擬。
  • 它將簡單的拼寫轉換爲輸出頁面。
  • 它執行每個子類的指針到(B::*)變體的未經檢查的轉換。
    • 並且使用所謂的方法指針調用子類方法。
  • 它添加了虛擬構造函數參數和成員變量,使宏擴展更容易。
  • 沒有人可以閱讀它,除非他們花了幾年時間寫這種宏。
  • FOREACH_DELEGATE必須在您想要使用宏的每個翻譯單元中定義,因此只有在B及其所有子類都在一個文件中定義時才適用。如果您想將B置於FOREACH_DELEGATE旁邊的標題中,您必須創建額外的宏來聲明委託人與定義他們。