1

我有一个从未格式化的 fortran 文件中读取一维数组的函数:

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 是一个类的一部分,该类包含一个名为“文件”的 ifstream,并且 sizeof_fortran_array 是已知的。(对于那些不太熟悉 fortran 无格式写入的人,“pre”数据表示数组的长度(以字节为单位),“post”数据是相同的)

我的问题是我有一个场景,我可能想用 float* 或 double* 调用这个函数,但这要到运行时才能知道。

目前我所做的只是有一个要读取的数据类型的标志,并且在读取数组时我复制了类似这样的代码,其中数据类型是在运行时设置的字符串:

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);
}
4

2 回答 2

3

如果您考虑一下标题,您会意识到存在一个矛盾,即模板实例化是在编译时执行的,但您希望根据仅在运行时可用的信息进行调度。在运行时,您无法实例化模板,因此这是不可能的。

您采用的方法实际上是正确的:在编译时实例化这两个选项,并根据可用信息决定在运行时使用哪一个。话虽如此,您可能想考虑一下您的设计。

我想,根据运行时值,不仅读取而且处理也会有所不同,因此您可能希望将所有处理绑定到每个类型的(可能是模板)函数中,并if进一步向上移动调用层次结构。


另一种避免必须基于类型分派到模板的不同实例的方法是放松一些类型安全性并实现一个函数,该函数接受void*分配的内存和size数组中类型大小的参数。请注意,这将更加脆弱,并且它并不能解决读取数据后必须对不同数组进行操作的整体问题,因此我不建议遵循此路径。

于 2013-08-16T13:45:33.720 回答
1

因为直到运行时您才知道要采用哪个代码路径,所以您需要设置某种动态调度。您当前的解决方案使用 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);
}
于 2013-08-16T15:18:54.170 回答