2016-04-27 2 views
0

Я реализую свой собственный streambuffer для выходного потока. В основном это векторный потоковый буффер, в котором каждый раз функция переполнения просто перераспределяет буфер в два раза больше. Функция синхронизации будет записывать все данные на устройство, указанное файловым дескриптором fd.Настроить streambuffer для C++ ostream

class MyStreamBuf : public ::std::streambuf { 

    constexpr static size_t INIT_BUFFER_SIZE {1024}; 

    public: 

    MyStreamBuf(); 
    ~MyStreamBuf(); 

    void fd(const int); 

    int sync() override; 
    int_type overflow(int_type ch = traits_type::eof()) override; 

    private: 

    int _fd {-1}; 
    size_t _size; 
    char_type* _base;  
    void _resize(const size_t); 
}; 


MyStreamBuf::MyStreamBuf() { 
    _size = INIT_BUFFER_SIZE; 
    _base = static_cast<char_type*>(malloc(_size * sizeof(char_type))); 
    setp(_base, _base + _size - 1); // -1 to make overflow easier. 
} 

// Destructor. 
MyStreamBuf::~MyStreamBuf() { 
    ::free(_base); 
} 

// Procedure: fd 
// Change the underlying device. 
void MyStreamBuf::fd(const int fd) { 
    _fd = fd; 
} 

// Procedure: _resize 
// Resize the underlying buffer to fit at least "tgt_size" items of type char_type. 
void MyStreamBuf::_resize(const size_t tgt_size) { 

    // Nothing has to be done if the capacity can accommodate the file descriptor. 
    if(_size >= tgt_size) return; 

    // Adjust the cap to the next highest power of 2 larger than num_fds 
    for(_size = (_size ? _size : 1); _size < tgt_size; _size *= 2); 

    // Adjust and reset the memory chunk. 
    _base = static_cast<char_type*>(::realloc(_base, _size*sizeof(char_type))); 

    setp(_base, _base + _size - 1); // -1 to make overflow easier. 
} 

int MyStreamBuf::sync() { 

    int res = 0; 

    ::std::ptrdiff_t remain = pptr() - pbase(); 

    while(remain) { 

    issue_write: 
    auto ret = ::write(_fd, pptr() - remain, remain); 

    if(ret == -1) { 
     if(errno == EINTR) { 
     goto issue_write; 
     } 
     else if(errno == EAGAIN) { 
     break; 
     } 
     else { 
     res = -1; 
     break; 
     } 
    } 
    remain -= ret; 
    } 

    if(remain) { 
    ::memcpy(pbase(), pptr() - remain, remain*sizeof(char_type)); 
    } 
    pbump(pbase() + remain - pptr()); 

    return res; 
} 

typename MyStreamBuf::int_type MyStreamBuf::overflow(int_type ch) { 
    assert(traits_type::eq_int_type(ch, traits_type::eof()) == false); 
    _resize(_size * 2); 
    return ch; 
} 

Однако я получаю Segfault при замене cout с моим собственным буфером. Я не мог найти, где ошибка после борьбы с GDB.

// Function: main 
int main() { 

    auto fd = open("./test.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); 

    MyStreamBuf d; 

    d.fd(fd); 

    ::std::cout.rdbuf(&d); 

    ::std::cout << 1 << " " << 2 << ::std::endl; 

    close(fd); 

    return 0; 
} 

Есть ли что-то не так с этой реализацией? Я видел много статей, обычно переопределяющих sync и overflow.

ответ

1

Проблемы, кажется, что ваш объект d будет уничтожен до std::cout, и, таким образом, конечных вызовов для разрушающих глобального объекта, которые включают в себя промывочные буфера, и которые принимают Palce после окончания main() (помните, что это глобальный объект), попытайтесь выполнить операции с более не существующим streambuf объектом. Ваш буферный объект определенно должен пережить поток, с которым вы его связываете.

Один из способов иметь это в вашей программе - сделать d указателем, который вы никогда не удалите. Кроме того, вы можете сохранить свой локальный объект, как вы его использовали, но назовите std::cout.flush(), а затем назначьте буфер cout на что-то еще (даже nullptr) до выходит из сферы действия.

Во время тестирования с вашей программой (и до того, как я нашел проблему), я сделал небольшие изменения, которые имели смысл для меня. Например, после успешной записи в дескриптор, вы можете просто bump(ret) (вы уже знаете, что ret!=-1, поэтому его можно использовать).

Других изменений, которые я не делаю, но вы могли бы рассмотреть, должны иметь дескриптор устанавливается сам конструктором, имеющий деструктор закрыть оборванный дескриптор, и, возможно, изменить динамическое выделение из C-ориентированных malloc()/realloc()/free() - C++ - ориентированный std::vector.

Говоря о распределении, вы сделали очень распространенную ошибку при использовании realloc(). Если перераспределение не удалось, realloc() сохранит исходный указатель без изменений и сообщит об ошибке, указав нулевой указатель. Поскольку вы используете один и тот же указатель для получения возвращаемого значения, вы рискуете потерять ссылку на все еще выделенную память. Итак, если вы вообще не можете использовать контейнеры C++ вместо указателей C, вам следует изменить код на что-то еще:

char *newptr; 
newptr=static_cast<char *>(realloc(ptr, newsize)); 
if(newptr) 
    ptr=newptr; 
else { 
    // Any treatment you want. I wrote some fatal failure code, but 
    // you might even prefer to go on with current buffer. 
    perror("ralloc()"); 
    exit(1); 
} 
Смежные вопросы