I have a small C++/Qt library that interfaces with an application in another language (so interfacing must be performed using primitives, notably pointers to Handler
objects). To implement debug logging, each Handler
has its own debug file, managed by a Log
object. These are both constructed in the initializeHandler
function.
To make use of RAII, I didn't want Log
objects to have a default constructor - they have a copy-constructor, and a constructor that takes a QString
to open a QFile
with. If the Log
can't open the file, I want the initializeHandler
function to fail, so I made it throw a LogExceptFailedOpen
exception. To interact with the application using my library, I have to catch
that exception and re-interpret it as a returned error code (I can't just let the exception propogate - the calling application can't catch it). But I want to use the Log
object if construction succeeds. Which leads me to my problem.
If I declare and initialize the Log
in the try
block, I can't access it after the catch
block. I can get around this by using a QSharedPointer<Log>
with a scope larger than the try block, then using the Log
copy-constructor to create another Log
object afterwards (or I could just use the Log
stored in logPtr
, but that's not the point). However, this feels like a hack, and I feel that the code structure could instead be reworked somehow.
There's some discussion here around someone with a similar problem, which suggests that I should either
- do all my work within the try block, or
- abandon RAII for
Log
objects.
Neither option appeals to me very much. RAII is important for Log
objects, as they get passed around and copied a lot and it would be a major hassle to look for all the places where they could go out of scope.
How can I improve my code to avoid this problem?
initializeHandler function:
typedef int ErrorCode;
#define ERROR_FILE_FAILED_OPEN -12
#define ERROR_INVALID_ARGS -1
#define ERROR_OK 0
ErrorCode initializeHandler(Handler*& handler, const char * logFile, int x, int y)
{
QSharedPointer<Log> logPtr;
try {
logPtr.reset(new Log(QString(logFile)));
} catch (const LogExceptFailedOpen& e) {
return ERROR_FILE_FAILED_OPEN;
}
Log logger(*logPtr);
logger.log("Log started.");
logger.log("Initializing handler...");
Handler* temp;
try {
temp = new Handler(logger, x, y);
} catch (const HandlerExceptInvalidArgs& e) {
logger.log("Arguments invalid. x = " + QString::number(x)
+ ", y = " + QString::number(y));
return ERROR_INVALID_ARGS;
}
handler = temp;
logger.log("Successfully initialized handler.");
return ERROR_OK;
}
Log class:
class Log
{
private:
QSharedPointer<QFile> file;
QSharedPointer<QTextStream> stream;
public:
void log(const QString& message)
{
(*stream) << (message + "\r\n");
}
// file constructor
Log(const QString& fileName) : file(fileName)
{
if (!file->open(QFile::WriteOnly)) {
throw LogExceptFailedOpen();
}
stream = QSharedPointer<QTextStream>(new QTextStream(file.data()));
}
// copy-constructor
Log(const Log& other) : file(other.file), stream(other.stream) {}
}