Factory pattern where abstract class has templating?

76 Views Asked by At

This is a problem where I understand why the issue is occurring, but I don't know how to change my design in order to fix it. It's my first time trying to use templates in C++, and I want to make a factory pattern where the base class is using templating. Here is the code I have so far, and this part works fine.

class Formats {
    public:
    template <typename T> class Format {
        protected:
        GlobalHandleLock<T> lock;
        public:
        Format(HGLOBAL mediaData) {
            lock = GlobalHandleLock<T>(mediaData);
        }
        virtual bool writeFile() = 0;
    };

    class BMPFormat : Format<RGBData> {
        public:
        BMPFormat(HGLOBAL mediaData) : Format(mediaData) {
        }
        // an override of writeFile that
        // writes a bitmap file from this data
        bool writeFile();
    };

    class WAVFormat : Format<PCMData> {
        public:
        WAVFormat(HGLOBAL mediaData) : Format(mediaData) {
        }
        // an override of writeFile that
        // writes a sound file from this data
        bool writeFile();
    };
};

The scenario here is that I want to export whatever is on the clipboard (which could be in a variety of possible formats) to a file, with the proper headers and whatnot for that format. I am receiving raw data in the form of a Windows HGLOBAL (not up to me, it's what the library I am working with uses.)

I've made a helper class called GlobalHandleLock, which takes in an HGLOBAL and calls GlobalLock on it, and GlobalUnlock in its deconstructor. Then I can call lock.get() or lock.size() to access the raw pointer/size, kind of similar to a std::shared_ptr. It takes in the pointer's type (like RGBData or PCMData in this example) as a template argument. This lock is then used by the writeFile implementations.

I want to have different classes which take in these different types of data, and all of these behind a factory pattern. So in my code, instead of directly using a class like this:

Formats::BMPFormat bmpFormat(mediaData);

I want to be able to do this:

Format* format = formatFactory.getFormat("BMP", mediaData);

The problem starts when I try and define a format factory.

class FormatFactory {
    public:
    Format* getFormat(std::string formatName, HGLOBAL mediaData) {
        if (formatName == "BMP") {
            return new Formats::BMPFormat(mediaData);
        } else if (formatName == "WAV") {
            return new Formats::WAVFormat(mediaData);
        }
    }
};

The definition of getFormat is invalid because Format* does not specify any template arguments. In actual reality, I never want to directly return a Format object - I want it to be pure virtual, and every class that derives from it needs to specify the template argument. I know this, but the compiler doesn't know this. If I change getFormat to return BMPFormat* then it works, but then I can't return all the formats, just the one.

How do I get it to recognize this, so that the code getting the format doesn't need to redundantly specify the type in the template arguments?

1

There are 1 best solutions below

1
pptaszni On BEST ANSWER

I think the easiest way would be to introduce a common non-templated base class:

class FormatBase {
public:
    virtual ~FormatBase()    = default;
    virtual bool writeFile() = 0;
};

template <typename T>
class Format: public FormatBase {
public:
    Format(HGLOBAL mediaData) { lock = GlobalHandleLock<T>(mediaData); }
    // also abstract, no override of writeFile

protected:
    GlobalHandleLock<T> lock;
};

class BMPFormat: public Format<RGBData> {
public:
    BMPFormat(HGLOBAL mediaData) : Format(mediaData) {}
    // an override of writeFile that
    // writes a bitmap file from this data
    bool writeFile() override { return false; }
};

class FormatFactory {
public:
    FormatBase* getFormat(std::string formatName, HGLOBAL mediaData) {
        if (formatName == "BMP") {
            return new BMPFormat(mediaData);
        }
        // else if, else if ...
        else
            return nullptr;
    }
};

This way you still can use the common interface, e.g. if you would like to store your formats pointers in a container

auto* format = factory.getFormat("BPM", data);
bool result = format->writeFile();

and the logic of locking in the constructor doesn't have to be repeated for every different format.