在C++泛型編程中如何只特化類的某個成員函數


我們知道在C++模板編程中如果我們特化或是偏特化某個模板類, 我們需要重寫整個模板類中的所有函數, 但是這些代碼通常是非常相似的, 甚至在某些情況下可能只有一兩個函數會不一樣,其他函數都是一樣的。在這種情況下,同時存在多份相同的代碼,對我們維護這些代碼是非常不利的, 我們最好只需要特化其中不一樣的那個函數。

比如下面這個模板類:

 

template<typename T, unsigned B>
struct Base
{
     // other function
    
// ....
     void Func(){ cout <<  " primary function " << endl; }
};
void test1()
{
    Base< int1> a;
    a.Func();
    Base< int16> b;
    b.Func();
}
int main()
{
     test1();
}

 

 

只有當B等於16時, Func這個函數需要特化, 但是其他函數無論什么情況下都是一樣的。

下面是我們的一些可能解決方案:

方法1:

 

template<typename T>
struct Base<T,  16>
{
     // other function
    
// ....
     void Func(){ cout <<  " specialization function " << endl; }
};

 

點評:通過偏特化實現,需要重寫所有的類成員方法。

 

方法2:

 

template<typename T, unsigned B>
struct Base
{
     // other function
    
// ....
     void Func()
    {
         if(B ==  16)
        {
            cout <<  " primary function " << endl;
        }
         else
        {
            cout <<  " specialization function " << endl;
        }
    }
};

 

點評: 通過運行時判斷, 容易理解,但是相對低效。

 

方法3:

 

template<typename T, unsigned B>
struct Base
{
     // other function
    
// ....
     void Func()
    {
#if B!=16
            cout <<  " primary function " << endl;
#else
            cout <<  " specialization function " << endl;
#endif
    }
};

 

點評: 試圖通過預編譯來實現,但是這個方法是錯誤的。C++模板編譯包括預編譯,語法檢查,模板實例化等階段,在預編譯階段模板參數都還沒有實例化呢。

 

方法4:

 

template<typename T, unsigned B>
struct Base
{
     // other function
    
// ....
    template<unsigned S>
     struct FuncObj
    {
         void  operator()()
        {
            cout<< " primary function "<<endl;
        }
    };
    template<>
     struct FuncObj< 16>
    {
         void  operator()()
        {
            cout<< " specialization function "<<endl;
        }
    };
    FuncObj<B> Func;
};

 

點評: 通過成員類以防函數的形式特化, 增加了類成員變量。

 

方法5:

 

template<typename T, unsigned B>
struct Base
{
     // other function
    
// ....
    template<unsigned N>
     void FuncImpl()
    {
        cout<< " primary function "<<endl;
    }
    template<>
     void FuncImpl< 16>()
    {
        cout<< " specialization function "<<endl;
    }
     void Func()
    {
        FuncImpl<B>();
    }
};

 

點評:通過類成員模板函數特化來實現。

 

方法6:

 

template<typename T, unsigned B>
struct Base
{
     // other function
    
// ....
    template<unsigned N> 
     class Int2Type
    {
         enum { value = N };
    };
    template<unsigned V>
     void FuncImpl( const Int2Type<V>)
    {
        cout<< " primary function "<<endl;
    }
     void FuncImpl( const Int2Type< 16>)
    {
        cout<< " specialization function "<<endl;
    }
     void Func()
    {
        FuncImpl(Int2Type<B>());
    }
};

 

點評: 通過將int根據值的不同轉成不同的類型,然后通過函數重載實現。

 

方法7:

 

namespace
{
    template < bool,typename T,typename>  struct conditional { typedef T type; };
    template <typename T,typename U>  struct conditional< false,T,U> {typedef U type; };
}
template< class T, unsigned B>
struct Base
{
     // other function
    
// ....

     void Func ()
    {
        typedef typename ::conditional<B!= 16,primary_t,spec_t>::type type;
        Func_impl(type());
    }
private:
     struct primary_t { };
     struct spec_t    { };
     void Func_impl (primary_t) { std::cout <<  " primary function " << std::endl; }
     void Func_impl (spec_t   ) { std::cout <<  " specialization function " << std::endl; }
};

 

點評: 和方法6類似,通過函數重載實現

 

方法8:

 namespace

 {
    template < bool,typename T =  voidstruct enable_if { typedef T type; };
    template <typename T>  struct enable_if< true,T> {};
}
template< class T, unsigned B>
struct Base
{
     // other function
    
// ....

    template <unsigned N>
    typename ::enable_if< 16!=N>::type
        FuncImpl () { std::cout <<  " primary function " << std::endl; }
    template <unsigned N>
    typename ::enable_if< 16==N>::type
        FuncImpl () { std::cout <<  " specialization function " << std::endl; }
     void Func() {
        FuncImpl<B>();
    }
};

點評:通過enable_if, 利用SFINAE實現。

 

我們可以看到根據編譯時模板參數int值的不同,我們重寫模板類的某個成員函數的方法是多種多樣的。針對上面這種情況,個人其實最推薦方法2,我們沒必要把簡單的問題復雜化。

 

下面我們考慮另外一個需求, 當模板類的某個參數是某種類型時, 我們要求特化其中的一個成員函數:

 

template<typename T1, typename T2>
struct Base
{
     // other function
    
// ....
     void Func(){ cout <<  " primary function " << endl; }
};
void test2()
{
    Base< intint> a;
    a.Func();
    Base< intstring> b;
    b.Func();
}
int main()
{
    test2();
}

 

要求上面的模板類如果T2 是string類型, 我們要求對Func特殊重寫,其他的成員函數無論什么情況實現都是一樣的。

有了上面的那個例子的實現經驗, 對這個問題我們解決就方便多了。

 

方法1:

 

template<typename T1, typename T2>
struct Base
{
     // other function
    
// ....
     void Func()
    {
         if(typeid(std:: string) == typeid(T2))
        {
            cout<< " specialization function "<<endl;
        }
         else
        {
            cout <<  " primary function " << endl; 
        }
    }
};

 

點評:通過運行時類型識別(RTTI)實現,需要打開相關編譯選項,並且低效。

 

方法2:

 

template<typename T1, typename T2>
struct Base
{
     // other function
    
// ....
    template<typename T>
     void FuncImpl()
    {
        cout <<  " primary function " << endl; 
    }
    template<>
     void FuncImpl< string>()
    {
        cout <<  " specialization function " << endl; 
    }
     void Func()
    {
        FuncImpl<T2>();
    }
};

 

點評:通過成員函數特化實現

 

方法3:

 

template<typename T1, typename T2>
struct Base
{
     // other function
    
// ....
    template<typename T> 
     class Type2Type
    {
        typedef T type;
    };
    template<typename T>
     void FunImpl( const Type2Type<T>)
    {
        cout <<  " primary function " << endl; 
    }
    template<typename T>
     void FunImpl( const Type2Type< string>)
    {
        cout <<  " specialization function " << endl; 
    }
     void Func()
    {
        FunImpl<T2>(Type2Type<T2>());
    }
};

 

點評: 通過函數重載實現

 

方法4:

 

template<typename T>
struct IsString
{
     enum { value =  false };
};
template<>
struct IsString< string>
{
     enum { value =  true };
};
template<typename T1, typename T2>
struct Base
{
     // other function
    
// ....
     void Func()
    { 
         if(IsString<T2>::value)
        {
            cout <<  " specialization function " << endl; 
        }
         else
        {
            cout <<  " primary function " << endl; 
        }
    }
};

 

點評: 通過編譯時類型判斷實現。

 

方法5:

 

template<typename T3,  typename T4>
struct must_be_same_type
{
     enum { ret =  0 };
};
template<>
struct must_be_same_type< stringstring>
{
     enum { ret =  1 };
};
template < typename T1,typename T2 >
class Base{
public:
     // other function
    
// ....
     void Func(){
         if(must_be_same_type<T2,  string>::ret)
        {
            cout <<  " specialization function " << endl; 
        }
         else
        {
            cout <<  " primary function " << endl; 
        }
    }
};

 

點評: 和方法4類似, 是不過實現方式不一樣。

 

最后,探討下我自己遇到的問題, 我們在寫一個事件委托(delegate)類,大概如下:

 

template<typename return_type, typename first_type, typename second_type>
class CEvent 
{
public:
     // other function
    
// ....
    return_type  operator()(first_type p1, second_type p2)
    {
        return_type ret = return_type();
         // ...
        
// ret = invoker(p1, p2);
         return ret;
    }
};
void test3()
{
    CEvent< intintint> e1;
    e1( 12);
    CEvent< voidintint> e2;
    e2( 12);
}
int main()
{
    test3();
}

 

我們可以看到,當return_type是void時, 因為沒有返回值,上面的代碼會編譯失敗,因此我們只能偏特化這種情況:

 

template<typename first_type, typename second_type>
class CEvent< void, first_type, second_type>
{
public:
     // other function
    
// ....
     void  operator()(first_type p1, second_type p2)
    {
         // ...
        
// invoker(p1, p2);
         return;
    }
};

 

但是,我們會發現只有這個operator()函數是需要根據return_type特殊對待的,其他函數永遠都是一樣的。

我們現在的問題就是如何只特化這個函數。

 

首先我們會想到如下的實現方法:

 

template<typename T>
struct IsVoid
{
     enum { value =  false };
};
template<>
struct IsVoid< void>
{
     enum { value =  true };
};
template<typename return_type, typename first_type, typename second_type>
class CEvent 
{
public:
    other function
    ....
    return_type  operator()(first_type p1, second_type p2)
    {
         if(IsVoid<return_type>::value)
        {
            cout <<  " return type is void " << endl;
             // ...
            
// invoker(p1, p2);
        }
         else
        {
            cout <<  " return type is not void " << endl;
            return_type ret = return_type();
             // ...
            
// ret = invoker(p1, p2);
             return ret;
        }
    }
};

 

但是我們很快會發現這種情況下if語句被編譯進去了, 所以return_type是void的情況下還是會編譯失敗。

我們要解決的問題就是如何把這個if語句變成函數重載,於是我們想到如下實現:

 

template<typename T>
struct IsVoid
{
     enum { value =  false };
};
template<>
struct IsVoid< void>
{
     enum { value =  true };
};
template< int v>
class Int2Type
{
     enum {value = v };
};
template<typename return_type, typename first_type, typename second_type>
class CEvent 
{
public:
     // other function
    
// ....
    return_type InvokerImpl(first_type p1, second_type p2, Int2Type< true>)
    {
        cout <<  " return type is void " << endl;
         // ...
        
// invoker(p1, p2);
    }
    return_type InvokerImpl(first_type p1, second_type p2, Int2Type< false>)
    {
        cout <<  " return type is not void " << endl;
        return_type ret = return_type();
         // ...
        
// ret = invoker(p1, p2);
         return ret;
    }
    return_type  operator()(first_type p1, second_type p2)
    {
         return InvokerImpl(p1, p2, Int2Type<IsVoid<return_type>::value>());
    }
};

 

上面的實現首先通過編譯時類型識別,然后再把識別后相應的bool值轉成不同類型, 最后再利用不同類型函數重載實現。

 

最后總結下,我們可以看到,從編譯時到運行時,從面向對象到普通泛型編程再到模板元編程,C++復雜得讓人無語, 也強大得讓人無語, 而且C++語言本身是在不斷發展的(C++11), 同一問題在C++中往往有多種解決方案,這些解決方案有的簡單,有的復雜,有的高效, 也有的低效, 而我們的目標就是利用C++這把利器尋找簡單而高效的解決方案。

 

注:本人初學C++ templates編程,如有錯誤,歡迎指正。

      參考資料:http://bbs.csdn.net/topics/390116038

                    http://bbs.csdn.net/topics/270041821


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM