【问题标题】:C++ virtual templated methodC++虚拟模板方法
【发布时间】:2016-09-28 12:37:51
【问题描述】:

我知道这个问题已经一次又一次地以许多不同的形式得到了回答,这可能是 C++ 设计中最令人困惑的点之一,但我正在努力学习这门语言,经过多年在纯 C 中做了很多事情在 C++ 世界中将被认为是非常非法的(想想函数指针杂耍)。在放弃并改用另一种语言之前,我想尝试让自己接受 C++ 思维方式。

我刚刚开始了一个项目,其中最基本的组件是一个流类,为此我希望它是通用的:它将流式传输什么样的数据取决于它的子类。

template <typename T>
class BasicStream {
protected:
    T *buffer;
    unsigned int bufferSize;
    unsigned int bufferPos;
    bool streamEnd=false;
public:
    virtual T read();
};

我的想法是将对象链接在一起,就像某个类的一个对象的输出被另一个类的另一个对象直接读取一样。但要使其工作,所有对象都必须能够接受通用的read() 函数并返回其所需的类型。例如,我有一个类来拼接接受字节(无符号字符)作为输入的位:

class BitExtractor : public BasicStream<bool> {
private:
    unsigned char bitMask;
    unsigned char byte;
    BasicStream<unsigned char> &byteSource;

public:
    BitExtractor(BasicStream<unsigned char> &source);
    virtual bool read();
};

它返回一个bool 类型并且需要从BasicStream 派生的任何类并且具有&lt;unsigned char&gt; 返回类型作为输入。我的想法是让输入与数据源完全无关;无论是文件、互联网流,甚至是内存中的某个位置;全部围绕从BasicStream&lt;unsigned char&gt;派生的类。

一个例子是 FileReader 类,用于处理异步文件加载:

class FileReader : public BasicStream<unsigned char> {
protected:
    FILE *file;
    bool asyncFlag;
    bool asyncOpReady;
    bool fileEnded;
    pthread_t asyncThread;
    unsigned int lastRead;
public:
    FileReader(char *fileName,int bufferSize=1024,bool asyncRead=false);
    ~FileReader();

    virtual unsigned char read();

private:
    typedef struct {
        unsigned int amount;
        unsigned int *read;
        unsigned char *buffer;
        FILE *file;
        bool *flag;
        bool *flagStreamEnd;
    } TData;
    static void AsyncRead(void *data);
};

现在,假设我想创建一个使用 FileReader 作为数据源的 BitExtractor。

BitExtractor bx=BitExtractor(FileReader("SomeFile.abc"));
bool firstBit = bx.read();

在内部,BitExtractor 正在调用 FileReader read() 方法。我的假设是,由于FileReader 是从BasicStream&lt;unsigned char&gt; 派生的类,它应该识别模板化函数。

BitExtractor::BitExtractor(BasicStream<UInt8> &source):bitMask(128),byteSource(source){}

bool BitExtractor::read(){
    bool bit=byte&bitMask;
    if(streamEnd==false){
        bitMask>>=1;
        if(bitMask==0){
            try {
                byte=byteSource.read();
                bitMask=128;
            } catch (...) {
                streamEnd=true;
            }
        }
    }
    else{
        throw "Bytesource has ended!\n";
    }

    return bit;
}

即使编译成功,由于vtable 错误导致链接失败:

Undefined symbols for architecture x86_64:
  "BasicStream<bool>::read()", referenced from:
      vtable for BasicStream<bool> in BitIO.o
  "BasicStream<unsigned char>::read()", referenced from:
      vtable for BasicStream<unsigned char> in FileIO.o

我已经通过其他 StackOverflow 问题了解到,我的代码在 C++ 中是不可能的,因为它缺乏运行时多态性(编译器无法确定子类在运行时调用的 BasicStream 的哪个模板)。我的问题是,鉴于我的数据流/链接模式,是否有任何其他“C++ish”替代方案来实现我的设计,例如使用或继承 STL 中的某些东西(我几乎一无所知)?

还是在 C++ 中根本无法实现?

【问题讨论】:

  • 您的代码没有出现您在标题中提到的问题。模板基类可能会引入一个虚函数(甚至是依赖于类模板类型参数的虚函数)。实例化模板后,vtable 中只有一个定义明确的条目。
  • 您的问题可能出在您未显示的代码中。
  • 至少,请始终显示确切的编译器错误。包含足够多的实现以允许其他人尝试编译也是非常有帮助的。
  • @MVittiS:在 BasicStream 中,更改 'virtual T read();'到'虚拟T读取()= 0;'并为其添加一个虚拟析构函数。这将使 BasicStream 成为一个抽象类,编译器不会期望它的实现。除非您还没有在派生类中提供用于读取的实现,否则该错误不应出现在该更改之后。
  • 这是一个经典的初学者错误;如果有人没有找到与此问题重复的较早问题,我会感到惊讶。阅读“纯虚函数”。

标签: c++ class templates polymorphism virtual


【解决方案1】:

目前的问题是您将模板成员函数声明为虚拟:

virtual T read();

... 但你没有定义它;这就是为什么您会收到链接时错误 - BasicStream&lt;bool&gt; 类的 vtable 需要一个函数来指向,但没有。我很确定问题可以通过将其设为纯虚拟来解决:

virtual T read() = 0;

...或提供默认定义。

【讨论】:

    【解决方案2】:

    当然,您应该使用标准库中的流。

    您提到的唯一重要功能是从流中读取布尔值,这是通过 std::basic_istream 的适当 operator&gt;&gt; 完成的。如果您有额外的特殊需求,可以在 ad hoc 流子类中覆盖它。

    问题中的代码存在人为限制,即假装流是某种类型的同质值序列,具有非常不方便的模板参数。 实际上,流应该被认为是任何类型的值的源或接收器(您可以在任何位置读取或写入任何内容)或字节序列(读取、写入和从其他类型的值转换为其他类型的值)。标准库流使用重载和模板化的 operator>> 和 operator

    【讨论】:

    • 一方面,标准库流旨在读取和写入格式化文本。 OP 的代码清楚地表明了阅读二进制文件的愿望。 OP 可以在 streambuf 周围包装一些东西,但这只会虚拟化事物的源端。第二件事是,“你在重新发明轮子”类型的回答不能解决问题,最好保留为 cmets 而不是答案。
    • @Spencer:虽然输出格式不同,但您可以忽略&lt;iostream&gt; 设计中特定于文本的部分并适应一般模式。特别是T* buffer 的想法很棘手;这意味着你不能混合来自两种不同缓冲区类型的输出,你会失去相对顺序。 &lt;iostream&gt; 改为缓冲格式化输出。
    • @MSalters 但这正是我的意图;我不想混合不同类型的数据。每个类应该只接受一种类型并返回另一种类型(无论是相同类型还是不同类型)。我主要使用模板来执行此操作。
    猜你喜欢
    • 2011-12-19
    • 2014-10-25
    • 2021-06-16
    • 1970-01-01
    • 1970-01-01
    • 2016-04-12
    • 1970-01-01
    • 2011-11-28
    相关资源
    最近更新 更多