C++智能指针混淆

C++ smart pointers confusion

我了解到在C++领域提倡使用智能指针。我有一个简单的程序如下。

/* main.cpp */
#include <iostream>
#include <memory>
using namespace std;

/* SQLite */
#include "sqlite3.h"

int main(int argc, char** argv)
{
    // unique_ptr<sqlite3> db = nullptr; // Got error with this
    shared_ptr<sqlite3> db = nullptr;

    cout << "Database" << endl;
    return 0;
}

当我用 unique_ptr 行编译时得到一条错误消息:

error C2027: use of undefined type 'sqlite3'
 error C2338: can't delete an incomplete type

当我用 shared_ptr 行编译时,它是成功的。从几个问题和答案中,我的理解是 unique_ptr 应该是首选,因为我不打算让对象共享资源。在这种情况下最好的解决方案是什么?使用 shared_ptr 还是回到裸指针的旧方法 (new/delete)?

sqlite3 是一个 不透明结构 (很像 C 中的 FILE)。您所拥有的只是它的声明,而不是它的定义。这意味着您不能在没有自定义删除器的情况下直接在 std::unique_ptr 中使用它。

大体的做法在@SomeProgrammerDudes的回答中(采纳)。但为了解决您的顾虑,我发布了这个。

你不应该回到原始的新的和删除的。既不是因为 sqlite3 是不透明类型,也不是因为 std::shared_ptr 的开销。正如指定的另一个答案,您使用 std::unique_tr.

唯一的区别是您如何设置自定义删除器。对于 std::unique_ptr 它是类型定义的一部分,而不是 运行 时间参数。所以你需要做这样的事情:

struct sqlite3_deleter {
  void operator()(sqlite3* sql) {
    sqlite3_close_v2(sql);
  }
};

using unique_sqlite3 = std::unique_ptr<sqlite3, sqlite3_deleter>;
#include <memory>
#include <stdexcept>

/* sqlite 3 interface */
struct sqlite3 {};
extern void sqlite3_close(sqlite3*);
extern int sqlite3_open(sqlite3**);

/* our boilerplate */
struct closer
{
    void operator()(sqlite3* p) const
    {
        sqlite3_close(p);
    }
};

using sqlite3_ptr = std::unique_ptr<sqlite3, closer>;

/* handy maker function */
sqlite3_ptr make_sqlite()
{
    sqlite3* buffer = nullptr;
    int err = sqlite3_open(&buffer);
    if (err) {
        throw std::runtime_error("failed to open sqlite");
    }
    return sqlite3_ptr(buffer);
}

int main()
{
    auto mysqlite = make_sqlite();
}

shared_ptr

的解决方案

我正在学习C++和SQLite,所以我也有这个问题。读完这篇 post 后,我尝试了一些答案。结果是一个工作示例和一个小分析。

  • 首先为智能指针创建自定义删除器。
  • 然后,创建一个空的 share_ptr,包括自定义删除器
  • 然后,为数据库处理程序创建一个空指针(sqlite3 * DB;)
  • 之后,open/create数据库。
  • Link 指向共享指针的原始指针。
  • shared_ptr超出范围后,它也会删除原始指针。

这是相当低效的(见结论),但这是我设法在 sqlite3 中使用智能指针的唯一方法,所以我决定 post 这是一个回答。

#include <iostream>
#include<sqlite3.h>
#include<memory>

//Custom deleter
auto del_sqlite3 = [](sqlite3* pSqlite)
{
    std::cout << "Calling custom deleter." << std::endl;
    sqlite3_close_v2(pSqlite);
};

int main()
{
//Uncomment to run
//const char* dir = "C:\test\db_dir\test.db"
openOrCreateDB(dir);
return 0;
}


int openOrCreateDB(const char* dirName)
{
    std::shared_ptr<sqlite3> DB(nullptr, del_sqlite3);//custom deleter
    auto pDB = DB.get();
    {
        int exit = sqlite3_open(dirName, &pDB);
        DB.reset(pDB);// Replace nullptr with pDB and link
     }
    return 0;
}

为什么使用 sqlite3 的智能指针?

使用智能指针的主要原因是自动化内存管理并避免内存泄漏。因此,如果我们考虑使用 newdelete.

在空闲存储上分配内存,就会发生这种情况

但是我在免费存储中分配数据库处理程序的所有尝试都失败了。

失败 1:使用 sqlite3* DB = new sqlite3;

int openOrCreateDB(const char* dirName)
{
    sqlite3* DB = new sqlite3;//E0070: Incomplete type not allowed
    int exit = sqlite3_open(dirName, &DB);
    sqlite3_close(DB);
    return 0;
}

失败 2:使用 share_ptr

static int openOrCreateDB(const char* dirName)
{
   
    std::shared_ptr<sqlite3> DB(new sqlite3, del_sqlite3);// Incomplete type not allowed
    auto pDB = DB.get();
    {
        int exit = sqlite3_open(dirName, &pDB);
        DB.reset(pDB);
     }
    
    return 0;
}

失败 3:使用 make_shared

我都没试过。在 Meyers 的 Effective Modern C++ 中,第 21 项明确指出您不能使用 make_shared 在堆 上使用自定义删除器构造智能指针.

结论

也许我做错了什么,但 SQLite 似乎不喜欢在堆上分配数据库处理程序(sqlite3 对象)。那么为什么还要使用智能指针呢?即使您在堆栈上分配 db 处理程序,智能指针也会使用更多内存和更多代码行。

使用智能指针的另一个原因是管理所有权。但是,在 sqlite3 中,工作流程非常重复:在例程中:

  • 创建数据库处理程序。
  • 打开数据库,执行SQL语句等
  • 完成声明
  • 完成数据库连接。

所以我不明白为什么我们应该在这个工作流之外传递一个数据库处理程序。

我的建议是继续使用原始指针并用 sqlite3_close(sqlite3 * ptr) 销毁它们。