MVittiS
MVittiS

Reputation: 434

C++ virtual templated method

I know this question has been answered in many different forms again and again, and is perhaps one of the most confusing points in C++'s design, but I'm trying to learn the language after many years doing many things in pure C that would be considered quite illegal in the C++ world (think function pointers juggling). I want to try educating myself to the C++ mindset before giving up and going to another language.

I just started a project in which the most fundamental component is a streaming class, and for that I wanted it to be generic: what kind of data it would stream would be up to its subclasses.

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

My idea was to chain objects together, as in the output of one object of certain class being directly read by another object of another class. But for this to work, all objects must be able to accept a generic read() function and return its desired type. For instance, I have a class to splice bits which accepts bytes (unsigned char) as inputs:

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

It returns a bool type and needs any class which derives from BasicStream and has an <unsigned char> return type as input. My idea was to make the input completely agnostic from the data source; be it a file, an internet stream, or even some position in memory; all wrapped around classes derived from BasicStream<unsigned char>.

An example would be a FileReader class, to handle a/synchronous file loading:

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

Now, let's suppose I want to create a BitExtractor using a FileReader as data source.

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

Internally, BitExtractor is calling the FileReader read() method. My assumption was that since FileReader is a class derived from BasicStream<unsigned char>, it should recognize the templated function.

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

Even though it does compile, it fails to link due to vtable errors:

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

I've already learnt though other StackOverflow questions that my code is impossible in C++, given its lack of runtime polymorphism (the compiler cannot decide which template of BasicStream the subclass invokes at runtime). My question is, given my data streaming/chaining pattern, is there any other more "C++ish" alternative to implementing my design, like using or subclassing something from the STL (of which I know almost nothing)?

Or is it simply unimplementable in C++?

Upvotes: 2

Views: 189

Answers (2)

davmac
davmac

Reputation: 20631

As it stands the problem is that you declare the template member function virtual:

virtual T read();

... but you don't define it; that's why you get a link-time error - the vtable for the BasicStream<bool> class needs a function to point at, and there isn't one. I'm pretty sure the problem could be fixed either by making it pure virtual:

virtual T read() = 0;

... or providing a default definition.

Upvotes: 5

Lorenzo Gatti
Lorenzo Gatti

Reputation: 1270

Of course you should use streams from the standard library.

The only nontrivial feature you mention is reading a bool from a stream, which is done with the appropriate operator>> of std::basic_istream. If you have extra special needs, you can override it in an ad hoc stream subclass.

The code in the question has the artificial limitation of pretending that a stream is a homogeneous sequence of values of a certain type, with an highly inconvenient template parameter. In reality a stream should be considered a source or sink of values of any type (you can read or write anything at any position) or a sequence of bytes (which are read, written, and transformed from and to values of other types). The standard library streams do both, with the overloaded and templated operator>> and operator<< and with the character-based low-level I/O and position accounting.

Upvotes: 1

Related Questions