C++模板编程设计问题 - 根据输入文件返回不同的类型

C++ template programming design question - return different type according to input file

本文关键字:返回 文件 输入 类型 编程 问题 C++      更新时间:2023-10-16

多媒体文件可能包含不同日期类型的数据,例如uint8_t,int16_t,浮点数等。 以下三个示例显示了第一个字节表示数据类型的文件内容:

1st File: 0,<uint8 data><uint8 data><uint8 data>...
2nd File: 1,<int16 data><int16 data><int16 data>...
3rd File: 2,<float data><float data><float data>...

FileReader 类读取文件并返回不同类型的 DataStream。我使用DataStreamBase,以便客户端持有指针。

////////////////////////////////////////////////
class FileReader {
DataStreamBase* readFile(string filename) {
switch (first_byte) {   // the first byte in a file.
case 0:
return new DataStream<uint8_t>();
case 1:
return new DataStream<int16_t>();
case 2:
return new DataStream<float>();
case 3:
// ... there are many more "case <n>:"
}
return nullptr;
}
};
////////////////////////////////////////////////////
class DataStreamBase {
};
///////////////////////////////////////////////////
template<class T>
class DataStream : public DataStreamBase {
private:
T* data_;
};
///////////////////////////////////////////////////
// client 
int main() {
FileReader reader;
DataStreamBase* stream = reader.readFile("some file name");
// Question: how to get a pointer to the data which may be uint8_t, int16_t, or float. Below approach is ugly.
//uint8_t* data = stream->getDataUint8();
//int16_t* data = stream->getDataInt16();
//float* data = stream->getDataFloat();
//...
}

客户端不知道输入文件是包含uint8_t、int16_t还是浮点数据,直到运行时。

问题: 客户端如何获取指向uint8_t、int16_t、浮点数、...哪些可以传递给第三方库?这种设计是解决此类问题的正确方法吗?谢谢。

最后,我选择了双重调度的解决方案。这不是原始问题的解决方案,但它避免了编写重复代码的问题。

使用双重调度的原因是客户端需要读取两个文件并通过传递两个数据流来调用第三方程序。第三方程序的接口为:

template<class T>
int calculate(const T* input_1, const T* input_2, vector<float>& result);

没有双重调度,客户需要像这样调用第三方程序:

DataStreamBase * ds1;
DataStreamBase * ds2;
if (auto cast_ds1 = dynamic_cast<DataStream<uint8_t>*>(ds1)) {
if (auto cast_ds2 = dynamic_cast<DataStream<uint8_t>*>(ds2)) {
calculate(cast_ds1->getData(), cast_ds2->getData(), result);
}
else if (auto cast_ds2 = dynamic_cast<DataStream<int16_t>*>(ds2)) {
calculate(cast_ds1->getData(), cast_ds2->getData(), result);
}
...
}
else if (auto cast_ds1 = dynamic_cast<DataStream<int16_t>*>(ds1)) {
if (auto cast_ds2 = dynamic_cast<DataStream<uint8_t>*>(ds2)) {
calculate(cast_ds1->getData(), cast_ds2->getData(), result);
}
else if ...
}
...

请注意,模板类 DataStream 定义方法 "const T* getData((",而基类 DataStreamBase 无法定义 getData(( 方法,因为它不知道返回类型是什么。

///////////////////////////////////////////////////
template<class T>
class DataStream : public DataStreamBase {
public:
const T* getData() const { return data_; }
private:
T* data_;
};

使用《现代C++设计》一书中的TypeList和StaticDispatch,我能够让编译器使用模板技术为我生成那些重复的代码。这个草图的想法:

客户端(主.cpp(:

using MyTypeList = TYPELIST_6(DataStreamBase<uint8_t>, DataStream<int16_t>, DataStream<int32_t>, DataStream<int64_t>, DataStream<float>, AudioStream<double>);
using DataStreamDispatcher = DoubleDispatcher<Calculator, int, std::vector<float>, DataStreamBase, DataStreamTypeList>;
DataStreamDispatcher::DispatchT1(*ds1, *ds2, calculator, calculate));

计算器.cpp:

class Calculator {
public:
// this api is 3rd party library.
template<class T1, class T2>
int calculate(const T1*, int, const T2*, int, std::vector<float>&);
template<class T1, class T2>
int applyDoubleDispatch(const T1& s1, const T2& s2, std::vector<float>& res)
{
return calculate(s1.getData(), s2.getData(), res);
}
...
};

双重调度.cpp:

template<class Executor, class ResType, class ParamType, class T1, class TypeList1, class T2 = T1, class TypeList2 = TypeList1>
class DoubleDispatcher {
public:
static ResType DispatchT1(T1& obj1, T2& obj2, Executor exec, ParamType& param) {
using HeadType = typename TypeList1::HeadType;
using TailType = typename TypeList1::TailType;
if (HeadType* t1 = dynamic_cast<HeadType*>(&obj1)) {
return DoubleDispatcher<Executor, ResType, ParamType, HeadType, TypeList1, T2, TypeList2>::DispatchT2(*t1, obj2, exec, param);
}
else {
return DoubleDispatcher<Executor, ResType, ParamType, T1, TailType, T2, TypeList2>::DispatchT1(obj1, obj2, exec, param);
}
}

static ResType DispatchT2(T1& obj1, T2& obj2, Executor exec, ParamType& param) {
using HeadType = typename TypeList2::HeadType;
using TailType = typename TypeList2::TailType;
if (HeadType* t2 = dynamic_cast<HeadType*>(&obj2)) {
return exec.applyDoubleDispatch(obj1, *t2, param);
}
else{
return DoubleDispatcher<Executor, ResType, ParamType, T1, TypeList1, T2, TailType>::DispatchT2(obj1, obj2, exec, param);
}
}
};
// see the book for code of specialization when T1 and T2 are not supported