1

I am converting my code to use unique_ptr instead of just pointers. I am trying to create a unique_ptr to a sqlite3_statement that automatically calls the function sqlite3_finalize(sqlite3_stmt *pStmt) in its custom deleter. How can I get this to work in my header file? I am completely at loss here, any help is welcome. Thanks.

With a lot of help from igleyy ( his solution gave me memory access errors when the deleter was called for some reason) I came up with this solution that seems to work and looks elegant;

Datasource.h


#pragma once
#include <sqlite\sqlite3.h>
#include <string>
#include <vector>
#include <memory>

class Datasource
{

    void statementDeleter(sqlite3_stmt* object);
    std::unique_ptr<sqlite3_stmt, std::function<void(sqlite3_stmt*)>> statement;

    Datasource(Datasource const &);
    Datasource &operator=(Datasource const &);

public:
Datasource(void);
~Datasource(void);
};

Datasource.cpp


#include "Datasource.h"

Datasource::Datasource(void) : 
statement(nullptr,std::bind(&Datasource::statementDeleter,this,std::placeholders::_1))
{
}

void Datasource::statementDeleter(sqlite3_stmt * s)
{
sqlite3_finalize(s);
}

Datasource::~Datasource(void)
{
}
Aktaeon
  • 189
  • 2
  • 14

3 Answers3

2

You could write simple lambda expression like this:

auto d = [](sqlite3_stmt* stmt) { sqlite3_finalize(stmt); };
std::unique_ptr<sqlite3_stmt, decltype(d)> statement(new sqlite3_stmt, d);

Explanation

Custom deleter is created using lambda expression and stored in variable of automatically recognized type. decltype is used to obtain type of expression (which in your case you must provide while creating unique_ptr to feed it with your custom deleter).

Edit

There are two statement variables in your Datasource class code, you need to rename one of them. You cannot initialize d and first statement variables like this. I think there is no possibility to use auto in such context, but we can use std::function. Include and replace auto d ... with std::function<void(sqlite3_stmt*)> d;. Initialize d in constructor initialization list. Change statement to std::unique_ptr<int, decltype(d)> statement; and also initialize it in constructor initialization list.

Datesource::Datasource() : 
  d([](sqlite3_stmt* stmt) { sqlite3_finalize(stmt); }), 
  statement(new sqlite3_stmt) { /* constructor code */ }
igleyy
  • 605
  • 4
  • 16
  • Hi entering this in my header file VC2012 gives me the following errors; -Error cannot deduce 'auto' type (initializer required) on the d and 'Error a lambda is not allowed in a constant expression' on the []. Any ideas? – Aktaeon Nov 11 '13 at 17:52
  • Could you show where in your code you pasted `auto d ...` expression? – igleyy Nov 11 '13 at 17:59
  • Immediately after the class definition in my header file replace the std::unique_ptr in my code above. Besides the class constructor and deconstructed there is nothing else defined. – Aktaeon Nov 11 '13 at 18:02
  • See the edited post above second code block, thanks for the help! – Aktaeon Nov 11 '13 at 18:20
  • @Aktaeon I updated my answer to explain why it didn't work and how to fix it. – igleyy Nov 11 '13 at 18:39
  • Sorry about that it was commented out before, I am trying bstamour's solution at the same time. Even after deleting the second statement I have the same error :( As mentioned in my first comment. – Aktaeon Nov 11 '13 at 18:43
1

Just define a custom deleter that frees the statement:

struct sqlite_finalizer {
    void operator()(sqlite3_stmt*& s) const {
        sqlite3_finalize(s);
        // ... anything else.
    }
};

and then make a custom typedef for it:

using sqlite3_handle = std::unique_ptr<sqlite3_stmt, sqlite_finalizer>;
sqlite3_handle handle;
bstamour
  • 7,746
  • 1
  • 26
  • 39
0

I would say that unique ptr is not the way to go on this one. I say this because the stdlib already has a parallel to this for mutexes called lock_guard, you can't use lock_guard but the idea is the same. Make a class called sql_stmt_guard or something. Basically how it would work is on construction it would do the setup methods on the statement and the destructor would call sqlite3_finalize. lock_guard (or unique_lock when the situation arises) in c++ is considered idiomatic and is very useful because RAII gives you the guarantee that the unlock method will run, and for you the guarantee that your sqlite3_finalize will get called

aaronman
  • 18,343
  • 7
  • 63
  • 78