通过下面这个非常简单的C++程序(从真实的东西简化而来),我得到了:
test: ./SQLiteCpp-3.3.0/src/Database.cpp:92: void SQLite::Database::Deleter::operator()(sqlite3*): Assertion `0 == ret && "database is locked"' failed.
Aborted (core dumped)
字符串
当库试图在Database
的析构函数中使用sqlite3_close()
关闭与数据库的连接时,就会发生这种情况。
实际上,ret
的值是SQLITE_忙碌(5)。SQLITE文档表明这通常是指向另一个与同一数据库有连接的进程/线程,但是你可以看到这里不是这样。
main.cpp:
#include "nl.hpp"
int main()
{
auto nl = new NL();
delete nl; // Here only to illustrate the problem.
}
型
nl.hpp:
#include "SQLiteCpp/SQLiteCpp.h"
using namespace std;
class NL
{
public:
NL();
unique_ptr<SQLite::Statement> sqlst_insert_net;
optional<SQLite::Database> db;
void compile_sql_statements();
void create_db();
};
型
nl.cpp:
#include <nl.hpp>
NL::NL() {
this->create_db();
this->compile_sql_statements();
}
void NL::create_db() {
this->db.emplace(SQLite::Database(":memory:", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE));
this->db->exec(
"CREATE TABLE net(id INTEGER PRIMARY KEY, name TEXT UNIQUE);"
);
}
void NL::compile_sql_statements() {
this->sqlst_insert_net = make_unique<SQLite::Statement>(
*(this->db), "INSERT INTO net (name) VALUES (?)");
}
型
在这里起作用的一些事情似乎是:
- 如果我删除
delete nl;
,问题不会发生。我不是C++Maven,但我会想象nl
对象在main()
结束时被销毁,对吗? - 如果我不准备这些语句,只要从
NL
的构造函数中删除this->compile_sql_statements();
,问题就不会发生。
以上两点是必要的。这只是一个非常简化的代码,只是为了复制问题。NL
的示例保存在真实的程序中的shared_pointer
中,因此最终必须调用析构函数。并且还需要准备好的语句。
我以前使用过看起来相同的代码,我没有这个问题。
编辑:附加信息。
下面是堆栈跟踪:
SQLite::Database::Deleter::operator()(SQLite::Database::Deleter * const this, sqlite3 * apSQLite) (.../SQLiteCpp-3.3.0/src/Database.cpp:92)
std::unique_ptr<sqlite3, SQLite::Database::Deleter>::~unique_ptr(std::unique_ptr<sqlite3, SQLite::Database::Deleter> * const this) (/usr/include/c++/9/bits/unique_ptr.h:292)
SQLite::Database::~Database(SQLite::Database * const this) (.../SQLiteCpp-3.3.0/include/SQLiteCpp/Database.h:264)
std::_Optional_payload_base<SQLite::Database>::_M_destroy(std::_Optional_payload_base<SQLite::Database> * const this) (/usr/include/c++/9/optional:257)
std::_Optional_payload_base<SQLite::Database>::_M_reset(std::_Optional_payload_base<SQLite::Database> * const this) (/usr/include/c++/9/optional:277)
std::_Optional_payload<SQLite::Database, false, false, false>::~_Optional_payload(std::_Optional_payload<SQLite::Database, false, false, false> * const this) (/usr/include/c++/9/optional:398)
std::_Optional_base<SQLite::Database, false, false>::~_Optional_base(std::_Optional_base<SQLite::Database, false, false> * const this) (/usr/include/c++/9/optional:471)
std::optional<SQLite::Database>::~optional(std::optional<SQLite::Database> * const this) (/usr/include/c++/9/optional:656)
NL::~NL(NL * const this) (.../src/nl.hpp:8)
main() (.../src/test.cpp:5)
型
以及Assert失败的函数(来自SQLiteCpp的Database.cpp):
// Deleter functor to use with smart pointers to close the SQLite database connection in an RAII fashion.
void Database::Deleter::operator()(sqlite3* apSQLite)
{
const int ret = sqlite3_close(apSQLite); // Calling sqlite3_close() with a nullptr argument is a harmless no-op.
// Avoid unreferenced variable warning when build in release mode
(void) ret;
// Only case of error is SQLITE_BUSY: "database is locked" (some statements are not finalized)
// Never throw an exception in a destructor :
SQLITECPP_ASSERT(SQLITE_OK == ret, "database is locked"); // See SQLITECPP_ENABLE_ASSERT_HANDLER
}
型
如前所述,ret==5 (SQLITE_BUSY)
。
这是 Package 库:SQLiteC++
编辑:附加信息。
正如注解中所建议的,语句没有正确终止。这里显示的析构函数(其中sqlite3_finalize(stmt)
是)从未被调用:
// Prepare SQLite statement object and return shared pointer to this object
Statement::TStatementPtr Statement::prepareStatement()
{
sqlite3_stmt* statement;
const int ret = sqlite3_prepare_v2(mpSQLite, mQuery.c_str(), static_cast<int>(mQuery.size()), &statement, nullptr);
if (SQLITE_OK != ret)
{
throw SQLite::Exception(mpSQLite, ret);
}
return Statement::TStatementPtr(statement, [](sqlite3_stmt* stmt)
{
sqlite3_finalize(stmt);
});
}
型TStatementPtr
是存在于Statement
中的shared_pointer。Statement
的析构函数只是默认值。所以看起来Statement
没有被销毁。
1条答案
按热度按时间1hdlvixo1#
这个问题非常微妙。正如@Shawn在对这个问题的评论中所建议的那样,在关闭与数据库的连接时,准备好的语句还没有完成。实际上,
Statement
的析构函数应该调用sqlite3_finalize(stmt)
,但它从未被调用。这是因为~Database
被首先调用。这仅仅是因为
NL
中声明Statement
和Database
的顺序。这决定了成员被销毁的顺序。首先声明
Database
,然后声明Statement
,会导致Statement
首先被销毁,从而解决问题。