【SQLite C/C++ API】sqlite3, sqlite3_stmtオブジェクトの解放忘れを防ぐ（C++）

# 目的

勝手にリソースを解放してくれるような言語やライブラリに慣れた僕にとって、SQLite C/C++ APIを使う際、以下の煩わしさがある。

* 生成した`sqlite3`, `sqlite3_stmt` オブジェクトの解放を忘れる。
* `sqlite3`は`sqlite3_close()`, `sqlite3_stmt`は`sqlite3_finalize()`でそれぞれ解放する必要があるが、間違えて`sqlite3_free()`のようなメモリ解放関数に`sqlite3_stmt`オブジェクトを渡してランタイムエラーを起こしてしまうことがある。

上記問題を解決するために、`sqlite3`, `sqlite3_stmt`オブジェクトの生成をクラスに移譲し、そのクラスのインスタンス破棄時に、生成したオブジェクトを自動的に破棄するようにする。

# クラスの定義と利用

sqlite3の生成、解放を担う`SQLiteConnector`クラスを以下のように定義する。

```C++
class SQLiteConnector
{
public:
    // filePathはUTF-8で渡すこと
    SQLiteConnector(const std::string& filePath)
        : m_filePath(filePath) {}

    ~SQLiteConnector()
    {
        for (int i = 0, ii = m_dbs.size(); i < ii; i++)
        {
            sqlite3 *db = m_dbs.at(i);
            if (db != nullptr)
            {
                sqlite3_close_v2(db);
            }
        }
    }

    sqlite3 *open()
    {
        int rc;
        sqlite3 *db = nullptr;
        rc = sqlite3_open(m_filePath.c_str(), &db);

        if (rc != SQLITE_OK)
        {
            // エラーハンドリング
        }
        m_dbs.push_back(db);
        return db;
    }

protected:
    std::string m_filePath;
    std::vector<sqlite3*> m_dbs;
};
```

以下のように利用する。

```c++
void useConnector()
{
    std::string filePath = "test.sqlite";
    SQLiteConnector connector(filePath);
    sqlite3 *db = connector.open();

    // *dbを使った処理

    // この関数内でsqlite3_close()を呼び出す必要はない。
}
```

関数`useConnector()`のスコープから抜ける時点で、`SQLiteConnector`インスタンスは破棄される。
破棄される際のデストラクタで、生成された`sqlite3`オブジェクトは開放される。
従って、`useConnector()`内で`sqlite3_close()`や`sqlite3_close_v2()`を呼び出す必要はない。

`sqlite3_stmt`についても、同様の生成、破棄処理を担うクラスを定義できる。

```c++

class SQLiteStmtMaker
{
public:
    SQLiteStmtMaker(sqlite3 *db)
        : m_db(db) {}

    ~SQLiteStmtMaker()
    {
        for (int i = 0, ii = m_stmts.size(); i < ii; i++)
        {
            sqlite3_stmt *stmt = m_stmts.at(i);
            if (stmt != nullptr)
            {
                sqlite3_finalize(stmt);
            }
        }
    }

    sqlite3_stmt *prepare(const std::string& sql)
    {
        int rc;
        sqlite3_stmt* stmt = nullptr;
        rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr);
        if (rc != SQLITE_OK)
        {
            // エラーハンドリング
        }
        m_stmts.push_back(stmt);
        return stmt;
    }

protected:
    sqlite3 *m_db;	// このクラスで解放しない
    std::vector<sqlite3_stmt*> m_stmts;	// このクラスで解放する
};

```

以下のように利用する。

```c++
void useStmtMaker()
{
    std::string filePath = "test.sqlite";
    SQLiteConnector connector(filePath);
    sqlite3 *db = connector.open();
    SQLiteStmtMaker maker(db);

    sqlite3_stmt *stmt = maker.prepare("CREATE TABLE IF NOT EXISTS t1(c1 TEXT)");

    int rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE)
    {
        // エラーハンドリング
    }

    // この関数内でsqlite3_finalize()を呼び出す必要はない
}
```

`useStmtMaker()`関数から抜けるタイミングで、生成された`sqlite3`オブジェクト、および`sqlite3_stmt`オブジェクトは自動的に解放される。
これで解放忘れによるリソースリークの悪夢から逃れられる。

## 注記：sqlite3の解放にはsqlite3_close_v2()を利用する

`SQLiteConnector`のデストラクタでは、`sqlite3_close()`ではなく`sqlite3_close_v2()`を利用して`sqlite3`オブジェクトを解放する。

`sqlite3_close()`を利用して解放する場合、解放しようとする`sqlite3`と関連付けられた`sqlite3_stmt`が全て解放されていないと、`sqlite3_close()`が`SQLITE_BUSY`を返し、`sqlite3`が解放されない。
すなわち、必ず`sqlite3_finalize()` -> `sqlite3_close()` の順で呼び出す必要がある。

一方で、`sqlite3_close_v2()`を利用すると、`sqlite3`と関連付けられた`sqlite3_stmt`のうちで解放されていないものがあったとしても、エラーとならない。関連付けられた`sqlite3_stmt`がすべて解放された時点で、`sqlite3`も解放される。
すなわち、`sqlite3_finalize()`、`sqlite3_close_v2()`の呼び出しは順不同で良い。

よって、`sqlite3_close_v2()`を利用することで、`SQLiteConnector`、`SQLiteStmtMaker`それぞれのインスタンスが破棄される順序を気にしなくて良くなる。

# まとめ

`sqlite3`、`sqlite3_stmt`のような、独自の関数で明示的に解放する必要があるリソースを利用する際には、生成・解放を担うクラスを定義し、そのクラスに生成・解放処理を委譲する。
これにより、解放忘れによるリソースリークを回避することができる。

# 参考文献
本記事の執筆にあたっては[An Introduction To The SQLite C/C++ Interface](https://www.sqlite.org/cintro.html)と、そこに挙げられている各関数やオブジェクトのリファレンスを参考にした。