SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

よく使うC++のイディオム 「NVI」と「RAII」


RAII:Resource Acquisition Is Initialization

  RAII(Resource Acquisition Is Initialization)って、ご存じですか? 直訳すれば「リソースの確保は初期化時に」、実装寄りに意訳(?)すると「必要なリソースはコンストラクタで確保しとけ」ってテクニックですけど、これの裏というか対になるのが「不要になったリソースはデストラクタで解放(返却)しろ」です。

 開発屋さんからまたまた小さなツールの作成を頼まれました。「XMLの中から<log time="~" msg="~"/>という<log>要素を抽出しtime,msg属性値をcoutにカンマ区切りで出力してくれ」ってんです。どうやらXML内に点在するデバッグ用のデータをまとめてCSVで出力すればExcelで読めて楽じゃね?って魂胆らしい。

 実装にはXMLParserの定番:Apache Xerces-C++をつかいましょうか。今回の処理はXML要素の親子関係は無視し、特定の要素を抽出するだけなのでXMLの構造(階層)まるごとメモリに蓄積するDOMParserはオーバー・スペックと判断し、イベントドリブン型の軽量パーサ:SAXParserを使うことにしました。実装のアウトラインはこんな感じ:

list07
class SAXHandler : public xercesc::HandlerBase {
protected:
  virtual void startElement(const XMLCh* const name, xercesc::AttributeList& attributes) {
    /* 要素が見つかるたびにココに飛び込む。
       nameが"log"であったら
       attributesから time,msg属性を取り出し、
       カンマ区切りで出力する */
  }
};

int main(int argc, char* argv[]) {
  ...
  xercesc::SAXParser parser;
  SAXHandler handler;
  parser.setDocumentHandler(&handler); // 上記ハンドラをセットして
  parser.parse(argv[1]); // パース開始
  ...
}

 ここまでは何ということもありません。キモとなるのはSAXHandler::startElement()のナカミです。SAXParserが読み取ってstartElement()に渡してくれる要素名(name)の型はXMLCh*,属性を読み取るにはconst XMLCh* AttributeList::getValue(const XMLCh*)、なんにせよXMLChを相手にせにゃなりません。ここでXMLChとwchar_tが同じものならためらいなくstd::wstringに仲介してもらえます(Visual C++/Xerces-cでXMLを扱うときはそうしてました)。ところが今回のターゲットはLinux、sizeof(XMLCh)は2であるのに対しsizeof(wchar_t)は4、両者は別物のようで、std::wstring使って楽できそうにはありません。

 XercesのAPIを探ってみたところ、2つの変換関数:

  • char* xercesc::XMLString::transcode(const XMLCh*)
  • XMLCh* xercesc::XMLString::transcode(const char* )

を見つけました。これでなんとかなりそう...ただし、transcode()による変換で得られた文字列はヒープから確保したリソースであり、用が済んだら必ず xercesc::XMLString::release(XMLCh**) で返却すべし!との注意書きがありました。

 ともかく書いてみましょう:

list08
class SAXHandler : public xercesc::HandlerBase {
protected:
  virtual void startElement(const XMLCh* const name, xercesc::AttributeList& attributes) {
    char* c_name = xercesc::XMLSTring::transcode(name);
    std::string s_name(c_name);
    xercesc::XMLString::release(&c_name);
    if ( s_name == "log" ) {
      XMLCh* x_key = xercesc::XMLString::transcode("time");
      char* c_val = xercesc::XMLString::transcode(attributes.getValue(x_key));
      cout << c_val << ',';
      xercesc::XMLString::release(&x_key);
      xercesc::XMLString::release(&c_val);
      ....

 うわーめんどくせー…文字列使うたんびにrelease()せんならん、やってらんねーです。

 要らなくなったら勝手にまとめてrelease()してくれんもんじゃろか。

 ...そこでRAII、C++にはデストラクタという強い味方がいます。デストラクタで勝手にreleaseしてくれるヘルパーさんを作っちゃいました。

list09
class TranscodeContext {
public:
  ~TranscodeContext() {
    // 貸出簿に掲載されている全リソースを解放
    for ( XMLCh* x_str : xlog_ ) {
      xercesc::XMLString::release(&x_str);
    }
  }

  std::string operator()(const XMLCh* str) {
    char* c_str = xercesc::XMLString::transcode(str);
    std::string result(c_str);
    xercesc::XMLString::release(&c_str);
    return result;
  }

  const XMLCh* operator()(const char* str) {
    XMLCh* result = xercesc::XMLString::transcode(str);
    xlog_.push_back(result); // 貸出簿に載せる
    return result;
  }

private:
  std::vector<XMLCh*> xlog_; // 貸出簿
};

 要はTranscodeContextが変換したすべてのXMLCh*を記録に残しておき、デストラクト時にまとめて始末してもらうってスンポーです。

 TranscodeContext(と小さなlambda式)を使ってstartElement()を書き直しました。

list10
class SAXHandler : public xercesc::HandlerBase {
protected:
  virtual void startElement(const XMLCh* const name, xercesc::AttributeList& attributes) {
    TranscodeContext tc;
    auto attr = [&](const char* key) 
                   { return tc(attributes.getValue(tc(key))); };
    std::string c_name = tc(name);
    if ( c_name == "log" ) {
      std::cout << attr("time) << ',' << attr("msg") << std::endl;
    }
  }
}

 ...まぁ涼しい。やりたいことが明確でコンパクトなコードになりましたね。

 ──以上、小さな工夫で楽をするおはなし、2つまとめてお届けしました。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/7294 2013/08/26 14:31

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー