2013/06/27


先日twitterで「C++でデバッグする時、よくやるよね」って言ったら結構知らない人がいたのでここでも紹介してみる。
既存のコードでcout/cerrを使ったデバッグ文がわんさかあって、これログファイルとして出力したいな...って場合ありますよね。
そんな場合
#include <iostream>
#include <fstream>

using namespace std;

int main() {
    // こんなの
    ofstream ofs("debug.log");
    cout.rdbuf(ofs.rdbuf());
    // いれとく

    cout << "debug string" << endl;
}

こうしておくと、その後のcoutへの出力が全てdebug.logというファイルへ出力される。
なおrdbufを元に戻すには #include <iostream>
#include <fstream>

using namespace std;

int main() {
    ofstream ofs("debug.log");
    streambuf* oldrdbuf = cout.rdbuf(ofs.rdbuf());

    cout << "output to file" << endl;

    cout.rdbuf(oldrdbuf);

    cout << "output to console" << endl;
}
こうやる。
ちなみに、昔のC言語使いのオッサン達はこうやる。
freopen("file.txt""w"stdout);
これをもう少し発展させて、coutへの出力をログファイルぽくする物を先日書いたので紹介してみる。
/* Copyright 2011 by Yasuhiro Matsumoto
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <streambuf>
#include <sstream>
#include <ctime>

template <class CharT, class Traits = std::char_traits<CharT> >
class basic_clogger : public std::basic_streambuf<CharT, Traits> {
public:
  typedef std::basic_streambuf<CharT, Traits> streambuf_type;
  typedef Traits traits_t;
  typedef CharT char_t;
  typedef typename traits_t::int_type int_t;
  typedef std::streamsize streamsize_t;
  typedef std::basic_stringstream<char_t, traits_t> sstream_t;

private:
  streambuf_type* sbuf_;
  char_t* cbuf_;
  static int const BUFFER_SIZE = 256;

public:

  basic_clogger(streambuf_type* buf) :sbuf_(buf),
      cbuf_(new char_t[BUFFER_SIZE]) {
    this->setp(cbuf_, cbuf_ + BUFFER_SIZE);
  }

  ~basic_clogger() {
    this->pubsync();
    delete[] cbuf_;
  }

protected:

  int_t
  overflow(int_t c = traits_t::eof()) {
    streamsize_t n = static_cast<streamsize_t>(this->pptr() - this->pbase());
    time_t ct;
    int year, month, day, hour, minute, second;
    time(&ct);
    struct tm* d = localtime(&ct);
    std::basic_stringstream<char_t, traits_t> ss;

    for (streamsize_t i = 0; i < n; i++)
      if (cbuf_[i] == '\n') { n = i; break; }
    if (n) {
#define CLOGGER_PAD4(o,d) if(d<10)o<<'0'<<'0'<<d; else if (d<100)o<<'0'<<d; else o<<d;
#define CLOGGER_PAD2(o,d) if(d<10)o<<'0'<<d; else o<<d;
      CLOGGER_PAD4(ss, d->tm_year + 1900);
      ss << '/';
      CLOGGER_PAD2(ss, d->tm_mon + 1)
      ss << '/';
      CLOGGER_PAD2(ss, d->tm_mday + 1)
      ss << ' ';
      CLOGGER_PAD2(ss, d->tm_hour)
      ss << ':';
      CLOGGER_PAD2(ss, d->tm_min)
      ss << ':';
      CLOGGER_PAD2(ss, d->tm_sec)
#undef CLOGGER_PAD4
#undef CLOGGER_PAD2
      ss << '\t';
      ss << std::basic_string<char_t>(cbuf_, n) << std::endl;
      std::basic_string<char_t> s = ss.str();
      if (sbuf_->sputn(s.c_str(), s.size()) != s.size())
        return traits_t::eof();
      this->setp(cbuf_, cbuf_ + BUFFER_SIZE);
    } 

    if (!traits_t::eq_int_type(c, traits_t::eof())) {
      traits_t::assign(*this->pptr(), traits_t::to_char_type(c));
      this->pbump(1);
    } 
    return traits_t::not_eof(c);
  }

  int
  sync() {
    if (traits_t::eq_int_type(overflow(traits_t::eof()), traits_t::eof())) {
      return -1;
    }
    return sbuf_->pubsync() == -1 ? -1 : 0;
  }
};

typedef basic_clogger<char> clogger;
ちょっと長ったらしいけど...。これを以下の様にして使う。
#include <iostream>
#include <fstream>
#ifdef _WIN32
#include <windows.h>
#define sleep(x) Sleep(x*1000)
#else
#include <unistd.h>
#endif

int
main() {
  std::ofstream file("debug.log", std::ios::out | std::ios::app);
  clogger logger(file.rdbuf());
  std::streambuf* oldrdbuf = std::cout.rdbuf(&logger);

  std::cout << "Yes" << std::endl;
  sleep(1);
  std::cout << "高洲" << std::endl;
  sleep(1);
  std::cout << "クリニック" << std::endl;

  std::cout.rdbuf(oldrdbuf);
}
するとdebug.logというファイルに 2011/04/03 01:26:09 Yes
2011/04/03 01:26:10 高洲
2011/04/03 01:26:11 クリニック
こう出力される。

Yes 高須クリニック!


良いC++デバッグライフをお送り下さい。
Posted at by



2013/05/28


なんびとたりとも俺の前は走らせねぇ
ガチバトルです。
rapidjson - A fast JSON parser/generator for C++ with both SAX/DOM style API - Google Project Hosting

Rapidjson is an attempt to create the fastest JSON parser and generator. Small but complete. Support...

https://code.google.com/p/rapidjson/
fastest をうたうとは度胸があるなーと思いながら、半信半疑で試してみました。
rapidjson も picojson 同様に、ヘッダファイルだけあればコンパイル出来る C++ 向け JSON パーサ(およびシリアライザ)です。
まずは rapidjson を使ったコード。
#include <rapidjson/document.h>
#include <string>
#include <sstream>
#include <fstream>
#include <iostream>
#include <time.h>

int
main(int argc, char* argv[]) {
  clock_t t;
  t = clock();
  for (int n = 0; n < 1000; n++) {
    std::stringstream ss;
    std::ifstream f;
    f.open("foo.json", std::ios::binary);
    ss << f.rdbuf();
    f.close();

    rapidjson::Document doc;  
    if (doc.Parse<0>(ss.str().c_str()).HasParseError()) {
      std::cerr << "parse error" << std::endl;
      return 1;
    }

    rapidjson::Value& entries = doc["entries"];
    int i, l = entries.Size();
    for (i = 0; i < l; i++) {
      std::cerr << entries[rapidjson::SizeType(i)]["title"].GetString() << std::endl;
    }
  }
  std::cout << "score: " << clock() - t << std::endl;
  return 0;
}
そして picojson を使ったコード #include <picojson.h>
#include <string>
#include <sstream>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <time.h>

int
main(int argc, char* argv[]) {
  clock_t t;
  t = clock();
  for (int n = 0; n < 1000; n++) {
    std::stringstream ss;
    std::ifstream f;
    f.open("foo.json", std::ios::binary);
    ss << f.rdbuf();
    f.close();

    picojson::value v; ss >> v;
    picojson::array a = v.get<picojson::object>()["entries"].get<picojson::array>();
    int i, l = a.size();
    for (i = 0; i < l; i++) {
      std::cerr << a[i].get<picojson::object>()["title"].to_str() << std::endl;
    }
  }
  std::cout << "score: " << clock() - t << std::endl;
  return 0;
}
picojson については、std::for_each を使っても見たけど結局自前ループで回した方が速い結果になりました。

コンパイルは両者とも g++ -std=c++11 -O2 foo.cxx でやりました。
読み込んでる JSON はこれを使いました。ウチのサイトの JSON 形式のフィードです。やってる内容は
  • ファイルを読み込む
  • パースする
  • entries の一覧を取る
  • 各 title の内容を標準エラーに出力する
これを1000回実行します。標準エラーは捨てながら計測しました。

結果は以下の通り。 rapidjson score: 3559
picojson score: 7961
こうなりました。picojson と比べて rapidjson がほぼ2倍速いという結果になりました。速い理由をソースを見ながら調べてみましたが、rapidjson は STL を使わず、自前で DOM ライクな API を提供しています。また std::string なども使いません。これによってオーバーヘッドを最小限に抑えているのだと思います。
もちろん、picojson の STL フレンドリも僕は好きですし、他の C++ ライブラリと連携する際にも便利です。

Performance - rapidjson - Compares some performance benchmark results of rapidjson 0.1 and other parsers. Also gives some analysis. - A fast JSON parser/generator for C++ with both SAX/DOM style API - Google Project Hosting

Why rapidjson is fast? rapidjson has made use of several techniques to achieve high performance. C++...

https://code.google.com/p/rapidjson/wiki/Performance
それどころか、このスコア表を見ると picojson のスコアは素晴らしいレベルだと分かりました。
唯一難点として rapidjson は確かに速いのですが、パースエラー詳細に取る方法が分かりませんでした。(あるのかもしれません。知ってる人いたら教えて下さい)

という事で、これからも picojson 使って行こうと思います。
Posted at by



2013/05/14


おしょうさんのを盛大にパクった。罪悪感はない。
netlib で HTTP Server を書いてみた - C++でゲームプログラミング

netlib で HTTP Server を書いてみた Boost . Asio だけだとしんどそうだったので netlib でちょっと書いてみました。 [環境] netlib 0...

http://d.hatena.ne.jp/osyo-manga/20130511/1368242653
mattn/cpp-lingrbot - GitHub
https://github.com/mattn/cpp-lingrbot
#include <boost/network/include/http/server.hpp>
#include <picojson.h>

struct cpp_bot {
  typedef boost::network::http::server<cpp_bot> server;

  void
  operator ()(server::request const &request, server::response &response) {
    namespace http = boost::network::http;
    typedef server::string_type string;
    string body = request.body;
    std::ostringstream data;

    std::cout << body << std::endl;
    picojson::value v;
    string err;
    picojson::parse(v, body.begin(), body.end(), &err);
    if (!err.empty()) {
      response = server::response::stock_reply(
        server::response::bad_request, "invalid request"
      );
      return;
    }

    if (!v.is<picojson::object>()) {
      response = server::response::stock_reply(
        server::response::bad_request, "invalid request"
      );
      return;
    }
    picojson::array arr;
    picojson::object obj;
    
    obj = v.get<picojson::object>();
    v = obj["events"];
    if (!v.is<picojson::array>()) {
      response = server::response::stock_reply(
        server::response::bad_request, "invalid request"
      );
      return;
    }

    arr = v.get<picojson::array>();
    BOOST_FOREACH(auto x, arr) {
      if (!x.is<picojson::object>()) {
        continue;
      }
      picojson::value message = x.get<picojson::object>()["message"];
      if (!message.is<picojson::object>()) {
        continue;
      }
      picojson::value text = message.get<picojson::object>()["text"];
      if (!text.is<string>()) {
        continue;
      }
      string str = text.get<string>();
      if (str.find("cpp"0) != string::npos) {
        data << "くぴぴ\n";
      }
    }

    std::string ret = data.str();
    size_t e = ret.find_last_not_of(\t\r\n");
    if (e != string::npos) {
      ret = string(ret, 0, e);
    }
    response = server::response::stock_reply(
      server::response::ok, ret
    );
  }

  void
  log(...){
    
  }
};


int
main(){
  typedef cpp_bot::server server;
  
  auto address = "localhost";
  auto port = "11614";
  try{
    cpp_bot handler;
    server server_(address, port, handler);
    server_.run();
  }
  catch(std::exception& e){
    std::cout << e.what() << std::endl;
    return 1;
  }

  return 0;
}
cpp という単語が含まれた発言に反応して「くぴぴ」と80年代アニメ風に応答します。

Posted at by