程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 【C++模版之旅】項目中一次活用C++模板(traits)的經歷,活用traits

【C++模版之旅】項目中一次活用C++模板(traits)的經歷,活用traits

編輯:C++入門知識

【C++模版之旅】項目中一次活用C++模板(traits)的經歷,活用traits


問題與需求:

請讀者先看這篇文章,【C++模版之旅】項目中一次活用C++模板(traits)的經歷。 對於此篇文章提出的問題,我給出一個新的思路。

talking is cheap,show me the code.

代碼:

class ExportData
{
    union
    {
        string * sp;
        long*  lp;
        double* dp;
        void* vp;
 
    };
    enum my_type {SP,LP,DP} types;
    static unordered_map<type_index,my_type> typeMap;
public:
 
    template <typename T> ExportData(T t)
    {
        if(typeMap.find(typeid(t))==typeMap.end())
            assert(false);
        vp=new T(t);
        types= typeMap[typeid(T)];
    }
    template <typename T> void setData(T t)
    {
        if(typeMap.find(typeid(t))==typeMap.end())
            assert(false);
        switch(types)
        {
        case SP:
            delete sp;
            break;
        case DP:
            delete dp;
            break;
        case LP:
            delete lp;
            break;
        }
        vp=new T(t);
        types=typeMap[typeid(T)];
 
    }
    template <typename T> void getData(T& t)
    {
        if(typeMap[typeid(T)]!=types) assert(false);
        t=*(static_cast<T*>(vp));
    }
 
};
 
unordered_map<type_index,ExportData::my_type> ExportData::typeMap
{
    {typeid(string),ExportData::my_type::SP},
    {typeid(long),ExportData::my_type::LP},
    {typeid(double),ExportData::my_type::DP},
};

 

重復一下,四點需求:

 

1. ExportData需要僅支持整型(long),浮點型(double),字符串(string)以及二進制(void*, size)4種類型的操作。(我並沒有考慮二進制) 2. ExportData需要考慮結構的尺寸,盡量減少空間冗余(我使用聯合體,保存各種類型數據的指針) 3. 即使對以上4種不同數據類型進行操作,還是希望在從ExportData中Get或Set真實數據時,使用的方法能統一(方法顯然是統一的,因為使用的是模版) 4. 當調用者嘗試使用了以上4種類型以外的數據類型時,能通過返回錯誤讓調用方知道類型不匹配(為了方便演示,試圖使用其他類型都會導致斷言失敗,終止運行)

如果你也討厭代碼中存在swtich,可以再次使用表驅動法。代碼如下所示:
class DeleteLong
{
public:
    void operator()(void *p)
    {
        delete static_cast<long*>(p);
    }
};
class DeleteString
{
public:
    void operator()(void *p)
    {
        delete static_cast<string*>(p);
    }
};
class DeleteDouble
{
public:
    void operator()(void *p)
    {
        delete static_cast<double*>(p);
    }
};
 
class ExportData
{
    union
    {
        string * sp;
        long*  lp;
        double* dp;
        void* vp;
 
    };
    enum my_type {SP,LP,DP} types;//change it to object.
    static unordered_map<type_index,my_type> typeMap;
    static vector<function<void(void*)>> deleters;
public:
 
    template <typename T> ExportData(T t)
    {
       static_assert(is_same<T,string>::value||is_same<T,double>::value||is_same<T,long>::value,"never support!");//感謝崔方方
// if(typeMap.find(typeid(t))==typeMap.end()) // assert(false); vp=new T(t); types= typeMap[typeid(T)]; } template <typename T> void setData(T t) { if(typeMap.find(typeid(t))==typeMap.end()) assert(false); (deleters[types])(vp); vp=new T(t); types=typeMap[typeid(T)]; } template <typename T> void getData(T& t) { if(typeMap[typeid(T)]!=types) assert(false); t=*(static_cast<T*>(vp)); }
//這裡可以改成重載,void getData(long& t){...} void getData(sting& t){....} void getData(double& t){...}調用其他類型則編譯錯誤 }; unordered_map<type_index,ExportData::my_type> ExportData::typeMap { {typeid(string),ExportData::my_type::SP}, {typeid(long),ExportData::my_type::LP}, {typeid(double),ExportData::my_type::DP}, }; vector<function<void(void*)>> ExportData::deleters {DeleteString(),DeleteLong(),DeleteDouble(),};

 這裡是測試代碼:

int main()
{
   
    long i=5;
    long j=0;
    string s="Hello";
    string ss;
    ExportData p(i);
    p.setData(++i);
    p.getData(j);
    p.setData(s);
    p.getData(ss);
    cout<<j<<endl;
    cout<<ss<<endl;
    return 0;
}

 

 

這是一個精簡版,使用重載:

class ExportData
{
    union
    {
        string * sp;
        long*  lp;
        double* dp;
    };
public:
    ExportData(long t)
    {
        lp=new long(t);
    }
    ExportData(double t)
    {
        dp=new double(t);
    }
    ExportData(string t)
    {
        sp=new string(t);
    }
    void    setData(long t)
    {
        *lp=t;
    }
    void   setData(double t)
    {
        *dp=t;
    }
    void setData(string t)
    {
        *sp=t;
    }
    void getData(long& t)
    {
        t=*lp;
    }
    void getData(double& t)
    {
        t=*dp;
    }
    void getData(string& t)
    {
        t=*sp;
    }
    //1.析構函數需要解決內存洩露問題 2.如當前指針指向double,setData函數傳入string,會發生內存錯誤。
};

這個版本存在兩個嚴重的問題,1.析構函數需要解決內存洩露問題 2.如當前指針指向double,setData函數傳入string,會發生內存錯誤。 

我覺得第二個錯誤,沒辦法在編譯期阻止用戶編譯,因為在setData的時候,無法在編譯期知道哪個指針有效。

這段代碼額外的優點:

1.代碼更加的短小緊湊。(代碼量減少)
2.ExportData對象使用起來更容易。
3.ExportData對象僅有兩個數據,一個是指針聯合體,一個是枚舉值。(性能更優)
4.我在作者提出4點需求基礎上添加了一個額外功能,ExportData可以動態的改變持有數據的類型。(功能更強)
5. 類中所有方法如果不使用模版而是使用重載,雖然會導致代碼量大增,但好處是我們可以在編譯期提示用戶ExportData不支持某些類型,也能提高一點運行速度。要不要這麼做,可具體問題具體分析。
6.因為使用模版,所以可擴展性強,當增加支持類型時,只需改動少量代碼。(可擴展性更好)

最後,這段代碼是示例代碼,也許經不起推敲,那麼引用原文作者的話,“我想肯定還有更好的解決方法,比如可以嘗試在編譯時就提示類型不支持而不是在運行時通過返回錯誤來提示。如果有更好的解決方案,歡迎一起討論。”,ME TOO。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved