C++智能指针混乱

C++ smart pointers confusion

本文关键字:混乱 指针 智能 C++      更新时间:2023-10-16

我知道在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还是回到裸指针的旧方法(新建/删除(?

一般方法是在@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>;

sqlite3是一个不透明的结构(很像CFILE(。你所拥有的只是它的声明,而不是它的定义。这意味着如果没有自定义删除器,您将无法直接在std::unique_ptr中使用它。

#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,所以我也有这个问题。读完这篇文章后,我尝试了一些答案。结果是一个工作示例和一个小分析。

  • 首先为智能指针创建自定义删除器。
  • 然后,创建一个包含自定义删除程序的空share_ptr
  • 然后,为 DB 处理程序创建一个空指针 (sqlite3 * DB;(
  • 然后,打开/创建数据库。
  • 将原始指针链接到共享指针。
  • shared_ptr超出范围后,它也会删除原始指针。

这是相当低效的(见结论(,但这是我在sqlite3中使用智能指针的唯一方法,所以我决定将其作为答案发布。

#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 的有效现代C++第 21 项中,很明显你不能使用make_shared在带有自定义删除器的堆上构造一个智能指针。

结论

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

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

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

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

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