首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++11及更高版本:用于管理由低级C库提供的系统资源的shared_ptr

C++11及更高版本:用于管理由低级C库提供的系统资源的shared_ptr
EN

Stack Overflow用户
提问于 2017-10-29 14:55:20
回答 3查看 537关注 0票数 2

在过去的几年里,我主要是一名C开发人员,在这里和那里都有一些Python编码。由于有大量相互矛盾的资源,我很难找到在现代C++中管理系统资源的正确方法。

我是利布匹的作者--一个通过字符设备控制GPIOs的库(这是从用户空间管理GPIOs的新方法,而不是现在不推荐的sysfs接口)。核心库代码是用C编写的,但我计划从C++11开始为其他语言提供绑定。

我不想详细了解什么是GPIO,但一般来说,GPIO线是一个可配置的引脚,我们可以控制它与GPIO芯片相关联,而GPIO芯片通常公开几行。C库用两个核心结构( struct gpiod_chipstruct gpiod_line )对这个双层层次结构进行建模。这两种方法只对用户可见,都是由提供的API函数操作的不透明指针。在内部,该芯片与打开的文件描述符(位于/dev/中的设备文件)和包含对象状态的两个变量相关联。

指向分配的芯片对象的指针从其中一个gpiod_chip_open()变体返回给用户。用户负责使用gpiod_chip_close()释放分配的资源。与大多数低级C代码一样,用户的任务是管理资源句柄.该库显式地使线程感知(因为没有全局状态),但不具有线程安全。

芯片管理与它相关的所有行对象的资源,所以下面的问题只与芯片相关。

现在,根据我到目前为止所读到的,在现代C++中,新的和删除的操作符一般不应该手动使用。因此,我对芯片类的最初想法是:

代码语言:javascript
复制
namespace gpiod {

class chip {

    // [snip!]

private:

    std::shared_ptr<::gpiod_chip> _m_chip;

};

}

这将使芯片类保存对gpiod_chip对象的引用。Copy & move构造函数和赋值操作符将简单地利用shared_ptr的引用计数来允许自由移动和复制芯片对象。当引用计数降到0时,自定义删除器将调用C对象上的gpiod_chip_close()

但后来我注意到,有些人建议在这种情况下使用工厂,让用户将对象包装在自己选择的智能指针中。

就现代C++而言,对于我的用例的正确方法有什么建议吗?

EN

回答 3

Stack Overflow用户

发布于 2017-10-29 15:19:02

除非您需要资源具有共享所有权,否则没有必要使用std::shared_ptr,而且在您的示例中,情况似乎并非如此。在大多数情况下,只需要使用std::unique_ptr,它将在其构造函数中获取指针的所有权,然后使用析构函数中的delete销毁指针。您说您的库主要是用C实现的,所以gpiod_chip_open()返回的指针可能是用malloc分配的,所以用delete销毁它,这是一种未定义的行为。要解决这个问题,您可以为std::unique_ptr指定一个自定义删除函式,将gpiod_chip_close()称为析构函数,而不是delete

你可以这样做:

代码语言:javascript
复制
#include <memory>

struct gpiod_chip_deleter {
    void operator()(::gpiod_chip* chip) noexcept {
        ::gpiod_chip_close(chip);
    } 
};

using gpiod_chip_ptr = std::unique_ptr<::gpiod_chip, gpiod_chip_deleter>;

// ...

gpiod_chip_ptr chip(::gpiod_chip_open());

// ...
票数 1
EN

Stack Overflow用户

发布于 2017-10-29 16:36:51

只有在实际需要共享所有权语义的情况下,才应该使用std::shared_ptr,而这种语义往往很少见。

指向分配的芯片对象的指针从其中一个gpiod_chip_open()变体返回给用户。用户负责用gpiod_chip_close()释放分配的资源。

C似乎没有提供共享所有权。你没必要再加了,是吗?

相反,您应该将std::unique_ptr与自定义删除器一起使用,或者使用移动语义编写自己的小包装类,这具有类似的效果,但提供了一个更具体问题的接口。或者用带有自定义删除器的私有std::unique_ptr实现这样的包装类。关键是真正利用移动语义

下面是一个理解这意味着什么的例子:

代码语言:javascript
复制
class Chip final
{
public:
    Chip() :
        ptr(gpiod_chip_open())
    {
        // throw if gpiod_chip_open reported an error
    }

    // add other constructors for additional variants of gpiod_chip_open()

    ~Chip()
    {
        gpiod_chip_close(ptr); // add nullptr check if gpiod_chip_close requires one
    }

    Chip(Chip const&) = delete;
    Chip& operator=(Chip const&) = delete;

    Chip(Chip&& other) :
        ptr(other.ptr)
    {
        other.ptr = nullptr;
    }

    gpiod_chip* Get()
    {
        return ptr;
    }

private:
    gpiod_chip* ptr;
};

基本上这就是你所需要的。如果需要,添加其他操作(例如,移动赋值操作符)。您还可能希望摆脱Get成员函数,并将所有其他API函数完全包装在类中;这取决于所需的包装级别。

该库显式地使线程感知(因为没有全局状态),但不具有线程安全。

正确的同步是一个完全不同的问题,std::shared_ptr在任何情况下都无法神奇地使其消失。

std::shared_ptr所保证的就是共享所有权机制本身将在多线程上下文中工作--即,引用计数安全地递增和减少,如果引用计数达到零,则不会发生多次删除。

它不能保证托管对象本身可以安全地从多个线程中使用。这是否安全取决于托管对象。如果一个gpiod_chip (即它的操作)不能安全地从多个线程中使用,那么std::shared_ptr<gpiod_chip>也不能。

在线程A和线程B中,您实际上都可以安全地访问指向同一个gpiod_chip的一个gpiod_chip,但是您仍然需要同步该gpiod_chip上的操作。

票数 1
EN

Stack Overflow用户

发布于 2017-10-29 15:40:55

我不知道你的确切要求,但是否有任何问题,使chip不可复制,只是移动?

代码语言:javascript
复制
class chip {
  int* handle;
public:
  chip(const chip&) = delete;
  chip& operator=(const chip&) = delete;

  chip(chip&& c) { operator=(std::move(c)); }
  chip& operator=(chip&& c) {
    handle = c.handle;
    c.handle = nullptr;
    return *this;
  }

  chip() : handle(gpiod_chip_open()) { }

  ~chip() { gpiod_chip_close(handle); }
};
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47001618

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档