как я могу создать дескриптор файла RAII без создания нового int

Я хочу создать оболочку 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; }
}; 

Теперь я нахожу дизайн ужасным, а именно из-за "new int". Я думал о создании специального распределителя, указывающего на уже выделенный блок, но это кажется излишним. Есть ли у вас, ребята, предложение упростить это?


person qdii    schedule 01.03.2014    source источник
comment
Попробуйте вместо этого использовать std::shared_ptr<file_descriptor>   -  person Captain Obvlious    schedule 02.03.2014
comment
@CaptainObvlious: я не могу: для этого потребуется переписать весь код, использующий этот класс. Кроме того, он изменяет контракт класса в том смысле, что пользователь класса будет отвечать за совместное использование ресурса. Сейчас shared_ptr — это деталь реализации, скрытая от пользователей.   -  person qdii    schedule 02.03.2014
comment
shared_ptr представляет семантику на всю жизнь и действительно не должна быть скрыта. Недостатки в дизайне file_descriptor должны четко указывать на недостатки в остальной части вашего дизайна. Я предлагаю рассмотреть их в целом и использовать std::shared_ptr<file_descriptor>, иначе ваш дизайн будет продолжать страдать и его будет все труднее поддерживать.   -  person Captain Obvlious    schedule 02.03.2014
comment
@CaptainObvlious, не могли бы вы уточнить, почему наличие std::shared_ptr как части реализации класса является недостатком? я не понимаю эту часть   -  person qdii    schedule 02.03.2014


Ответы (3)


ИМХО, вы смешиваете обязанности. Пусть ваш класс RAII занимается открытием и закрытием файлового дескриптора. Пусть какой-то другой класс занимается вопросом жизни вашего класса RAII. Как у вас сейчас, пользователь вашего класса file_descriptor должен знать, что он использует shared_ptr внутри. На первый взгляд, если бы мне нужно было разделить file_descriptor между потоками, я бы сделал свой собственный shared_ptr<file_descriptor>, чтобы противостоять проблеме, о которой я действительно не знаю, что внутренне он уже делает один.

person Andre Kostur    schedule 02.03.2014
comment
Я считаю, что именно это имел в виду капитан Обвлиус в своем первоначальном комментарии, но теперь я понимаю это лучше. - person qdii; 02.03.2014

используйте мягкое насилие:

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;
};
person cpp-progger    schedule 01.03.2014

Почему бы не создать свой собственный контейнер? Как насчет чего-то вроде: 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;
}
person Brandon    schedule 01.03.2014
comment
подождите, вы только что заменили мой new int на другой new int? :) - person qdii; 02.03.2014
comment
и ваш shared_ptr.. и да я сделал. Это небольшой счетчик ссылок. Если вы не предпочитаете использовать static int для подсчета.. - person Brandon; 02.03.2014
comment
Итак, у меня все еще есть new int, но теперь я сам реализую std::shared_ptr? я что-то упускаю? - person qdii; 02.03.2014
comment
Хорошо, тогда вы можете просто сделать счетчик статическим, если вы так сильно ненавидите выделение памяти: ideone.com/Gs4Kb7 - person Brandon; 02.03.2014
comment
@JoelFalcou Может быть и так, но когда вы найдете лучший способ, кроме reinterpreting и int как void pointer, или создания new int в куче, чтобы вставляться в shared_ptr, как не хочет OP, дайте мне знать тогда. Я буду жить с отрицательными голосами. Не ахти какое дело. Прочитайте ответ над моим: Андре. Это хороший взгляд на исходную проблему. - person Brandon; 02.03.2014
comment
@CantChooseUsernames Я не проголосовал за вас;) Правильное решение - реорганизовать сам код, чтобы использовать std::shared_ptr‹file_descriptor› прямо. - person Joel Falcou; 02.03.2014