Here's a simplified example of what is called (I hope - please, correct me if I'm wrong) Strategy pattern: there's a class FileWriter which writes key-value pairs to a file and uses object of IFormatter interface for formatting text being written. There are different formatters implementations and formatter object is passed when FileWriter is created.
Here's one (bad) implementation of such pattern:
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <sstream>
using namespace std;
class IFormatter {
public:
virtual string format(string key, double value) = 0;
};
class JsonFormatter : public IFormatter {
public:
string format(string key, double value) {
stringstream ss;
ss << "\""+key+"\": " << value;
return ss.str();
}
};
class TabFormatter : public IFormatter {
public:
string format(string key, double value) {
stringstream ss;
ss << key+"\t" << value;
return ss.str();
}
};
class FileWriter {
public:
FileWriter(string fname, IFormatter& fmt):fmt_(fmt)
{
f_.open(fname.c_str(), ofstream::out);
}
void writePair(string key, double value)
{
f_ << fmt_.format(key, value);
}
private:
ofstream f_;
IFormatter& fmt_;
};
As can be seen, the main drawback of such approach is it's unreliability - Formatter object passed to FileWriter has to exist during whole FileWriter's lifetime, thus calls like FileWriter("test.txt", JsonFormatter()) lead directly to SegFault.
In this regard, I'd like to discuss what could be the other options for implementing such an approach with "easy-to-use" and simplicity requirements:
- either new formatter can be passed when file writer is created, or
- existing formatter can be passed and used.
I came up with several alternatives described below with their drawbacks (IMO):
- templates: having
FileWriteras a template class which takes exactFormatterClassas an argument; drawback: ugly to call:FileWriter<JsonFormatter>("test.txt", JsonFormatter())- here,JsonFormatteris typed twice. - raw pointers:
FileWriter("test.txt", new JsonFormatter()); drawback - who should delete formatter object?FileWriter? if yes, then passing an address of existing formatter will lead toSegFaultonceFileWriterobject attempts to delete formatter. - shared pointers:
FileWriter("test.txt", dynamic_pointer_cast<IFormatter*>(shared_ptr<JsonFormatter*>(new JsonFormatter())); drawback: ugly to call, and again, what if formatter was created before creating file writer?
What would be the best practices here?
UPDATE
In response to answers that suggested to use std::function - What if Formatter may store a state (say, precision) and have additional methods, like getHeader(), for instance, for CSV files?
Additionaly, storing IFormatter by value isn't possible since it's an abstract class.