You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
database_binder design suffers from a minor shortcoming. It has a defaulted move constructor and a destructor path that assumes every object was fully initialized by the parametrized constructor.
These are subtle contradictions, because when the move constructor is used for in-place initialization, the "moved" object is empty. So, for instance:
database_binder
class dbFront {
std::unique_ptr<database_binder> storedProcedure;
database db;
dbFront(const std::string &dbFile, const std::string &key): db(dbFile) {
db << "PRAGMA key = '" << key << "'";
createTables(db);
storedProcedure = std::make_unique<database_binder>(db << "INSERT INTO log (a, b) VALUES(?,?)");
}
(I understand the unique_ptr implications, it's beside the point. There are other scenarios where move constructor would apply. The reason unique_ptr was chosen is that I need to initialize the stored procedure as class members and there's no default constructor. We could add one, but, if at a later time, we decided to replace/update the stored procedure object [by move], the bug would trigger).
There are "two objects" created on the storedProcedure = ... line; the one returned by db << "..." which is moved into the empty one allocated by std::make_unique.
The default move construtor works ok, the problem with above is that the object returned by the "db << "..." statement, after the move, is replaced by the empty object, and this one has no statement at the _stmt member, nor a pointer to the db for that matter.
The empty object destructor is called on site, and it tries to call execute(), which won't work and throws.
There are many ways to fix it - I think the most polite would be to have a custom move construtor, but what I did instead was to check if the _stmt is valid and only call execute in this case:
~database_binder() noexcept(false) {
/* Will be executed if no >>op is found, but not if an exception
is in mid flight */
if(!execution_started && !std::uncaught_exception() && _stmt) {
execute();
}
}
The text was updated successfully, but these errors were encountered:
database_binder design suffers from a minor shortcoming. It has a defaulted move constructor and a destructor path that assumes every object was fully initialized by the parametrized constructor.
These are subtle contradictions, because when the move constructor is used for in-place initialization, the "moved" object is empty. So, for instance:
(I understand the unique_ptr implications, it's beside the point. There are other scenarios where move constructor would apply. The reason unique_ptr was chosen is that I need to initialize the stored procedure as class members and there's no default constructor. We could add one, but, if at a later time, we decided to replace/update the stored procedure object [by move], the bug would trigger).
There are "two objects" created on the storedProcedure = ... line; the one returned by db << "..." which is moved into the empty one allocated by std::make_unique.
The default move construtor works ok, the problem with above is that the object returned by the "db << "..." statement, after the move, is replaced by the empty object, and this one has no statement at the _stmt member, nor a pointer to the db for that matter.
The empty object destructor is called on site, and it tries to call execute(), which won't work and throws.
There are many ways to fix it - I think the most polite would be to have a custom move construtor, but what I did instead was to check if the _stmt is valid and only call execute in this case:
The text was updated successfully, but these errors were encountered: