【问题标题】:how to get result which is returned with C callback in C++如何获取在 C++ 中使用 C 回调返回的结果
【发布时间】:2015-08-16 05:10:30
【问题描述】:

我对在 C++ 中处理 C 回调比较陌生。我制作了一个 sqlite 包装器 c++ 类,它只调用 sqlite3_exec()。

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
  SqliteAccessor* sqlite = static_cast<SqliteAccessor*> NotUsed; 
  if(argc > 0) { 
    sqlite->set_table_exists(true); 
  }
  return 0;
}

class SqliteAccessor{    
public:
bool has_table(const string dbName, const string tblName)
{
  string sql;
  sql = "SELECT " + quote_string(tblName) + "FROM " + quote_string(dbName)
           + "WHERE type = 'table' AND name = " + quote_string(tblName) + ";";
  char *zErrMsg = 0;
  int rc = sqlite3_exec(m_db, sql.c_str(), callback, (void*) this, &zErrMsg);
  if( rc != SQLITE_OK ){
    printf("SQL error: %s", zErrMsg);
    sqlite3_free(zErrMsg);
  }
  // anyway to return the result directly?
  // return hasTable;
}

// can I avoid the following methods and the member variable? 
void set_table_exists(bool isExist) { m_table_exist = isExist; }
bool get_table_exists() { return m_table_exist; }
private: 
  static bool m_table_exist; 
};

int caller(){
   SqliteAccessor sqlite;
   // to check if table exist
   if (sqlite->has_table()){
      // will above work or 
      // I should do with an extra call to query the changed state?        
   }
}

现在,我很困惑调用者如何从 sqlite 包装器中获取结果。我认为,调用者不能通过简单地调用has_table() 获得结果,因为结果是由set_table_exists() 从回调返回的。那么调用者是否应该通过另一个调用来获得结果,例如打电话给sqlite-&gt;get_table_exists()

那么这意味着对于每一个回调,我需要在SqliteAccessor类中创建一个成员变量(也必须是static),以及一对set/get_state(),这样会很麻烦。

如何设计类以使其易于被调用者使用? 不幸的是,我们的代码库不支持 c++11。

【问题讨论】:

  • 即回调设置的结果。我想避免它。
  • hasTable 一旦初始化为false 就不会改变,所以has_table() 总是返回false。如果sqlite-&gt;get_table_exists() 确实存在,那是has_table() 应该返回的。那么SqliteAccessor的用户除了has_table()之外什么都不需要调用。
  • 我的错,我修改了它。谢谢!
  • 您的问题是缺少代码。你调用了一个成员函数set_table_exists(true),但是你没有展示它的实现。它有什么作用?如果它将成员 bool 变量设置为 true,那么是的,has_table() 可以返回它的值。
  • 可以吗?它是否已经由回调设置?我已经用这两个函数更新了我的代码。谢谢!

标签: c++ sqlite design-patterns callback


【解决方案1】:

如果您使用 C++11,请考虑使用 lambda 而不是回调。

class SqliteAccessor{    
public:
bool has_table(const string dbName, const string tblName)
{
  bool hasTable = false;
  string sql;
  sql = "SELECT " + quote_string(tblName) + "FROM " + quote_string(dbName)
           + "WHERE type = 'table' AND name = " + quote_string(tblName) + ";";
  char *zErrMsg = 0;
  int rc = sqlite3_exec(m_db, sql.c_str(), [&](void *NotUsed, int argc, char **argv, char **azColName){
  SqliteAccessor* sqlite = static_cast<SqliteAccessor*> NotUsed; 
  if(argc > 0) { 
    hasTable = true; 
  }
 }
 , (void*) this, &zErrMsg);
  if( rc != SQLITE_OK ){
    printf("SQL error: %s", zErrMsg);
    sqlite3_free(zErrMsg);
  }
  return hasTable;
}
};

如果您无法访问 C++11,您始终可以手动编写仿函数。但是,您会失去一些简洁性和局部性。好的部分是函子可以保存您需要的状态。

struct callback{
  bool operator(void *NotUsed, int argc, char **argv, char **azColName)
  {
  SqliteAccessor* sqlite = static_cast<SqliteAccessor*> NotUsed; 
  if(argc > 0) { 
    hasTable = true; 
  }
  return false;
  }
  bool hasTable;
};


class SqliteAccessor{    
public:
bool has_table(const string dbName, const string tblName)
{
  bool hasTable = false;
  string sql;
  sql = "SELECT " + quote_string(tblName) + "FROM " + quote_string(dbName)
           + "WHERE type = 'table' AND name = " + quote_string(tblName) + ";";
  char *zErrMsg = 0;

  callback c;
  int rc = sqlite3_exec(m_db, sql.c_str(), c, (void*) this, &zErrMsg);
  if( rc != SQLITE_OK ){
    printf("SQL error: %s", zErrMsg);
    sqlite3_free(zErrMsg);
  }
  return c.hasTable;
}
};

【讨论】:

  • 不错!你可以省略SqliteAccessor* sqlite = static_cast&lt;SqliteAccessor*&gt; NotUsed;
  • 很遗憾,c++11 不可用。 :-(
  • 哦,这很不幸,但您可以编写自己的仿函数来这样做!不太好,但它也可以工作,我会升级答案
  • 是的,我真的很想知道,如何在 C++ 中使回调更好。
  • C++03 版本仅在 sqlite3_exec() 是模板时才有效,我对此表示怀疑,因为 OP 说它使用 C 回调函数。
【解决方案2】:

我会这样做的方式是使回调成为私有静态成员函数,并基本上做你所做的。像这样:

class SqliteAccessor
{
public:
    bool has_table(const std::string dbName, const std::string tblName);

private:
    static int callback(void *NotUsed, int argc, char **argv, char **azColName);

    bool m_hasTable;
};

int SqliteAccessor::callback(void *NotUsed, int argc, char **argv, char **azColName)
{
     SqliteAccessor* sqlite = static_cast<SqliteAccessor*>(NotUsed);
     if(argc > 0) sqlite->m_hasTable = true;
     return 0;
}


bool SqliteAccessor::has_table(const std::string dbName, const std::string tblName)
{
    m_hasTable = false;
    string sql = "SELECT " + quote_string(tblName) + "FROM " + quote_string(dbName)
           + "WHERE type = 'table' AND name = " + quote_string(tblName) + ";";
    char *zErrMsg = 0;
    int rc = sqlite3_exec(m_db, sql.c_str(), callback, (void*) this, &zErrMsg);
    if( rc != SQLITE_OK )
    {
        printf("SQL error: %s", zErrMsg);
        sqlite3_free(zErrMsg);
    }

    return m_hasTable;
}

int caller()
{
   SqliteAccessor sqlite;
   // to check if table exist
   if (sqlite.has_table())
   {
      // do stuff :)
   }
}

如果 sqlite3_exec() 不适用于静态函数,您可以尝试使用这样的全局函数:

class SqliteAccessor
{
public:
    bool has_table(const std::string dbName, const std::string tblName);

private:    
    bool m_hasTable;

    friend int callback(void *NotUsed, int argc, char **argv, char **azColName);
};

int callback(void *NotUsed, int argc, char **argv, char **azColName)
{
     SqliteAccessor* sqlite = static_cast<SqliteAccessor*>(NotUsed);
     if(argc > 0) sqlite->m_hasTable = true;
     return 0;
}

【讨论】:

  • 好。有更好的方法吗?因为如果我有 100 个回调,那么我将有 100 个私有回调,100 个成员变量。这很烦人。
  • @pepero 你只有 1 个回调,而不是 100 个。如果你有一个活着的对象,成员变量只会占用空间。因此,如果您必须按顺序进行 100 个 callbaks,您可以使用相同的 SqliteAccessor 并使用不同的参数继续调用 has_table()。这只会花费你 1 个 bool 变量(所以在 32 位机器上需要 4 个字节)。
  • 对不起,我不明白你的“只有 1 个回调”。如果我有两种不同的方法和两个不同的 sql 查询,例如has_table 和 remove_entry,我想我必须做两个回调?我对吗?另外,我不太喜欢这种方式,因为您必须将所有这些成员变量设为静态
  • @pepero 对于每一个不同的回调,你都需要一个新的回调函数。即使在 C++11 中,您也需要 100 个 lambda 函数 :) 没有静态成员变量。只是保存回调想要返回的信息的普通变量。您可能可以为不同的回调使用相同的变量。对于每个返回 bool 的回调,您都可以使用 bool 成员变量,因为您一次不会使用多个回调。我认为一般来说没有更好的回调方法,很抱歉。
  • 回调函数是静态的,所以成员变量必须是静态的,不是吗?
猜你喜欢
  • 1970-01-01
  • 2016-03-21
  • 1970-01-01
  • 2016-03-27
  • 2015-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多