Defining a unique pointer in a header file with a custom deleter defined externally

StackOverflow https://stackoverflow.com/questions/19912395

  •  30-07-2022
  •  | 
  •  

Вопрос

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)
{
}
Это было полезно?

Решение

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 */ }

Другие советы

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;

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top