2016-10-03 35 views
1

假設我有一個模板類這樣:虛函數和泛型編程

template <class T> 
class Foo 
{ 
public: 

    explicit Foo(const T& value) 
     : m_Value(value) 
    { 
    } 

    bool Bar(const T& value) 
    { 
     return m_Value == value; 
    } 

private: 

    T m_Value; 
}; 

而且,我們說,我有一些其他的用戶類型,如:

class A 
{ 
}; 

那麼這個代碼是完全有效的,即使類一個沒有定義相等操作符:

int main(int argc, char* argv[]) 
{ 
    A a; 
    Foo<A> foo(a); 

    return 0; 
} 

但是,如果我做美孚::酒吧()虛:

virtual bool Bar(const T& value) 
    { 
     return m_Value == value; 
    } 

代碼不再編譯:

錯誤C2676:二進制 '==': 'A' 不定義此運算符或轉換爲預定義運營商可接受的類型

我完全理解爲什麼這是一個問題。糾正我,如果我錯了,但我的理解是,因爲該函數是虛擬的,編譯器必須編譯該函數(即使它從來沒有被調用),以便它可以在Foo的v表中引用它。

我想知道是否有解決此問題的方法。我想要一個模板類來處理只能實現部分接口的泛型類型。只要沒有使用缺失的位,代碼應該編譯得很好。這與很多STD容器已經工作的方式類似,但它們不使用虛擬功能。

我該怎麼做?有沒有一個優雅的解決方案呢?

+1

如果一個函數是虛擬的,它必須被定義和編譯。這是根本。 –

+3

類模板的非虛擬成員函數本身是函數模板,僅在第一次使用時才實例化。相比之下,虛擬成員函數總是作爲類模板特化實例的一部分而被實例化。 –

+1

當一個類模板被實例化時,其成員函數的*聲明*被編譯,而不是它們的主體。而虛擬非純成員函數總是被odr使用,因此完全編譯爲類模板實例化的一部分。 – 0x499602D2

回答

1

正如上述Kerrek SB所解釋的,當模板被實例化時,虛擬函數總是被立即執行。所以當虛擬方法未被使用時,沒有辦法讓你的程序編譯得很好如果它被使用並且你想要包裝的類不提供它自己的operator==,它將無法編譯。

但是,您可以使程序在運行時崩潰(使用assert/terminate)或拋出異常。

免責聲明:我不認爲這是一個好主意,可以做你正在嘗試做的事情,因爲它允許創建不支持他們聲稱提供的接口的類。使用下列風險自擔風險。

這裏要介紹的方法是爲每個要提供的方法使用自定義類型特徵,即使被包裝的類本身沒有實現它。在你的情況上面這只是operator==,以及相應的代碼看起來是這樣的:

namespace traits { 
template <typename T> 
using operator_eq_t = decltype(std::declval<T>() == std::declval<T>()); 

template <typename, typename = void> 
struct has_operator_eq : std::false_type {}; 

// check that operator== is defined and returns the correct type `bool`. 
template <typename T> 
struct has_operator_eq<T, std::void_t<operator_eq_t<T>>> 
    : std::is_same<operator_eq_t<T>, bool> {}; 
} // namespace traits 

如果您沒有訪問C++ 1Z但你可以使自己的std::void_t版本,其他的一切都是有效的C++ 14:

template <typename...> 
using void_t = void 

由於在地方,你可以用標籤分發創建包裝類模板:

template <typename T> 
class Foo : public IFoo<T> { 
public: 
    explicit Foo(T const& value) 
     : m_Value(value) { 
    } 

    bool Bar(T const& value) override { 
     return BarImpl(value, traits::has_operator_eq<T>{}); 
    } 

private: 
    T m_Value; 

    bool BarImpl(T const& value, std::false_type) { 
     // some sensible default, in this case you might 
     // consider just to return false 
     assert(!"Called `Bar` on class that does not implement `operator==`."); 
     throw std::logic_error("Called `Bar` on class that does not implement `operator==`."); 
    } 

    bool BarImpl(T const& value, std::true_type) { 
     return value == m_Value; 
    } 
}; 

的工作前充足可以發現here

+0

這非常有趣。但我認爲改變課堂設計是一種更優雅的解決方案,正如我們在評論中討論的那樣。無論如何,這將在未來派上用場。 – Zeenobit