フィルタ系のプログラムの実装

すごーくひさしぶりに日記書いてみる。

cat, grepなど元のデータを加工して,データを出力するフィルタ系のプログラムを書く場合メモ。

1.入力が標準入力の場合とファイル入力の場合の切り替え

これらのプログラムで入力データを標準入力から得る場合と,ファイルから読み込む場合との両方を実装する場合を考えてみた。

filter < hoge.txt  # 標準入力からの読み込み  
filter hoge.txt    # ファイルからの読み込み

このように読み込み元が異なるが,処理自体は同じなのでコードを共通化したい。


C言語の場合(こちらのサイトから引用)

#include <stdio.h>
#include <stdlib.h>

....

char *filename;              /* NULL ならファイル指定なしとします */
FILE *fp;

....

if ( filename == NULL ) {    /* ファイル指定なし */
    fp = stdin;
} else {                     /* ファイル指定あり */
    fp = fopen( filename, "r" );
    if ( fp == NULL ) {
        fprintf( stderr, "'%s'が読み込めません.\n", filename );
        exit(1);             /* 異常終了 */
    }
}


C++の場合その1:入力ストリームを切り替え得るwrapper関数を作成する

#include <iostream>
#include <fstream>
#include <string>

// 読み込むべきストリームを切り替え得るwrapper
//   bool isStd     : 入力ストリームが、std::cin かファイルか指定する
//   ifstream& ifs : 入力ストリームへの参照
std::istream& pickInput( bool isStd, std::ifstream& ifs) {
  if (isStd) {
    return std::cin;
  } else {
    return ifs;
  }
}


int main (void) {
  std::ifstream ifs("hoge.txt"); 
  bool isStd;

  std::string line;

  isStd = true; // 標準入力から受け取る場合

  while (std::getline( pickInput(isStd, ifs), line)) {
    if (isStd) {
      std::cout << "cin : " << line << std::endl;
    } else {
      std::cout << "ifs : " << line << std::endl;
    }
  }

  return 0;
}


C++の場合その2:istream型のポインタ変数を利用

ifstream型変数もstd::cinもistreamクラスを継承していることに着目し,istream型のポインタ変数をもちいて,ifstream型変数,std::cinを切り替える。

#include <iostream>
#include <fstream>
#include <string>


int main (void) {
  std::ifstream ifs("hoge.txt"); 
  std::istream* ptrIs; 
  bool isStd;

  std::string line;

  isStd = true;
  if (isStd) {
    ptrIs = &std::cin;
  } else {
    ptrIs = &ifs;
  } 

  while (std::getline( *ptrIs, line)) {
    if (isStd) {
      std::cout << "cin : " << line << std::endl;
    } else {
      std::cout << "ifs : " << line << std::endl;
    }
  }

  return 0;
}

ここで,ポインタ変数でなく参照変数を利用したくなるが,コンパイルエラーになる。*1

#include <iostream>
#include <fstream>
#include <string>


int main (void) {
  std::ifstream ifs("hoge.txt"); 
  std::istream& refIs = std::cin; 

  bool isStd;

  std::string line;

  isStd = true;
  if (!isStd) {
    refIs = ifs;   // compile error
  }

  while (std::getline( refIs1, line)) {
    if (isStd) {
      std::cout << "cin : " << line << std::endl;
    } else {
      std::cout << "ifs : " << line << std::endl;
    }
  }

  return 0;
}


rubyの場合

ARGF.each_line do |line|
  filename = ARGF.filename
  lineno = ARGF.file.lineno
  puts "#{filename}:#{lineno}:#{line}"
end

*1:参照変数の初期化は可能だが,代入は不可能になる。原因はよくわからんけど,これと同じような理由?→そもそも参照変数は初期化のみ可ですね。