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

Posted

技术标签:

【中文标题】C++模板编程设计题——根据输入文件返回不同类型【英文标题】:C++ template programming design question - return different type according to input file 【发布时间】:2020-06-15 15:23:17 【问题描述】:

一个多媒体文件可能有uint8_t、int16_t、float等不同日期类型的数据。下面三个例子展示了文件内容,第一个字节表示数据类型:

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、float 等的指针,这些指针可以传递给第三方库?这种设计是解决这类问题的正确方法吗?谢谢。

【问题讨论】:

“这种设计是解决这类问题的正确方法吗?” 这可能是基于意见的。但我会说这是一种可能的方法。但是你的问题是什么?您期待是/否的答案吗? 问题是“客户端如何获取指针以便将其传递给第三方程序进行处理?”。 你知道指针只是一个内存地址吗?您可以传递该指针,但数据将不存在。您可以传递所有数据,也可以使用共享内存。默认情况下,程序不共享内存。 将指针传递给库,而不是应用程序。 【参考方案1】:

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

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

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,我可以让编译器使用模板技术为我生成那些重复的代码。这个草图的想法:

客户端(main.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);
  
  ...
;

doubledispatch.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

【讨论】:

以上是关于C++模板编程设计题——根据输入文件返回不同类型的主要内容,如果未能解决你的问题,请参考以下文章

C++之模板初阶

在 C++ 中,如何将返回值模板化为与参数值不同?

C#题库06:编程题

如何根据运行时输入实例化 c++ 模板?

c语言编程题目求解

c++重用和模板