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