其他分享
首页 > 其他分享> > c – 使用RAII从C风格的API管理资源

c – 使用RAII从C风格的API管理资源

作者:互联网

Resource Acquisition is Initialization(RAII)通常用于管理资源的生命周期,这些资源在生命周期结束时需要某种方式的清理代码,从删除新指针到释放文件句柄.

如何快速轻松地使用RAII来管理从C风格API获取的资源的生命周期?

在我的情况下,我想使用RAII从C风格的API中自动执行清理函数,当它发布的C风格资源的变量超出范围时.我不需要额外的资源包装,我想在这里最小化使用RAII的代码开销.有没有一种简单的方法可以使用RAII来管理C风格的API资源?

How to encapsulate C api into RAII C++ classes?是相关的,但我不认为它是重复的 – 这个问题是关于更完整的封装,而这个问题是关于获得RAII优势的最小代码.

解决方法:

有一种简单的方法可以使用RAII来管理来自C风格界面的资源:标准库的智能指针,有两种形式:std::unique_ptr用于拥有单个所有者的资源,以及用于共享资源的std::shared_ptrstd::weak_ptr团队.如果您在确定资源是什么时遇到问题,this Q&A should help you decide.访问智能指针正在管理的原始指针就像调用其get成员函数一样简单.

如果您想要简单的基于范围的资源管理,std :: unique_ptr是一个很好的工具.它的设计用于最小的开销,并且易于设置以使用自定义销毁逻辑.事实上,在声明资源变量时可以这么简单:

#include <memory> // allow use of smart pointers

struct CStyleResource; // c-style resource

// resource lifetime management functions
CStyleResource* acquireResource(const char *, char*, int);
void releaseResource(CStyleResource* resource);


// my code:
std::unique_ptr<CStyleResource, decltype(&releaseResource)> 
    resource{acquireResource("name", nullptr, 0), releaseResource};

acquireResource在变量生命周期的开始处执行. releaseResource将在变量的生命周期结束时执行,通常是在它超出范围时.1不相信我吗?你可以see it in action on Coliru,我已经为获取和释放功能提供了一些虚拟实现,所以你可以看到它发生.

如果您需要该品牌的资源生命周期,您可以使用std :: shared_ptr做同样的事情:

// my code:
std::shared_ptr<CStyleResource> 
    resource{acquireResource("name", nullptr, 0), releaseResource};

现在,这两个都很好,但标准库有std::make_unique2和std::make_shared,其中一个原因是进一步的异常安全.

GotW #56 mentions对函数的参数的求值是无序的,这意味着如果你有一个函数可以获取你的闪亮的新std :: unique_ptr类型和一些可能在构造上抛出的资源,那么将这个资源提供给这样的函数调用:

func(
    std::unique_ptr<CStyleResource, decltype(&releaseResource)>{
        acquireResource("name", nullptr, 0), 
        releaseResource},
    ThrowsOnConstruction{});

意味着可能会按如下顺序排列说明:

>调用acquireResource
>构建ThrowsOnConstruction
>从资源指针构造std :: unique_ptr

如果第2步抛出,我们宝贵的C接口资源将无法正常清理.

再次如GotW#56中所提到的,实际上有一种相对简单的方法来处理异常安全问题.与函数参数中的表达式求值不同,函数求值不能交错.因此,如果我们获取资源并将其提供给函数内的unique_ptr,我们将保证在ThrowsOnConstruction投入构建时不会泄漏我们的资源.我们不能使用std :: make_unique,因为它返回带有默认删除器的std :: unique_ptr,我们想要自己的自定义删除器.我们还想指定我们的资源获取功能,因为如果没有附加代码,就不能从类型中推断出它.使用模板的强大功能实现这样的功能很简单:3

#include <memory> // smart pointers
#include <utility> // std::forward

template <
    typename T, 
    typename Deletion, 
    typename Acquisition, 
    typename...Args>
std::unique_ptr<T, Deletion> make_c_handler(
    Acquisition acquisition, 
    Deletion deletion, 
    Args&&...args){
        return {acquisition(std::forward<Args>(args)...), deletion};
}

Live on Coliru

你可以像这样使用它:

auto resource = make_c_handler<CStyleResource>(
    acquireResource, releaseResource, "name", nullptr, 0);

并调用func无忧,像这样:

func(
    make_c_handler<CStyleResource(
        acquireResource, releaseResource, "name", nullptr, 0),
    ThrowsOnConstruction{});

编译器不能构造ThrowsOnConstruction并将其粘贴在对acquireResource的调用和unique_ptr的构造之间,所以你很好.

shared_ptr等价物同样简单:只需换出std :: unique_ptr< T,Deletion>使用std :: shared_ptr< T>返回值,并更改名称以指示共享资源:4

template <
    typename T, 
    typename Deletion, 
    typename Acquisition, 
    typename...Args>
std::shared_ptr<T> make_c_shared_handler(
    Acquisition acquisition, 
    Deletion deletion, 
    Args&&...args){
        return {acquisition(std::forward<Args>(args)...), deletion};
}

Use再次类似于unique_ptr版本:

auto resource = make_c_shared_handler<CStyleResource>(
    acquireResource, releaseResource, "name", nullptr, 0);

func(
    make_c_shared_handler<CStyleResource(
        acquireResource, releaseResource, "name", nullptr, 0),
    ThrowsOnConstruction{});

编辑:

正如评论中所提到的,你可以对std :: unique_ptr的使用做进一步的改进:在编译时指定删除机制,这样当它在程序中移动时,unique_ptr不需要携带一个指向删除器的函数指针. .在你正在使用的函数指针上创建一个无状态删除函数需要四行代码,放在make_c_handler之前:

template <typename T, void (*Func)(T*)>
struct CDeleter{
    void operator()(T* t){Func(t);}    
};

然后您可以像这样修改make_c_handler:

template <
    typename T, 
    void (*Deleter)(T*), 
    typename Acquisition, 
    typename...Args>
std::unique_ptr<T, CDeleter<T, Deleter>> make_c_handler(
    Acquisition acquisition, 
    Args&&...args){
        return {acquisition(std::forward<Args>(args)...), {}};
}

然后使用语法略有变化

auto resource = make_c_handler<CStyleResource, releaseResource>(
    acquireResource, "name", nullptr, 0);

Live on Coliru

make_c_shared_handler不会从更改为模板化删除器中受益,因为shared_ptr不会在编译时携带可用的删除程序信息.

1.如果智能指针的值在被破坏时为nullptr,它将不会调用相关的函数,这对于处理资源释放调用的库来说非常好,这些库使用空指针作为错误条件,如SDL.
2. std :: make_unique只包含在C 14的库中,所以如果你使用的是C 11,你可能想要implement your own – 即使它不是你想要的,它也非常有用.
3.此(以及链接在2中的std :: make_unique实现)取决于variadic templates.如果您使用的是具有有限C 11支持的VS2012或VS2010,则您无权访问可变参数模板.在那些版本was instead made with individual overloads for each argument number and specialization combination中实现std :: make_shared.按照你的意愿制作.
4. std :: make_shared实际上有more complex machinery than this,但它实际上需要知道该类型的对象有多大.我们没有这种保证,因为我们正在使用C风格的界面,并且可能只有我们资源类型的前向声明,所以我们不会在这里担心它.

标签:raii,c,c11,c14,c-faq
来源: https://codeday.me/bug/20190926/1822093.html