我作为C#程序员具有扎实的背景,但没有使用C / C ++的经验。说,我正在尝试创建一个标量函数来返回一个对象,无论数据类型如何,都通过如下所示的void指针返回。
void * DB::Scalar(char * sqlCmd)
{
sqlite3_prepare_v2(db, sqlCmd, -1, &stmt, NULL);
void *obj = NULL;
if(sqlite3_step(stmt) == SQLITE_ROW)
{
if(sqlite3_column_type(stmt, 0) == SQLITE_INTEGER)
{
int resInt = sqlite3_column_int(stmt, 0);
obj = (void *)&resInt;
}else if(sqlite3_column_type(stmt, 0) == SQLITE_TEXT)
{
//when column type is text copies string, so I can call
//sqlite3_finalize before return;
char* tmp = (char* )sqlite3_column_text(stmt, 0);
int len = strlen(tmp) + 1;
char* resChar = (char*)malloc(len);
memset(resChar, 0, len);
strcpy(resChar, tmp);
obj = (void *)resChar;
}else if(sqlite3_column_type(stmt, 0) == SQLITE_FLOAT)
{
double resDoub = sqlite3_column_double(stmt, 0);
obj = (void *)&resDoub;
}
sqlite3_finalize(stmt);
return obj;//return void pointer and whoever calls it is responsible for
//the appropriate cast
}
}
那看起来不错? 我测试过并且工作得非常好。
答案 0 :(得分:4)
在这段代码中,您有未定义的行为:
if(sqlite3_column_type(stmt, 0) == SQLITE_INTEGER)
{
int resInt = sqlite3_column_int(stmt, 0);
obj = (void *)&resInt;
}
问题在于resInt
变量的范围仅在{
和}
之间的那个块内。一旦代码离开该块,该变量不再在范围内,并且任何指向该变量的指针都不再有效。
答案 1 :(得分:2)
由于C ++不包含任何真正意义上的类型内省功能,因此您可能希望使用模板函数(或其中的一族)。我可能首先将sqlite3_stmt
管理包装在资源类中,如:
class sqlite3_statement {
public:
sqlite3_statement(sqlite3 *db, std::string const& query) {
sqlite3_prepare_v2(db, query.c_str(), -1, &stmt_handle, NULL);
}
~sqlite3_statement() {
sqlite3_finalize(stmt_handle);
}
bool get_next_row() {
return sqlite3_step(stmt_handle) == SQLITE_ROW;
}
int column_type(int index) {
return sqlite3_column_type(stmt_handle, index);
}
operator sqlite3_stmt*() { return stmt_handle; }
private:
sqlite3_stmt *stmt_handle;
};
这将通过RAII原则使资源管理更清洁,更安全。然后,您可以将query_for_scalar
实现为一系列模板函数。请注意,您需要为要支持的每个标量类型显式专门化。
template <typename T> T query_for_scalar(std::string const& query);
template <> int
query_for_scalar<int>(std::string const& query)
{
sqlite3_statement statement(db, query);
if (statement.get_next_row()) {
if (statement.column_type(0) != SQLITE_INTEGER) {
throw runtime_error("invalid column type");
}
return sqlite_column_int(statement, 0);
} else {
throw runtime_error("no row retrieved");
}
}
template <> float
query_for_scalar<float>(std::string const& query)
{
sqlite3_statement statement(db, query);
if (statement.get_next_row()) {
if (statement.column_type(0) != SQLITE_FLOAT) {
throw runtime_error("invalid column type");
}
return sqlite_column_float(statement, 0);
} else {
throw runtime_error("no row retrieved");
}
}
类型映射也可以使用转换器类型完成。这样可能会有点干净。在任何一种情况下,这都将避免其他海报提到的UB,这是一个更加C ++惯用的解决方案。
答案 2 :(得分:1)
首先,没有任何C / C ++语言。当然,这并不意味着您不能混合C和C ++构造,或者使用C ++中的C API(您可以使用来自几乎任何体面语言的C API);但它确实意味着响应应该是C还是C ++会对程序产生重大影响。
目前,你所拥有的是一个带有非常不安全的界面的错误C函数。由于我们谈论的是C,因此我们普遍接受不安全,但我们可以做得更好。
typedef enum { None, Int, CString } Kind_t;
typedef struct Result { Kind_t kind; void* item; } Result_t;
// Note: the memory pointed by Result_t::item, if any, falls
// under the responsibility of the caller.
Result_t db_scalar(char const* sqlCmd) {
// where do db and stmt come from ?
sqlite3_prepare_v2(db, sqlCmd, -1, &stmt, NULL);
Result_t result = { None, 0 };
if (sqlite3_step(stmt) != SQLITE_ROW) { goto exit; }
if (sqlite3_column_type(stmt, 0) == SQLITE_INTEGER)
{
int* i = (int*) malloc(sizeof(int));
if (i == 0) { goto exit; }
*i = sqlite3_column_int(stmt, 0);
result = { Int, i };
goto exit;
}
if (sqlite3_column_type(stmt, 0) == SQLITE_TEXT)
{
char const* tmp = (char*) sqlite3_column_text(stmt, 0);
if (tmp == 0) { goto exit; }
size_t const len = strlen(tmp) + 1;
char* text = (char*) malloc(len);
if (text == 0) { goto exit; }
strncpy(text, tmp, len);
result = { CString, text };
goto exit;
}
exit:
sqlite3_finalize(&stmt);
return result;
} // db_scalar
我对您的初始提案做了一些更改:
item
C ++是什么?好吧,事情以一种有趣的方式发生变化。
// RAII class to always finalize a statement
class StatementFinalizer {
public:
explicit StatementFinalizer(sqlite3_stmt& stmt): stmt(stmt) {}
~StatementFinalizer() { sqlite3_finalize(&stmt); }
private:
sqlite3_stmt& stmt;
}; // class StatementFinalizer
using Result = boost::variant<boost::none_t, int, std::string>;
Result DB::scalar(std::string const& sqlCmd) {
sqlite3_prepare_v2(db, sqlCmd.c_str(), -1, &stmt, nullptr);
StatementFinalizer const deferred(stmt);
if (sqlite3_step(stmt) != SQLITE_ROW) { return boost::none; }
if (sqlite3_column_type(stmt, 0) == SQLITE_INTEGER) {
return sqlite3_column_int(stmt, 0);
}
if (sqlite3_column_type(stmt, 0) == SQLITE_TEXT) {
unsigned char const* text = sqlite3_column_text(stmt, 0);
return text ? std::string(text) : std::string("");
}
return boost::none;
} // DB::scalar
您可能会注意到C ++解决方案明显缩短了。它也更有效(例如int
无分配)。
另一方面,它的结构基本相同,我们只是通过使用析构函数的功能来回避单一退出问题,总是在退出时执行一个动作。