-2

一个多媒体文件可能有不同日期类型的数据,例如 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、...的指针,这些指针可以传递给第三方库?这种设计是解决这类问题的正确方法吗?谢谢。

4

1 回答 1

0

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

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

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

双调度.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
于 2020-06-22T13:53:31.420 回答