C++/STLでCSVファイルの読み込み

久しぶりのC++/STLネタということで、CSVファイルの読み込みです。
もともと、Apacheが出力するアクセスログの読み込みの為に作ったのですが、せっかくなので、Excelが出力するCSVにも対応しました。
CSVファイルの読み込みは他の言語ならsplit一発でお茶を濁すのですが、カンマ(,)やダブルクオート(")、改行があるデータにも対応してます。

/**********************************************************************
 CSVファイルの読み込みサンプル
 試したコンパイル環境
    VC++ .NET 2003 / WINDOWS XP Professional 64 bit edition.
    GCC C++ 3.3.6 / glibc 2.3.4 / Vine Linux 4.2
**********************************************************************/

#include <functional>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <vector>
#include <string>

/**********************************************************************
 クオート文字の定義構造体
**********************************************************************/
struct quote{
    char    start;  // 開始文字
    char    end;    // 終了文字
    char    escape; // エスケープ文字
    quote( char start_, char end_, char escape_) :
         start(start_), end(end_), escape(escape_) {}
};

struct cmpStartQuote : public std::binary_function<quote,char,bool> {
    bool operator()(const quote &l_s1, const char &l_s2) const {
        return l_s1.start == l_s2;
    }
};

/**********************************************************************
 CSVの読取
**********************************************************************/
bool readCSV(
    std::vector< std::string > &result, // 結果格納ベクター
    std::istreambuf_iterator<char> &i,  // 入力文字イテレータ
    std::string &separator,             // 区切り文字
    std::vector<quote> &quotes)         // クオート文字ベクター
{
    bool retval = false;
    result.clear();
    std::string item;
    char    bc = '\0';
    bool    first = true;
    std::vector<quote>::iterator    ff = quotes.end();

    while ( i != std::istreambuf_iterator<char>() &&
                    (ff != quotes.end() || *i != '\n') ) {
        if ( ff == quotes.end() &&
                            strchr( separator.c_str(), *i) != 0 ) {
            result.push_back(item);
            item.clear();
            first = true;
            retval = true;
        } else {
            if ( first &&
                (ff = find_if( quotes.begin(),
                            quotes.end(),
                            bind2nd( cmpStartQuote(), *i)))
                    != quotes.end() ) {
                first = false;
            } else if ( ff != quotes.end() && ff->end == *i ) {
                if ( ff->end == ff->escape ) {
                    bc = *i++;
                    if ( i == std::istreambuf_iterator<char>() )
                        break;
                    if ( *i == ff->end ) {
                        item.push_back(*i);
                    } else {
                        ff = quotes.end();
                        continue;
                    }
                } else {
                    if ( bc == ff->escape ) {
                        item.push_back(*i);
                    } else {
                        ff = quotes.end();
                    }
                }
            } else {
                item.push_back(*i);
                first = false;
            }
        }
        bc = *i++;
    }
    result.push_back(item);
    if ( i != std::istreambuf_iterator<char>() ) {
        retval = true;
        i++;
    }
    return retval;
}

using namespace std;


int main(int argc, char* argv[])
{
    basic_ifstream<char>        inputFile("access_log");
    istreambuf_iterator<char>   sin(inputFile);
    vector< string >            rec;

    // APACHEのアクセスログ
    string                      separator(" ");
    vector< quote >             quotes;

    // クオート文字(")の設定("を表示したいときは\"になる)
    quotes.push_back( quote( '"', '"', '\\') );
    // 時間部分のクオート文字([]で括る)
    quotes.push_back( quote( '[', ']', 0 ) );

    while ( readCSV( rec, sin, separator, quotes) ) {
        vector< string >::iterator i;
        for ( i = rec.begin(); i < rec.end(); i++ ) {
            cout << i - rec.begin() << ":" << *i << endl;
        }
        cout << endl;
    }

    return 0;
}
 
上記コードは、Apacheのログを読み込むサンプルで、CSVファイルの読み込みの場合は、
separatorとquotesの部分が、
 
    string                      separator(",");
    vector< quote >             quotes;

    quotes.push_back( quote( '"', '"', '"') );
 
になります。

2008-08-04 | コメント:0件



コンテナの要素の参照は危険

C++/STLで、ついついやってしまうミスに、コンテナの要素への参照を保存するがある。
最近、このミスをやってしまったので覚書にしておく。
要するに、コンテナに値を追加・削除するとイテレーターが無効になる、と同じことなのだが、参照だとついついやってしまいます。

ちなみに、このバグはコンテナの領域が再配置されない限り発生しないので、reserveメソッドを使用して適当な領域を確保するとデバッグが困難になるので、マジで注意が必要です。

以下、バグの再現コードです。


#include <iostream>
#include <vector>

using namespace std;

int main(void)
{
    vector<int> array1;
    array1.reserve(10);
    for ( int i = 0; i < array1.capacity(); i++ ) {
        array1.push_back(i);
    }

    int &last_value = array1.back();    // コンテナの要素の参照を得る
    cout << "last_value: " << last_value << endl;

    array1.push_back(10);               // ここでlast_valueは無効になる
    cout << "last_value: " << last_value << endl;

    return 0;
}

実行結果
VC++ .NET 2003 / WINDOWS XP Professional 64 bit edition(32ビット環境で作成・実行)


last_value: 9
last_value: -572662307
2008-05-21 | コメント:0件



C++/STLの例外

C++の例外も、やはり避けて通っていたのだが、ちょっと調べて見ることにした。
以下、Visual C++ .NET 2003の調査結果の覚書。

例外をcatchしないとどうなるか?

例外をcatchしないとVC++.NET2003(おそらくそれ以降でも)
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
と出る。デバッグ版のライブラリをリンクしていると上記メッセージの前にダイアログが出てそのままデバッグできる。

C++の例外ってどういったものがあるか?


bad_alloc new演算子等でメモリを確保できないときに発生。vector等のコンテナでもメモリ確保に失敗すると発生する。
bad_cast dynamic_castキャストに失敗したときに発生、参照型で発生する。ポインタ型では発生しない。ポインタ型のキャストに失敗すると0(ヌルポインタ)が返る。
bad_typeid typeid演算子で無効なオブジェクトを渡すと発生する。
bad_exception 例外仕様に反した例外が発生した場合、プログラムは強制終了(terminateの呼び出し)されるが、bad_exceptionに変換する作法(?)がある。VC++ .NET 2003では正常に変換できなかった。


logic_error プログラムの論理エラー(回避可能なエラー)、以下のエラーの親(スーパークラス)になっている。
  
  • domain_error 領域エラー、VC++.NET 2003のSTLでこのエラーがスローされることはない模様。
  • invalid_argument 不正な引数、bitsetの初期化エラーでスローされる。
  • length_error 長さエラー、コンテナ(vector,deque,list,string等)でmax_size以上の領域を確保(reserve,insert)しようとしたら発生。max_sizeメソッドは各コンテナで扱うことの出来る要素の最大数を指す、実際に確保できるサイズはこの値未満になる。
  • out_of_range 範囲外、コンテナ(vector,deque等)のatメソッドの範囲チェックに引っかかった。

runtime_error 実行時エラーの総称、localeクラスのcombineテンプレートメソッドでスローされる。以下のエラーの親になっている。
  • underflow_error アンダーフロー、VC++.NET 2003のSTLでこのエラーがスローされることはない模様。
  • overflow_error オーバーフロー、bitsetのto_ulongメソッドでスローされる。
  • range_error 範囲エラー、VC++.NET 2003のSTLでこのエラーがスローされることはない模様。
  •    
  • ios_base::failure iostream関連の例外、basic_ios::exceptionsメソッドにより例外の発生をコントロールできる。


例外のサンプルコード

以下、各例外を検証するコードです。
#include <iostream>
#include <string>
#include <new>
#include <eh.h>

using namespace std;


/**********************************************************************
    bad_alloc
**********************************************************************/
int test_bad_alloc()
{
    int ret = 0;

    try {
        int *ptr = new int[511*1024*1024UL];
        if ( ptr == 0 ) {
            ret = 1;
        }
    } catch ( bad_alloc e) {
            ret = 2;
    } catch ( ... ) {
            ret = 3;
    }
    return ret;
}

/**********************************************************************
    bad_cast
    VC 2003 ランタイム型情報を有効にしないとコンパイルできない
**********************************************************************/
    class base_class {
        public:
        virtual void vfunc() const {}
    };

    class child_class: public base_class {
        public:
        virtual void virtualfunc() const {}
    };

int test_bad_cast_ref()
{
    int ret = 0;
    base_class base;
    base_class &ref_base = base;
    try {
        child_class& ref_child = dynamic_cast<child_class&>(ref_base);
        ret = 1;
    } catch (bad_cast) {
        ret = 2;
    } catch ( ... ) {
        ret = 3;
    }
    return ret;
}

int test_bad_cast_ptr()
{
    int ret = 0;
    base_class base;
    base_class *ref_base = &base;
    try {
        child_class *ref_child = dynamic_cast<child_class*>(ref_base);
        ret = 1;
    } catch (bad_cast) {
        ret = 2;
    } catch ( ... ) {
        ret = 3;
    }
    return ret;
}



int test_bad_typeid()
{
    int ret = 0;
    base_class  *b = 0;

    try {
        const type_info &t = typeid(*b);
        cout << t.name();
        ret = 1;
    } catch ( bad_typeid ) {
        ret = 2;
    }
    return ret;
}

/**********************************************************************
    bad_exception
**********************************************************************/
void func() throw()
{
    // warning C4297が出る(わざと例外仕様に反した例外を出す)。
    throw "bad_exception!";
}

void convert_unexpected()
{
    throw ;
}

int test_bad_exception()
{
    int ret = 0;

    // VC++ .NET 2003 SP1 で最適化を掛けるとconvert_unexpected関数が呼ばれない
    // VC++ .NET 2008 Express Edition でも同様
    unexpected_function back = set_unexpected(convert_unexpected);

    try {
        func();
        ret = 1;
    } catch ( char * ) {
        ret = 2;
    } catch ( bad_exception ) {
        ret = 3;
    } catch ( ... ) {
        ret = 4;
    }

    set_unexpected(back);
    return ret;
}

/**********************************************************************
    null pointer assigment
**********************************************************************/
int test_null_pointer_assigment()
{
    int ret = 0;

    // VC++ .NET 2003 SP1 で最適化を掛けるとcatch(...)に行かない。
    // _set_se_translatorを使って構造化例外処理からC++例外処理へ
    //  例外をコンバートできる
    try {
        char *ptr = 0;
        *ptr = '\a';
        ret = 1;
    } catch ( ... ) {
        ret = 3;
    }
    return ret;
}

int main(int argc, char* argv[])
{
    cout << "test_bad_alloc:" << test_bad_alloc() << endl;
    cout << "test_bad_cast_ref:" << test_bad_cast_ref() << endl;
    cout << "test_bad_cast_ptr:" << test_bad_cast_ptr() << endl;
    cout << "test_bad_typeid:" << test_bad_typeid() << endl;
    cout << "test_bad_exception:" << test_bad_exception() << endl;
    cout << "test_null_pointer_assigment:" << test_null_pointer_assigment() << endl;
    return 0;
}

コンパイル、実行結果

C:\>cl /GR /GX cpp_exception.cpp
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 13.10.6030 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.

cpp_exception.cpp
cpp_exception.cpp(98) : warning C4297: 'func' : 例外をスローしないはずだがそれを

する関数。
        __declspec(nothrow) または throw() が関数で指定されました。
Microsoft (R) Incremental Linker Version 7.10.6030
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:cpp_exception.exe
cpp_exception.obj

C:\>cpp_exception
test_bad_alloc:2
test_bad_cast_ref:2
test_bad_cast_ptr:1
test_bad_typeid:2
test_bad_exception:2
test_null_pointer_assigment:3
2008-04-30 | コメント:0件



C++/STLでODBC

C++でDBへアクセスするにはMFCだの、ADOだのややこしいライブラリをリンクするか、黙ってCで記述(各DBのライブラリを直接呼び出すコードを記述)するか、マネージドコードの仲間入りになるかになる。
最近流行りの言語(PHPとか)はあっさりDBをサポートしているのにC++/STLでさくっとSQLを書きたい場合、結構骨が折れる。
と言う訳で、なんちゃってodbcクラスを作りました。
以下のようにSQLが発行できちゃいます。


  db, "INSERT INTO test( c1, c2) VALUES(?,?)", "test", 100, endsql;

ぱっと見、何がなんだか分からないかもしれませんが、キモはSQL文に続けてパラメータが記述できる点で、結構楽にSQLが発行できます。
(見る人が見れば凶悪な演算子のオーバーロードに見えるかもしれないが・・・)
ちなみに、様々なDBに対応する為と、Linuxへの移植性を考えてODBCにしました。

ダウンロード(2011/04/15 SourceForgeにプロジェクトを作成しました)
2008-03-11 | コメント:5件



C++/STLで正規表現

今まで正規表現を使いたい場合、とっとと別の言語に切り替えていた私ですが、Boostというライブラリの正規表現パッケージを使ってみました。Boostのサイトにはサンプルはあるのですが、C++/STLでさくっと正規表現を使うにはイマイチ感があったので、覚書ということでサンプルのせます。ちなみに、Boostのインストール等の情報はLet's Boostがお勧めです。
若干ですがコードの説明を、サンプルはHTTPのレスポンスヘッダ(WEBサーバーからの戻りヘッダ)からCookie情報を取得するものです。関数 boost::regex_search が正規表現を使って文字列の検索を行っています。
その他、正規表現の利用法として入力値のチェックもあるかと思います。その場合、boost::regex_matchという関数が使えます。

#include <boost/regex.hpp>
#include <iostream>
int main(int , char* [])
{
  // 検索対象文字列
  std::string str( "HTTP/1.1 200 OK\r\n"
  "Server: Apache\r\n"
  "Set-Cookie: NAME1=VAL1\r\n"
  "Set-Cookie: NAME2=VAL2; expires=Mon, 18-02-2008 23:30:45 GMT\r\n"
  "Set-Cookie: NAME3=VAL3; path=path\r\n"
  "Set-Cookie: NAME4=VAL4; path=path; domain=example\r\n"
  "Set-Cookie: NAME5=VAL5; path=path; domain=example; secure\r\n"
  "Connection: close\r\n"
  "Content-Type: text/html\r\n");
  // 正規表現
  boost::regex r(
    "^Set-Cookie:\\s*([^;$\\s]+);?(?:\\s*expires=([^;$]+);?)?"
    "(?:\\s*path=([^;$\\s]+);?)?(?:\\s*domain=([^;$\\s]+);?)?"
    "\\s*(secure)?\\s*$"
    );
  boost::smatch what;  // マッチ文字列参照オブジェクト
  std::string::const_iterator start = str.begin();
  std::string::const_iterator end = str.end();
  // 検索対象の文字列から正規表現にマッチする文字列(複数有)を取り出す
  while ( boost::regex_search(start, end, what, r) ) {
    // 取得した文字列をstringに代入する(smatchにはイテレーターが入る)
    std::string cookie(what[1].first, what[1].second);
    std::string expires(what[2].first, what[2].second);
    std::string path(what[3].first, what[3].second);
    std::string domain(what[4].first, what[4].second);
    std::string secure(what[5].first, what[5].second);
    std::cout << cookie << ":" << expires << ":" << path << ":"
              << domain << ":" << secure  << ":" << std::endl;
    start = what[0].second;  // 次のマッチ文字列を取得する
  }
   return 0;
}
2008-02-20 | コメント:0件
Previous Page | Next Page