ファイルディスクリプタを C++のストリームに変換して利用する

GCC専用ではあるが、open(2)で開いたファイルもストリームとして扱う方法が実はある

2021年9月20日 嶋田大貴

C++の入門記事でよくある

std::cout << "Hello, World!" << std::endl;

みたいな I/Oストリームを使用した記述は C から C++への移民にとっては「なんじゃこりゃ!!」と言いたくなるようなものだが、Cだと常にバッファ境界のことを念頭に置きながら書かなければならなかった I/O処理の記述が C++だとだいたいの場合簡潔にできるので(メモリの豊富な実行環境がターゲットなら)これを使わない手はない。

とはいえ、Cで書かれている部分との兼ね合いで生のファイルディスクリプタしか手に入らない場合もある。(GCCを使用しており移植性を考えなくて良い状況なら)下記のように __gnu_cxx::stdio_filebuf を利用すればファイルディスクリプタを I/Oストリームで覆って C++の作法で利用することができる。また、生のファイルディスクリプタ以外に fopen(3)した FILE* を取るコンストラクタもあるようだ。(試していない)

stdio_filebufのデストラクタで close(2) が呼ばれるようなので、クローズせずに返却しなければならないファイルディスクリプタに対しては使わないこと。

#include <fcntl.h>
#include <unistd.h>

#include <iostream>
#include <fstream>
#include <ext/stdio_filebuf.h> // for __gnu_cxx::stdio_filebuf

int main()
{
    int fd = open("/etc/hostname", O_RDONLY);
    if (fd < 0) throw std::runtime_error("open(/etc/hostname) failed");

    std::string hostname;

    {
        __gnu_cxx::stdio_filebuf<char> filebuf(fd, std::ios::in);
        std::istream f(&filebuf);

        f >> hostname;
        // filebuf's scope ends here
    }

    std::cout << hostname << std::endl;

    // check whether fd has been closed
    if (close(fd) && errno == EBADF) {
        std::cout << "fd already closed(probably by stdio_filebuf's destructor)" << std::endl;
    }

    return 0;
}

2021年9月20日 嶋田大貴

記事一覧へ戻る