2015-09-02 79 views
4

比方說,我有一個簡單地爲任何類型T執行加法的類。我想添加一個可選範圍檢查(基於類型爲bool的模板參數),它將檢查結果的加法屬於給定範圍,否則會拋出。 這樣做的一種方法是將類的所有基礎知識包裝在基類中,然後專門研究布爾模板參數。喜歡的東西:基於模板參數的可選範圍檢查

// The base class; holds a starting value to add to and a maximum value 
template<typename T> 
class DummyImpl 
{ 
private: 
    T mval, mmax; 

public: 
    constexpr explicit DummyImpl(T x, T max_x) noexcept 
: mval{x}, mmax{max_x} 
    {}; 

    // base class; use a virtual destructor 
    virtual ~DummyImpl() {}; 

    T max() const noexcept {return mmax;} 
    T val() const noexcept {return mval;} 
}; 

// The "real" class; parameter B denotes if we want (or not) 
// a range check 
template<typename T, bool B> 
class Dummy : DummyImpl<T> {}; 

// Specialize: we do want range check; if sum not in range 
// throw. 
template<typename T> 
class Dummy<T, true> : DummyImpl<T> 
{ 
public: 
    explicit Dummy(T x, T max_x) noexcept : DummyImpl<T>(x, max_x) {}; 

    T add(T x) const noexcept(!true) 
    { 
    T ret_val = x + DummyImpl<T>::val(); 
    if (ret_val < 0 || ret_val > DummyImpl<T>::max()) { 
     throw 1; 
    } 
    return ret_val; 
    } 
}; 

// Specialize for no range check. 
template<typename T> 
class Dummy<T, false> : DummyImpl<T> 
{ 
public: 
    explicit Dummy(T x, T max_x) noexcept : DummyImpl<T>(x, max_x) {}; 

    T add(T x) const noexcept(!false) 
    { 
    return x + DummyImpl<T>::val(); 
    } 
}; 

現在,用戶可以編寫類似的代碼:

int main() 
{ 
    Dummy<float,false> d(0, 1000); //no range check; never throw 

    std::cout <<"\nAdding 156.7 gives " << d.add(156.7); 
    std::cout <<"\nAdding 3156.7 gives " << d.add(3156.7); 

    std::cout <<"\n"; 
    return 0; 
} 

是否有這樣做,而不使用繼承的方式嗎?我假設使用嵌套類會更高效,但下面的代碼 不能編譯。

template<typename T, bool RC> 
class Dummy 
{ 
private: 
    T mval, mmax; 

    // parameter S is only used to enable partial specialization on 
    // parameter I 
    template<bool I, typename S> struct add_impl {}; 

    template<typename S> struct add_impl<true, S> 
    { 
    T operator()(T x) const noexcept(!true) 
    { 
     T ret_val = x + mval; 
     if (ret_val < 0 || ret_val > mmax) {throw 1;} 
     return ret_val; 
    } 
    }; 

    template<typename S> struct add_impl<false, S> 
    { 
    T operator()(T x) const noexcept(!false) 
    { 
     return x + mval_ref; 
    } 
    }; 

public: 
    constexpr explicit Dummy(T x, T max_x) noexcept 
: mval{x}, mmax{max_x} 
    {}; 

    void bar() const { std::cout << "\nin Base."; } 
    T max() const noexcept {return mmax;} 
    T val() const noexcept {return mval;} 
    T add(T x) const noexcept(!RC) 
    { 
    return add_impl<RC, T>()(x); 
    } 
}; 


int main() 
{ 
    Dummy<float,false> d(0, 1000); 

    std::cout <<"\nAdding 156.7 gives " << d.add(156.7); 
    std::cout <<"\nAdding 3156.7 gives " << d.add(3156.7); 

    std::cout <<"\n"; 
    return 0; 
} 

它失敗(以g ++)的錯誤消息:

error: invalid use of non-static data member ‘Dummy<float, false>::mval’ 

有沒有解決這個的方法嗎?如果是這樣,它比第一種解決方案更有效嗎?嵌套類是否將尺寸添加到Dummy的任何實例?有沒有更優雅的設計/實施?

+0

如何根據參數修改模板代碼:查找SFINAE或模板特化。但是這對你的用例真的有用嗎?有一個bool參數爲false,它不會說什麼錯誤會導致代碼不可讀。 – Klaus

回答

3

我只會派發RC。使它成爲一個類型:

template<typename T, bool RC> 
class Dummy 
{ 
private: 
    using do_range_check = std::integral_constant<bool, RC>; 
    T mval, mmax; 
}; 

隨着指出:

T add(T x) const { 
     return add(x, do_range_check{}); 
    } 

private:  
    T add(T x, std::false_type /* range_check */) { 
     return x + mval; 
    } 

    T add(T x, std::true_type /* range_check */) { 
     T ret_val = x + mval; 
     if (ret_val < 0 || ret_val > mmax) {throw 1;} 
     return ret_val; 
    } 

的優點有,這是一個正常的成員函數 - 你不卸載到你需要通過會員一些其他類型到處。你不需要專門化......任何東西。這很棒。

+0

謝謝隊友!這確實很清楚和優雅。我想對於'noexcept'規範我可以使用'noexcept(do_range_check :: value)'。如果我正確地得到它,'do_range_check {}'產生一個'std :: false_type'或一個'std :: true_type'(取決於'RC'),而不僅僅是一個布爾變量(和'do_range_check :: value'一樣) 對? – xnth

+0

@xnth'noexcept(!do_range_check :: value)',否則全部正確。 – Barry

1

編譯器非常擅長消除明顯的死代碼(如源自布爾模板參數的代碼)。因此,我想用最簡單的解決方案去:

template<typename T, bool RC> 
class Dummy 
{ 
private: 
    T mval, mmax; 

public: 
    T add(T x) const noexcept(!RC) 
    { 
    T ret_val = x + val(); 
    if (RC && (ret_val < 0 || ret_val > DummyImpl<T>::max())) { 
     throw 1; 
    } 
    return ret_val; 
    } 

//... 
}; 

我會非常感到驚訝,如果爲實例化產生任何運行時代碼,其中RC == false。事實上,我會認爲這是一個優化器錯誤。

+0

@Agnew感謝隊友;這是我最初的設計,但我不確定編譯器是否會優化它。畢竟,這看起來像一個微不足道的案件。 – xnth

0

您可以使用組成

template<typename T, bool B> struct ThresholdChecker; 

template<typename T> 
struct ThresholdChecker<T, true> 
{ 
    ThresholdChecker(T value) : mMax(value) {} 

    void check(T value) const 
    { 
      if (value < 0 || mMax < value) { 
       throw std::out_of_range(""); 
      } 
    } 
private: 
    T mMax; 
}; 

template<typename T> 
struct ThresholdChecker<T, false> 
{ 
    ThresholdChecker(T) {} 

    void check(T) const noexcept {} 
}; 


template<typename T, bool RC> 
class Dummy 
{ 
private: 
    T mval; 
    ThresholdChecker<T, RC> mThresholdChecker; 

public: 
    explicit Dummy(T x, T max_x) noexcept : mVal(x), mThresholdChecker(max_x) {}; 

    T add(T x) const noexcept(noexcept(mThresholdChecker.check(x))) 
    { 
    T ret_val = x + val(); 
    mThresholdChecker.check(ret_val); 
    return ret_val; 
    } 

//... 
}; 
1

我平時儘量不使用布爾標誌在功能切換行爲。

您可以通過範圍檢查作爲策略,而不是模板參數,樣式爲policy-based design。這些策略不需要通過繼承來關聯,因爲除了從使用它們派生的模板參數之外,沒有對模板參數類型的約束。只要它提供了必要的界面,你就可以放入任何你喜歡的類型。這樣,我可以定義兩個沒有任何(繼承)關係的獨立類,並使用它們作爲模板參數。缺點是Dummy<float, X>Dummy<float, Y>是兩種不同的,不相關的類型,你不能將第一類型的實例分配給第二類型的實例,而不定義模板賦值運算符。

#include <stdexcept> 

template<typename T> 
struct NoMaxCheck 
{ 
    NoMaxCheck(T) {} 

    void check(T) const noexcept {} 
}; 

template<typename T> 
struct ThresholdChecker 
{ 
    ThresholdChecker(T value) : mMax(value) {} 

    void check(T value) const 
    { 
      if (value < 0 || mMax < value) { 
       throw std::out_of_range(""); 
      } 
    } 
private: 
    T mMax; 
}; 

template<typename T, typename CheckPolicy> 
class Dummy 
{ 
private: 
    T mVal; 
    CheckPolicy mThresholdChecker; 

public: 
    explicit Dummy(T x, T max_x) noexcept : mVal(x), mThresholdChecker(max_x) {}; 

    T add(T x) const noexcept(noexcept(mThresholdChecker.check(x))) 
    { 
    T ret_val = x + mVal(); 
    mThresholdChecker.check(ret_val); 
    return ret_val; 
    } 
}; 

template<typename T, template<typename> typename CheckPolicy> 
class DummyEmptyBaseClasss: private CheckPolicy<T> 
{ 
private: 
    T mVal; 

public: 
    explicit DummyEmptyBaseClasss(T x, T max_x) noexcept: 
     CheckPolicy<T>(max_x), 
     mVal(x) {}; 

    T add(T x) const noexcept(noexcept(check(x))) 
    { 
    T ret_val = x + mVal(); 
    check(ret_val); 
    return ret_val; 
    } 
}; 

int foo() 
{ 
    Dummy<float,NoMaxCheck<float>> unchecked(0, 1000); 
    Dummy<float,ThresholdChecker<float>> checked(0, 1000); 
    static_assert(sizeof(DummyEmptyBaseClasss<float, NoMaxCheck>) == sizeof(float), "empty base class optimization"); 
} 

您可以使用template-template參數更多地簡化它以擺脫冗餘的float參數。 DummyEmptyBaseClass顯示了這一點。

+0

代碼不會編譯。我不明白'NoMaxCheck'結構。它應該來自'ThresholdChecker'嗎?如果'CheckPolicy'是'NoMaxCheck',那麼'Dummy'對象的實例是否會大於所需的大小,或者是否會導致空基優化? – xnth

+0

@xnth如果修正了示例代碼並擴展瞭解釋。如果將'Dummy'更改爲從'CheckPolicy'私有繼承,則空基類優化應該消除空間開銷。 – Jens

+0

@xnth我已經添加了一個空基類優化的例子。 – Jens