将unique_ptr用于原始指针的正确方法是什么

What is the proper way to use unique_ptr for raw pointers?

本文关键字:方法 是什么 指针 原始 unique ptr 用于      更新时间:2023-10-16

有两个用于分配和释放原始指针的函数,我想使用C++轻松完成繁琐的工作。我找到了两种选择来unique_ptr处理它,但对我来说它们都不好看:

char *raw_alloc();
void raw_free(char *ptr);
int main (int argc, char *argv[])
{
    //First option:
    {
        std::unique_ptr<char, decltype(&raw_free)> ptr (raw_alloc(), raw_free);
        printf ("size1: %lun", sizeof(ptr) / sizeof(char *));
    }
    //Second option:
    {
        struct deleter { void operator()(char *ptr) { raw_free(ptr); } };
        std::unique_ptr<char, deleter> ptr (raw_alloc());
        printf ("size2: %lun", sizeof(ptr) / sizeof(char *));
    }
}

输出说第一个指针是第二个指针的两倍;当然,保留指向释放函数的指针需要空间。

同时,第二个选项要求我为我的类型创建存根删除器。当然,我可以编写一个函数模板来为我执行此操作:

template <typename T, void (*D)(T*)>
auto my_unique (T *ptr) {
    struct deleter { void operator()(T *ptr) { D(ptr); } };
    return unique_ptr<T,deleter>(ptr);
};

但是为什么unique_ptr不能为我做这件事,而只是接受删除器函数作为第二个模板参数呢?熟练C++人如何处理原始指针?

我通常使用第二种方法,但我会将Deleter类保留在全局范围内,靠近raw_allocraw_free的声明。定义一个raw_unique_ptr速记可能会有所帮助:

char *raw_alloc();
void raw_free(char *ptr);
struct raw_releter
{ void operator()(char * const ptr) const noexcept { raw_free(ptr); } };
using raw_unique_ptr = std::unique_ptr<char, raw_releter>;
默认情况下

std::unique_ptr包含与不包含删除器时不同的实现。由于带有 deletor 的版本将删除器存储为传递的模板类型,因此实际问题归结为从构造函数推导类模板参数。(请参阅此线程)

正如您所提到的,第一个选项比第二个选项占用更多的空间,但是我不认为这是一个问题,因为编译器可以优化很多。

使用

my_unique() 实现是减小std::unique_ptr大小的正确方法,因为它不会将 ptr 存储到函数中,但它假定一个真正的函数指针,而它不包括使用 lambda 作为删除器。(这不是问题,尽管这是这种方法的局限性)

另一种方法是使用 std::shared_ptr ,它不需要知道析构函数的外观,但是它总是存储删除器,并且会占用大量内存,而不必知道删除器的实际实现。

在我看来,当std::unique_ptr在本地时,我会使用类似于您的选项 1 或 2 的东西。如果这是 API 的一部分,则很难选择实现,并且所有三个选项(选项 1、2 或 std::shared_ptr)都很有趣。我会选择std::shared_ptr,因为您可以将其与其他具有自己的删除器的 API 结合使用。(考虑一个以JSON或XML转换数据的工厂,这两个工厂都是将char*清理到生成的流中的不同函数)

std::shared_ptr类似的另一种方法是创建一个 std::unique_ptr<T, std::function<void(T*)>> ,其中包含由于std::function而导致的性能开销,并且与原始选项相比很可能是较大的内存开销。

您可以使用重载的特定于类的 new 和 delete 运算符将原始指针包装到类中:

char* raw_alloc()
{
    // Don't allocate anything, just a demonstration of approach
    return (char*)"Hello, World!";
}
void raw_free(char* ptr)
{
    cout << ptr << endl;
}
class WrappedRawPointer
{
public:
    static void* operator new(std::size_t)
    {
        return raw_alloc();
    }
    static void operator delete(void* ptr, std::size_t)
    {
        raw_free((char*)ptr);
    }
};
int main() {
    // Will print "Hello, World!" twice: once for explicit
    // printing and once for deleter.
    auto ptr = unique_ptr<WrappedRawPointer>(new WrappedRawPointer());
    cout << (const char*) ptr.get() << endl;
    return 0;
}

我相信这种方法更好,因为它解耦了分配/释放逻辑,但同时不会造成任何重大的性能损失。