【问题标题】:how can I design a RAII file descriptor without creating a new int如何在不创建新 int 的情况下设计 RAII 文件描述符
【发布时间】:2014-03-01 21:16:08
【问题描述】:

我想围绕文件描述符创建一个 RAII 包装器。由于对象可能在线程中传递,它确实是一个共享资源:这就是为什么我通过使用带有自定义析构函数的 shared_ptr 进行第一个实现。

struct file_descriptor
{
    file_descriptor( const std::string & pathname, int flags )
        :m_fd( initialize( pathname, flags ) )
    {
    }

    file_descriptor( const int opened_fd )
        :m_fd( initialize( opened_fd ) )
    {
    }

    operator int() const { return *m_fd; }

private:
    std::shared_ptr<int> initialize( const int opened_fd )
    {
        std::shared_ptr<int> ptr_to_fd;

        try
        {
            int * shared_fd = new int;
            ptr_to_fd = std::shared_ptr<int>( shared_fd, file_descriptor_closer() );
            *shared_fd = opened_fd;
        }
        catch( std::bad_alloc & )
        {
            close( opened_fd );
            throw;
        }

        return ptr_to_fd;
    }

    std::shared_ptr<int> initialize( const std::string & pathname, int flags )
    {
        const int fd = open( pathname.c_str(), flags );        
        if (fd < 0)
            throw std::system_error( std::error_code(errno, std::system_category() ), "cannot create file descriptor" );

        return initialize( fd );
    }
    std::shared_ptr<int> m_fd;
};

自定义析构函数,非常简单:

struct file_descriptor_closer
{
    void operator()(int * const fd) noexcept { if (fd) close(*fd); delete fd; }
}; 

现在我发现设计很糟糕,因为“新 int”。我想过制作一个自定义分配器来指向一个已经分配的块,但这似乎有点矫枉过正。你们有什么建议可以简化一下吗?

【问题讨论】:

  • 考虑改用std::shared_ptr&lt;file_descriptor&gt;
  • @CaptainObvlious:我不能:那需要重写所有使用这个类的代码。此外,它改变了类的“契约”,即类的用户将负责共享资源。现在shared_ptr 是一个实现细节,对用户隐藏。
  • shared_ptr 表示生命周期语义,实际上不应该隐藏。 file_descriptor 设计中的缺陷应该清楚地表明您设计的其余部分存在缺陷。我建议将这些作为一个整体解决并使用std::shared_ptr&lt;file_descriptor&gt;,否则您的设计将继续受到影响并且变得越来越难以维护。
  • @CaptainObvlious 您能否详细说明为什么将std::shared_ptr 作为类实现的一部分是一个缺陷?我不明白那部分

标签: c++ c++11 file-descriptor raii


【解决方案1】:

恕我直言,您正在混合责任。让您的 RAII 类处理文件描述符的打开和关闭。让其他班级处理您的 RAII 班级的终身问题。正如您现在所拥有的那样,您的 file_descriptor 类的用户需要知道它在内部使用shared_ptr。乍一看,如果我要在线程之间共享一个 file_descriptor,我会自己创建一个 shared_ptr&lt;file_descriptor&gt; 来解决我真的不知道它在内部已经在这样做的问题。

【讨论】:

  • 我相信这就是 Obvlious 船长在他最初的评论中的意思,但现在我理解得更好了。
【解决方案2】:

使用一些温和的暴力:

struct file_descriptor_closer
{
    void operator()(void* fd) noexcept { if (fd) close(reinterpret_cast< int >(fd)); }
}; 
struct file_descriptor
{
    file_descriptor( const std::string & pathname, int flags )
        :m_fd( initialize( pathname, flags ) )
    {
    }

    file_descriptor( const int opened_fd )
        :m_fd( initialize( opened_fd ) )
    {
    }

    operator int() const { return reinterpret_cast< int >(m_fd.get()); }

private:
    std::shared_ptr<void> initialize( const int opened_fd )
    {
        try
        {
            return std::shared_ptr< void >( reinterpret_cast< void* >( opened_fd ), file_descriptor_closer() );
        }
        catch( std::bad_alloc & )
        {
            close( opened_fd );
            throw;
        }
    }

    std::shared_ptr<void> initialize( const std::string & pathname, int flags )
    {
        const int fd = open( pathname.c_str(), flags );        
        if (fd < 0)
            throw std::system_error( std::error_code(errno, std::system_category() ), "cannot create file descriptor" );

        return initialize( fd );
    }
    std::shared_ptr<void> m_fd;
};

【讨论】:

    【解决方案3】:

    为什么不创建自己的容器?比如:http://ideone.com/m3kmaJ 或静态计数器:http://ideone.com/Gs4Kb7

    #include <iostream>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <thread>
    #include <memory>
    #include <unistd.h>
    #include <atomic>
    
    class FD
    {
        private:
            int fd;
            static int count;
    
        public:
            FD(const char* FilePath, int flags) : fd(open(FilePath, flags)) {++FD::count;}
            FD(const FD& other) : fd(other.fd) {++FD::count;}
            FD(FD&& other) : fd(other.fd) { other.fd = -1; }
    
            ~FD()
            {
                FD::count -= 1;
                if (FD::count == 0)
                {
                    std::cout<<"Destroyed\n";
                    if (is_open())
                        close(fd);
                }
            }
    
            bool is_open() {return fd != -1;}
            FD* operator &() {return nullptr;}
            operator int() {return fd;}
    
            FD& operator = (FD other)
            {
                fd = other.fd;
                FD::count += 1;
                return *this;
            }
    
            FD& operator = (FD&& other)
            {
                fd = other.fd;
                other.fd = -1;
                return *this;
            }
    };
    
    int FD::count = 0;
    
    int main()
    {
        FD fd = FD("Unicode.cpp", O_RDONLY);
        FD copy = fd;
        FD cpy = FD(copy);
    
        return 0;
    }
    

    【讨论】:

    • 等等,你是不是把我的new int换成了另一个new int? :)
    • 和你的shared_ptr.. 是的,我做到了。这是一个小的参考计数器。除非您更喜欢使用static int 进行计数..
    • 所以我还有一个new int,但现在我自己实现std::shared_ptr?我错过了什么吗?
    • 好吧,如果你非常讨厌内存分配,你可以将计数器设为静态:ideone.com/Gs4Kb7
    • @JoelFalcou 也许是这样,但是当你想出一个更好的方法而不是 reinterpretingint 作为 void pointer 或在堆上创建一个 new int 以坚持到 @987654335 @like OP 不想要,然后告诉我。不过,我会接受反对票。没什么大不了的。阅读我上面的答案:安德烈。这是对原始问题的一个很好的看法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多