簡體   English   中英

在運行時調用帶有未知類型的模板化函數

[英]Calling templated function with type unknown until runtime

我有一個這個函數從未格式化的fortran文件中讀取1d數組:

template <typename T>
void Read1DArray(T* arr)
{
    unsigned pre, post;
    file.read((char*)&pre, PREPOST_DATA);

    for(unsigned n = 0; n < (pre/sizeof(T)); n++)
        file.read((char*)&arr[n], sizeof(T));

    file.read((char*)&post, PREPOST_DATA);
    if(pre!=post)
        std::cout << "Failed read fortran 1d array."<< std::endl;
}

我這樣稱呼:

float* new_array = new float[sizeof_fortran_array];
Read1DArray(new_array);

假設Read1DArray是一個類的一部分,它包含一個名為'file'的ifstream,而sizeof_fortran_array已經知道了。 (對於那些不太熟悉fortran無格式寫入的人來說,'pre'數據表示數組以字節為單位的時間長度,'post'數據是相同的)

我的問題是我有一個場景,我可能想用float *或double *來調用這個函數,但是直到運行時才知道這個。

目前我所做的只是有一個標志,用於讀取數據類型,在讀取數組時,我復制了類似這樣的代碼,其中datatype是在運行時設置的字符串:

if(datatype=="float")
    Read1DArray(my_float_ptr);
else 
    Read1DArray(my_double_ptr);

有人可以建議一種重寫方法,以便我不必復制兩種類型的函數調用嗎? 這是用它來調用它的唯一兩種類型,但是我必須把它稱為公平幾次,我寧願不要在整個地方都有這種重復。

謝謝

編輯:響應建議將其包裝在call_any_of函數中,這是不夠的,因為有時我做這樣的事情:

if(datatype=="float")
{
    Read1DArray(my_float_ptr);
    Do_stuff(my_float_ptr);
}
else 
{
    Read1DArray(my_double_ptr);
    Do_stuff(my_double_ptr);
}

// More stuff happening in between  

if(datatype=="float")
{
    Read1DArray(my_float_ptr);
    Do_different_stuff(my_float_ptr);
}
else 
{
    Read1DArray(my_double_ptr);
    Do_different_stuff(my_double_ptr);
}

如果您考慮標題,您會發現存在一個矛盾,即模板實例化是在編譯時執行的,但您希望根據僅在運行時可用的信息進行調度。 在運行時,您無法實例化模板,因此這是不可能的。

您采用的方法實際上是正確的方法:在編譯時實例化兩個選項,並確定在運行時使用哪個選項和可用信息。 話雖如此,你可能想要考慮你的設計。

我想,不僅基於運行時值,讀取和處理也會有所不同,因此您可能希望將每個類型的(可能是模板)函數中的所有處理綁定在一起,並將if進一步向上移動調用層次結構。


另一種避免必須根據類型分配到模板的不同實例的方法是松開一些類型的安全性並實現一個單獨的函數,它將void*帶到已分配的內存中,並且一個size參數帶有類型的大小。數組。 請注意,這將更加脆弱,並且它無法解決在讀取數據后必須對不同陣列執行操作的整體問題,因此我不建議遵循此路徑。

因為您不知道在運行時要使用哪個代碼路徑,所以您需要設置某種動態分派。 您當前的解決方案使用if-else執行此操作,必須在使用它的任何位置復制和粘貼它。

改進是生成執行調度的函數。 實現此目的的一種方法是將每個代碼路徑包裝在成員函數模板中,並使用指向該成員函數模板的特化的成員函數指針數組。 [注意:這在功能上等同於使用虛函數的動態調度。]

class MyClass
{
public:

    template <typename T>
    T* AllocateAndRead1DArray(int sizeof_fortran_array)
    {
        T* ptr = new T[sizeof_fortran_array];
        Read1DArray(ptr);
        return ptr;
    }

    template <typename T>
    void Read1DArrayAndDoStuff(int sizeof_fortran_array)
    {
        Do_stuff(AllocateAndRead1DArray<T>(sizeof_fortran_array));
    }

    template <typename T>
    void Read1DArrayAndDoOtherStuff(int sizeof_fortran_array)
    {
        Do_different_stuff(AllocateAndRead1DArray<T>(sizeof_fortran_array));
    }

    // map a datatype to a member function that takes an integer parameter
    typedef std::pair<std::string, void(MyClass::*)(int)> Action;

    static const int DATATYPE_COUNT = 2;

    // find the action to perform for the given datatype
    void Dispatch(const Action* actions, const std::string& datatype, int size)
    {
        for(const Action* i = actions; i != actions + DATATYPE_COUNT; ++i)
        {
            if((*i).first == datatype)
            {
                // perform the action for the given size
                return (this->*(*i).second)(size);
            }
        }
    }
};

// map each datatype to an instantiation of Read1DArrayAndDoStuff
MyClass::Action ReadArrayAndDoStuffMap[MyClass::DATATYPE_COUNT] = {
    MyClass::Action("float", &MyClass::Read1DArrayAndDoStuff<float>),
    MyClass::Action("double", &MyClass::Read1DArrayAndDoStuff<double>),
};

// map each datatype to an instantiation of Read1DArrayAndDoOtherStuff
MyClass::Action ReadArrayAndDoOtherStuffMap[MyClass::DATATYPE_COUNT] = {
    MyClass::Action("float", &MyClass::Read1DArrayAndDoOtherStuff<float>),
    MyClass::Action("double", &MyClass::Read1DArrayAndDoOtherStuff<double>),
};


int main()
{
    MyClass object;
    // call MyClass::Read1DArrayAndDoStuff<float>(33)
    object.Dispatch(ReadArrayAndDoStuffMap, "float", 33);
    // call MyClass::Read1DArrayAndDoOtherStuff<double>(542)
    object.Dispatch(ReadArrayAndDoOtherStuffMap, "double", 542);
}

如果性能很重要,並且在編譯時可以知道可能的類型集,那么可以執行一些進一步的優化:

  • 將字符串更改為表示所有可能的數據類型的枚舉,並通過該枚舉索引操作數組。

  • 給出Dispatch函數模板參數,允許它生成一個switch語句來調用相應的函數。

例如,編譯器可以內聯這一點來生成(通常)比上述示例和問題中的原始if-else版本更優化的代碼。

class MyClass
{
public:

    enum DataType
    {
        DATATYPE_FLOAT,
        DATATYPE_DOUBLE,
        DATATYPE_COUNT
    };

    static MyClass::DataType getDataType(const std::string& datatype)
    {
        if(datatype == "float")
        {
            return MyClass::DATATYPE_FLOAT;
        }
        return MyClass::DATATYPE_DOUBLE;
    }

    // find the action to perform for the given datatype
    template<typename Actions>
    void Dispatch(const std::string& datatype, int size)
    {
        switch(getDataType(datatype))
        {
        case DATATYPE_FLOAT: return Actions::FloatAction::apply(*this, size);
        case DATATYPE_DOUBLE: return Actions::DoubleAction::apply(*this, size);
        }
    }
};

template<void(MyClass::*member)(int)>
struct Action
{
    static void apply(MyClass& object, int size)
    {
        (object.*member)(size);
    }
};

struct ReadArrayAndDoStuff
{
    typedef Action<&MyClass::Read1DArrayAndDoStuff<float>> FloatAction;
    typedef Action<&MyClass::Read1DArrayAndDoStuff<double>> DoubleAction;
};

struct ReadArrayAndDoOtherStuff
{
    typedef Action<&MyClass::Read1DArrayAndDoOtherStuff<float>> FloatAction;
    typedef Action<&MyClass::Read1DArrayAndDoOtherStuff<double>> DoubleAction;
};


int main()
{
    MyClass object;
    // call MyClass::Read1DArrayAndDoStuff<float>(33)
    object.Dispatch<ReadArrayAndDoStuff>("float", 33);
    // call MyClass::Read1DArrayAndDoOtherStuff<double>(542)
    object.Dispatch<ReadArrayAndDoOtherStuff>("double", 542);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM