stream과 streambuf

stream과 streambuf

2023년 6월 16일

stream #

ios

스트림은 연속적인 데이터의 흐름을 말하며 데이터의 입력과 출력을 추상화하는 개념이다.
c++에서는 입력스트림(istream)과 출력스트림(ostream)을 제공하여 키보드, 네트워크, 파일, 메모리 등에서 데이터를 읽고 쓸 수 있다.

키보드의 입력을 받는 cin과 화면으로 출력하는 cout은 cerr, clog와 함께 iostream 헤더에서 글로벌로 빈 스트림 객체가 전방선언되지만 라이브러리 초기화 과정에서 표준 입출력과 스트림객체가 연결된다.
구현에 따라 다르지만, 보통 라이브러리의 초기화는 라이브러리 로딩 과정에서 진행된다.

stream객체는 streambuf의 포인터만 가지고 있으며 stream 파생클래스에서 streambuf 객체를 가지고있는다.
streambuf에서 파생된 버퍼객체에서 일정 크기의 버퍼를 만들고, 버퍼의 포인터를 초기화해둔다.
stream의 >>, << 연산은 streambuf의 get, put함수를 호출하여 버퍼에서 데이터를 가져오며 버퍼에서 읽을데이터가 없거나 가득찼을때 streambuf 파생객체에서 재정의한 overflow, underflow 함수를 호출하여 각 객체의 사용처에 맞게 입출력 데이터를 관리한다.


ios_base #

입력이나 출력, 템플릿타입에 무관하게 모든 stream의 객체의 공통적인 부분을 담고있다.

  1. 결국 스트림이라는건 바이너리(문자도 결국 바이너리) 데이터에 대해서 처리하는것이고, 입출력 데이터에 대한 서식에 대한 정보나 필드 너비, 정밀도 등을 플래그로 관리하게 된다.

  2. 이벤트 처리 함수를 등록해서 객체 복사, 생성, 파괴, 상태변경 때마다 호출되도록 콜백함수를 등록할 수 있다.

  3. 스트림에 원하는 데이터(int, void*)를 배열형태로 저장하고 꺼낼 수 있다.
    xalloc()을 호출하면 클래스변수의 카운트를 1 증가시키고 모든 클래스에 대해 인덱스가 1 증가하는 방식이기 때문에 모든 스트림에 공통적으로 저장해둘 컬럼이 추가되는것과 비슷하다고 보면 된다.

    상세보기: 모두코드

정의 #

  • 입출력 서식 플래그 (정렬, 정수출력서식 등)
  • 오픈 모드 (app, ate, bin, in, out, trunc …)
  • 스트림 상태 플래그 (good: 0, bad: 1, eof: 2, fail: 4)
  • 스트림 위치 (beg, end, cur)
  • 이벤트 (erase_event, imbue_event, copyfmt_event)

멤버 #

  • 포맷 플래그 (width, precisioan, flag)
  • locale (문자 인코딩 설정)
  • streambuf_state (스트림 상태 플래그)
  • words (iword, pword, word_size)
  • callback_list

메소드 #

  • 입출력 포맷 풀래그 관리 (flags, setf, unsetf, precision, width)
  • locale 관리 (getloc, setloc)
  • 내부변수 관리 (xalloc, pword, iword)
  • 이벤트 콜백 등록 (register_callback)
  • C 스트림 stdio와 동기화여부 지정 (sync_with_stdio)

ios (= basic_ios<char>) #

ios 클래스도 마찬가지로 입출력 스트림의 상위 클래스이며, 입출력에는 상관없이 템플릿 타입에 의존적인 멤버들이 정의되어 있다.
버퍼 포인터의 시작이기 때문에 버퍼에 대한 상태 비트도 관리할 수 있게 함수를 제공.
basic_ios의 char 특수화 버전인데, 앞으로 설명을 짧게 작성하기 위해 basic을 빼고 적을것이다.

  1. stream 상태 비트를 관리하는 함수. 웃긴건 관리하는 함수는 여기에 있는데, 상태비트의 정의는 ios_base에 있다.

  2. fill character: 필드 너비를 맞추기 위해 채워지는 문자 지정

  3. tied stream 포인터: 이 스트림 객체와 엮인 스트림의 포인터이며, tie() 메서드로 세팅하거나 얻을 수 있다.
    기본적으로 cin, cerr, clog는 cout에 엮여있고, wcin, wcerr, wclog는 wcout에 엮여있다.
    cout에 엮여있는 이유는 cin이 실행되기 전에 cout의 버퍼에 남아있는 데이터를 flush하기 위함이다.

  4. 스트림 버퍼 포인터: 스트림 데이터를 저장할 버퍼의 포인터를 가지고있다. rdbuf() 같은 메서드로 포인터를 가져올 수 있다.
    포인터이기 때문에 실제 스트림버퍼 객체는 아니고 그냥 모든 스트림에는 버퍼가 있어야 하기때문에 포인터만 정의되어 있으며, 객체를 따로 만들어서 연결하거나 상속받아서 구현 후 연결해야 한다.

 1template <class charT, class traits = char_traits<charT> >
 2class basic_ios : public ios_base {
 3public:
 4    typedef basic_streambuf<charT, traits> streambuf_type;
 5
 6    explicit basic_ios(basic_streambuf<_CharT, _Traits>* __sb)
 7        : ios_base(), _M_tie(nullptr), _M_fill(_CharT(' ')), _M_state_beg(), _M_exception_mask(0),
 8          _M_streambuf_state(0), _M_streambuf(nullptr), _M_word0(0), _M_word1(0), _M_showbase(false),
 9          _M_showpoint(false), _M_uppercase(false), _M_showpos(false), _M_skipws(false), _M_science(false),
10          _M_fixed(false), _M_unitbuf(false), _M_stdio(false), _M_pword(nullptr), _M_iword(0), _M_do_lock(true)
11    {
12        // ...
13        if (__sb)
14            this->rdbuf(__sb);
15        else
16            this->rdbuf(_Traits::new_streambuf(0));
17        // ...
18    }
19
20    void init(basic_streambuf<_CharT, _Traits>* __sb) {
21        // ...
22        this->_M_tie = nullptr;
23        this->_M_fill = _CharT(' ');
24        this->_M_do_lock = true;
25        if (__sb)
26            this->rdbuf(__sb);
27        else
28            this->rdbuf(_Traits::new_streambuf(0));
29        // ...
30    }
31
32
33private:
34    // streambuf 포인터를 저장하는 멤버 변수
35    streambuf_type* _M_streambuf;
36}

정의 #

멤버 #

  • 버퍼 포인터
  • 연결된 ostream 포인터
  • fill문자

메소드 #

  • 상태 체크 (good, eof, fail, bad, operator!, operator bool)
  • 상태 플래그 관리 (rdstate, setstate, clear)
  • 포맷 관리 (copyfmt, fill)
  • char문자를 locale에 맞게 인코딩, 디코딩 (widen, narrow)
  • tie
  • 버퍼 포인터 관리 (rdbuf)

istream, ostream, iostream (= basic_istream<char> …) #

ios 클래스를 상속받으며 실제 입력과 출력에 대한 메소드가 포함된다.
cin과 cout의 타입이며, 초기화 과정에서 글로벌로 스트림 선언후 stdin, stdout 에 대한 버퍼를 추가해서 화면, 키보드의 입출력이 가능한것이다.
iostream은 istream과 ostream을 가상상속받은 클래스이며 입출력 모두 가능한 클래스이다.

  1. istream은 >> 연산자로 스트림에서 데이터를 추출할 수 있고, ostream은 << 연산자로 stream에 데이터를 입력할 수 있게된다.
    이 연산자들은 인자로 전달되는 각 타입들에 따라 다른 서식으로 입출력하도록 오버로딩 되어있다.

  2. read(chars, cnt), get(char), getline(chars, cnt, delim), write(chars, cnt), put(char) 등의 메서드로 서식화되지 않은 원시데이터 입출력이 가능하다.

 1class basic_istream : virtual public basic_ios<_CharT, _Traits>
 2{
 3public:
 4    // istream에 >> 연산자로 __pf 함수 적용
 5    operator>>(__istream_type& (*__pf)(__istream_type&))
 6    { return __pf(*this); }
 7
 8    istream& get(char& c) {
 9        int ch = rdbuf()->sgetc();  // 현재 위치에서 문자를 하나 읽어들임
10        if (ch != EOF) {   // 읽어들인 문자가 없으면, EOF를 반환합니다.
11            c = static_cast<char>(ch);
12        } else {
13            setstate(eofbit);  // EOF 상태 플래그를 설정합니다.
14        }
15        return *this;
16    }
17}
18
19istream& getline(istream& is, std::string& str, char_type deliam = '\n') {
20    char c;
21    str.clear();
22    while (is.get(c)) {
23        if (c == delim) {
24            break;
25        }
26        str += c;
27    }
28    return is;
29}
30
31// 간단한 int형 operator>>
32istream& operator>>(istream& is, int& n) {
33    // 입력 스트림에서 공백 문자를 제외한 문자를 하나씩 읽어옵니다.
34    char c;
35    while (is.get(c) && std::isspace(c));
36    if (is.good()) {
37        // 숫자를 파싱합니다.
38        int sign = 1;
39        if (c == '-') {
40            sign = -1;
41            is.get(c);
42        }
43        n = 0;
44        while (std::isdigit(c)) {
45            n = n * 10 + (c - '0');
46            is.get(c);
47        }
48        // 숫자 뒤의 문자를 스트림으로 되돌립니다.
49        if (is.good()) {
50            is.putback(c);
51        }
52        n *= sign;
53    }
54    return is;
55}
 1ostream& operator<<(int n)
 2{
 3    const sentry ok(*this);
 4    if (ok)
 5    {
 6        const char __sign = (n < 0) ? ('-'/*-*/), n = -n : 0;// 부호를 코드값으로 변환
 7        const unsigned __base = 10;// 진법 결정
 8        char __digits[16 + 1];// 최대 16자리까지의 문자열 출력 예상
 9        char* __p = __digits + 16;
10        if (__sign)
11            --__p;
12
13        do {
14            *__--__p = static_cast<char>(n % __base + __widen('0'/*0*/));
15            n /= __base;
16        } while (n != 0);
17
18        //실행속도를 위해 미리 문자열 길이(크기)를 구해놓고 한 번의 출력
19        const streamsize __len = __digits + 16 - __p;// 문자열 길이 계산
20        if (rdbuf()->sputn(__p, __len) != __len)// 출력
21            setstate(badbit);
22    }
23    return *this;
24}

buffer #

ios 에서는 버퍼포인터로만 선언되어있고 버퍼는 따로 연결하게 되어있다.

basic_streambuf #

streambuf

버퍼를 관리하면서 입출력이 가능하도록 공통 인터페이스를 제공하는 추상클래스이다. 입력버퍼와 출력버퍼 포인터가 따로있고 setg, setp 메서드로 포인터를 세팅한다.
소스(키보드 등)에서 데이터를 읽어서 입력버퍼에 저장하고, ostream에서 대상(모니터 등)에 데이터를 쓰기전에 출력버퍼에 저장해둔다.
실제 버퍼는 파생클래스에서 한개만 선언하며, 버퍼 포인터의 초기화는 파생클래스 생성자에서 전달받은 인자(스트림모드)에 맞춰 초기화되고, 버퍼링 전략도 파생클래스에서 구현한다.

  1. 버퍼의 위치(시작, 현재, 끝) 포인터를 멤버로 가지고있고, 1~n문자의 데이터를 읽을때마다 버퍼의 포인터를 이동시키며 다음 데이터를 읽는 방식이라 데이터가 지워지진 않는다.

  2. 버퍼링 전략을 설정해서 라인별로 대상으로 전송하거나, 버퍼가 꽉찼을때 전송하거나, 버퍼없이 바로 전송할수도 있다. (virtual)

  3. 출력버퍼가 가득찼을때 overflow, 입력버퍼에서 읽을 데이터가 없을때 underflow 를 호출하게 되며 동작은 파생클래스에서 재정의해야한다. (virtual)

표준입출력 #

cout의 overflow는 flush해서 버퍼를 비우고 출력하고, cin의 underflow는 키보드입력이 스트림버퍼에 도착하기를 기다린다.

파일입출력 #

ofstream의 overflow도 flush해서 비우고, ifstream의 underflow는 파일에서 데이터를 가져와 버퍼를 채우는 역할을 한다.

 1template<typename _CharT, typename _Traits>
 2class basic_streambuf {
 3protected:
 4    // -  get == input == read
 5    // -  put == output == write
 6    char_type*        _M_in_beg;     // Start of get area. 
 7    char_type*        _M_in_cur;     // Current read area. 
 8    char_type*        _M_in_end;     // End of get area. 
 9    char_type*        _M_out_beg;    // Start of put area. 
10    char_type*        _M_out_cur;    // Current put area. 
11    char_type*        _M_out_end;    // End of put area.
12
13    void setg(char_type* __gbeg, char_type* __gnext, char_type* __gend) {
14        _M_in_beg = __gbeg;
15        _M_in_cur = __gnext;
16        _M_in_end = __gend;
17    }
18    void setp(char_type* __pbeg, char_type* __pend) {
19        _M_out_beg = _M_out_cur = __pbeg;
20        _M_out_end = __pend;
21    }
22
23public:
24    virtual int_type overflow(int_type /* __c */ = traits_type::eof())
25    { return traits_type::eof(); }
26
27    virtual int_type underflow()
28    { return traits_type::eof(); }
29
30    int_type sputc(char_type __c) {
31        if (__builtin_expect(this->pptr() < this->epptr(), true)) {
32            *this->pptr() = __c;
33            this->pbump(1);
34            return traits_type::to_int_type(__c);
35        }
36        return this->overflow(traits_type::to_int_type(__c));  // 버퍼가 꽉찬경우 overflow()
37    }
38
39    int_type sgetc() {
40        if (__builtin_expect(this->gptr() < this->egptr(), true)) 
41            return traits_type::to_int_type(*this->gptr());
42        return this->underflow();  // 버퍼의 데이터 읽기 끝에 도달한 경우 underflow()
43    }
44}

정의 #

멤버 #

  • 입력버퍼 포인터 (in_beg, in_cur, in_end)
  • 출력버퍼 포인터 (out_beg, out_cur, out_end)
  • locale

메소드 #

  • 입력버퍼 관리 (setg, gbump, eback(beg), gptr(cur), egptr(end))
  • 출력버퍼 관리 (setp, pbump, pbase(beg), pptr(cur), epptr(end))
  • 읽을 데이터 없는 경우 동작 (virtual underflow)
  • 출력버퍼가 꽉찬경우 동작 (virtual overflow)
  • locale 관리 (getloc, pubimbue, imbue)
  • 버퍼에 입출력 (sgetc, sgetn, sputc, sputn)

파생 클래스 #

stringstream #

stringstream iostream (basic_iostream<char>) 을 상속받아 문자열 관련 입력, 출력 스트림으로 사용할 수 있다.

<< 연산자로 문자열 스트림에 쓸 수 있고, >>연산자로 입력버퍼에서 원하는 서식에 맞게 데이터를 추출할 수 있다.

str(): 스트림에 있는 문자열을 꺼낼 수 있다.
str(const std::string& new_str): 스트림에 문자열 데이터를 세팅하면서 버퍼의 포인터도 초기화된다.

stringstream에서 출력버퍼가 eof에 도달하면, 더이상 입력이 되지 않는데, ss.clear(); 를 호출해서 eof 상태를 해제시켜야 한다.

 1#include <sstream>
 2#include <string>
 3using namespace std;
 4
 5int main()
 6{
 7    stringstream ss;
 8    string my_string = "10 20 30";
 9    int tmp;
10    ss << my_string;
11    while (ss >> tmp) {   // 추출할 데이터가 없는경우 false
12        cout << tmp;      // 102030 출력
13    }
14    return 0;
15}

ex2

 1// my_string = "_abc_d___ef_"
 2// ret : ["", "abc", "d", "", "" , "ef", ""]
 3vector<string> weird_split(string my_string) {
 4    vector<string> answer;
 5    stringstream ss(my_string);
 6    string data;
 7    
 8    while (getline(ss, data, '_')) {
 9        answer.push_back(data);
10    }
11    return answer;
12}

stringbuf #

 1/**** stringstream ****/
 2template<typename _CharT, typename _Traits, typename _Allocator>
 3class basic_stringstream : public basic_iostream<_CharT, _Traits>{
 4public:
 5    basic_stringstream(ios_base::openmode __mode = ios_base::out|ios_base::in)
 6        : basic_iostream<_CharT, _Traits>(&_M_stringbuf), _M_stringbuf(__mode)
 7    { }
 8
 9    explicit basic_stringstream(const basic_string<_CharT, _Traits, _Allocator>& __str,
10                                ios_base::openmode __mode = ios_base::out|ios_base::in)
11        : basic_iostream<_CharT, _Traits>(&_M_stringbuf), _M_stringbuf(__str, __mode)
12    { }
13
14private:
15    basic_stringbuf<_CharT, _Traits, _Allocator> _M_stringbuf; // 처리할 문자열 저장
16};
17
18
19/**** stringbuf ****/
20class simple_stringbuf : public std::streambuf {
21public:
22    // simple_stringbuf 생성자 예시
23    simple_stringbuf(const std::string& str, std::ios_base::openmode mode)
24        : m_str(str) {
25
26        if (mode & std::ios_base::in) {
27            setg(&m_str[0], &m_str[0], &m_str[0] + m_str.size());
28        }
29        if (mode & std::ios_base::out) {
30            setp(&m_str[0], &m_str[0] + m_str.size());
31        }
32    }
33
34private:
35    std::string m_str;   // 실제 데이터가 저장되는 버퍼
36};

ifstream, ofstream #

ifstream 파일 입출력을 위한 스트림이며 ifstream은 파일로부터 데이터를 읽어들이고, ofstream은 파일에 데이터를 쓰는작업을 한다.

open(const std::string& filename): 파일 스트림을 오픈
is_open(): 스트림이 열렸는지 확인
close(): 스트림 닫기

 1#include <fstream>
 2#include <iostream>
 3#include <string>
 4
 5int main() {
 6  std::ifstream fin;
 7  std::string filename = "input.txt";
 8  fin.open(filename);    // input.txt 파일 열기
 9
10  if (!fin) {             // 파일 열기에 실패한 경우
11    std::cout << "File open error" << std::endl;
12    return 1;
13  }
14
15  // 파일 한번에 읽어오기 
16  // std::stringstream buffer;
17  // buffer << input_file.rdbuf();
18  // std::string file_contents = buffer.str();
19  // std::cout << file_contents << std::endl;
20
21  std::string line;
22  while (std::getline(fin, line)) {
23    // 파일에서 한 줄씩 읽어서 처리
24    std::cout << line << std::endl;
25  }
26
27  fin.close();           // input.txt 파일 닫기
28}
 1#include <fstream>
 2#include <iostream>
 3#include <string>
 4
 5int main() {
 6  std::ofstream fout;
 7  std::string filename = "output.txt";
 8  fout.open(filename);   // output.txt 파일 열기
 9
10  if (!fout) {             // 파일 열기에 실패한 경우
11    std::cout << "File open error" << std::endl;
12    return 1;
13  } 
14
15  char ch = 'A';
16  fout.put(ch);           // 파일에 문자 쓰기
17
18  std::string str("Hello, world!");
19  fout.write(str.c_str(), str.size());  // 파일에 문자열 쓰기
20
21  fout.close();          // output.txt 파일 닫기
22}

filebuf #

 1/**** ifstream ****/
 2template<typename _CharT, typename _Traits>
 3class basic_ifstream : public basic_istream<_CharT, _Traits>
 4{
 5public:
 6    basic_ifstream() noexcept
 7        : basic_ifstream(nullptr, ios_base::in)
 8    { }
 9
10    // 파일명을 이용해 파일스트림 생성
11    explicit basic_ifstream(const char* __s,
12                            ios_base::openmode __mode = ios_base::in)
13        : basic_istream<_CharT, _Traits>(), _M_filebuf()
14    {
15        this->ios_base::init(&_M_filebuf);
16        _M_filebuf.open(__s, __mode | ios_base::in);
17    }
18
19private:
20    basic_filebuf<_CharT, _Traits> _M_filebuf;  // 입력 파일버퍼
21};
22
23
24/**** filebuf ****/
25template<typename _CharT, typename _Traits>
26class basic_filebuf : public basic_streambuf<_CharT, _Traits>
27{
28
29public:
30    explicit basic_filebuf(const char* file_name = nullptr,
31                           std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
32    : basic_streambuf<CharT, Traits>()
33    {
34        open(file_name, mode);
35    }
36
37    basic_filebuf* open(const char* __s, ios_base::openmode __mode) {
38        // _M_bufsize: 버퍼 메모리 크기를 설정하는 메소드
39        _M_bufsize = __filebuf_size;
40        // _M_buf: 실제 데이터가 저장되는 버퍼에 동적 메모리 할당
41        _M_buf = new char_type[_M_bufsize];
42        // 버퍼의 끝 포인터 설정
43        _M_buf_end = _M_buf + _M_bufsize;
44    }
45
46protected:
47    char_type* _M_buf;    // 실제 데이터가 저장되는 버퍼
48};
comments powered by Disqus