If he forgets to do the check against NULL, we risk our application to crash.
If he forgets to call mysql_free_result(), we'll have a memory leak.
To solve this issues we wrap the MYSQL_RES in a class, ResultSet, and we use a variation of the Null Object pattern, to let a dummy ResultSet object to be generated when actually no ResultSet is available.
A decision we have to take designing the ResultSet class is if we want to allow that an object of this class could be copied or not. A conservative suggestion would be not to allow that, the reason being that this would save us to mess around with the internal details of the MYSQL_RES object. On the other side, it could be useful to give the chance to the user to move around ResultSet object.
I have decided for an intermediate solution: a ResultSet object could be moved using the same strategy used, for instance, by std::auto_ptr. That means, after copied, the source object would be nullified.
Let's see a first implementation for the class:
class ResultSet
{
private:
MYSQL_RES* rs_;
void moveToThis(ResultSet& rhs); // 1.
public:
ResultSet() : rs_(0) {} // 2.
ResultSet(MYSQL_RES* rs) : rs_(rs) {} // 3.
ResultSet(ResultSet& rhs);
ResultSet& operator=(ResultSet& rhs);
~ResultSet();
_OPERATOR_BOOL() const { return (rs_ != 0 ? _CONVERTIBLE_TO_TRUE : 0); } // 4.
};
1. Common functionality used by both copy ctor and assignment operator.
2. Null Object ctor.
3. "Normal" ctor.
4. operator to test if actually we have a "real" object or a null one. It is based on defines for the MSVC compiler. To do something portable I could have used the boost library (please assume here that was not possible)
And here is the implementation for the declared methods:
void ResultSet::moveToThis(ResultSet& rhs)
{
rs_ = rhs.rs_;
rhs.rs_ = 0;
}
ResultSet::ResultSet(ResultSet& rhs)
{
moveToThis(rhs);
}
ResultSet& ResultSet::operator=(ResultSet& rhs)
{
moveToThis(rhs);
return *this;
}
ResultSet::~ResultSet()
{
mysql_free_result(rs_); // 1.
}
1. Calling mysql_free_result() with NULL is a no-op, so this piece of code works fine also for the Null Object.
So, now we can rewrite Connection::query() in this way:
ResultSet Connection::query(const char* command)
{
if(mysql_query(&mysql_, command))
throw MySQLException(error());
// if no resultset is expected, return a dummy
if(mysql_.field_count == 0)
return ResultSet();
MYSQL_RES* rs = mysql_store_result(&mysql_);
return ResultSet(rs);
}
No comments:
Post a Comment