c++abstract-classfactorytemplating

Factory pattern where abstract class has templating?


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?


Solution

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