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/06/20


元ネタ: 誰もが一度は陥る日付処理。各種プログラミング言語におけるDateTime型/TimeStamp型の変換方法のまとめ
Go言語が無かったので書いてみた。

現在時刻の取得

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Now())
}
2013-06-19 21:46:14.186298 +0900 +0900

Time => Unix時刻変換

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Now().Unix())
}
1371646123

Unix時刻 => Time変換

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Unix(13716461230))
}
ここからが面白くて、通常日付のフォーマットやパースは殆どの言語では %Y%m%d といった表記を使います。 しかしGo言語では、ある固定の数値を用いて表現する事で、いかにも日付らしく表現出来る手法を取っています。 package main

import (
    "fmt"
    "log"
    "time"
)

func main() {
    t, err := time.Parse(
        "2006-01-02 15:04:05 -0700",    // スキャンフォーマット
        "2013-06-19 21:54:23 +0900")    // パースしたい文字列
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(t)
}
Formatも同じく package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Now().Format("2006/01/02 15:04:05 MST"))
}
2013/06/19 22:01:01 +0900 この数値とタイムゾーン文字列で構成された文字列を、定数として提供しています。 const (
    ANSIC       = "Mon Jan _2 15:04:05 2006"
    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
    RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
    RFC822      = "02 Jan 06 15:04 MST"
    RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
    RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
    RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
    RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
    RFC3339     = "2006-01-02T15:04:05Z07:00"
    RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
    Kitchen     = "3:04PM"
    // Handy time stamps.
    Stamp      = "Jan _2 15:04:05"
    StampMilli = "Jan _2 15:04:05.000"
    StampMicro = "Jan _2 15:04:05.000000"
    StampNano  = "Jan _2 15:04:05.000000000"
)
この手法を使う事で、ちょっとした日付フォーマットの差異も自分のプログラムで吸収する事が出来る様になっています。
初めてこの実装を見た時は戸惑いましたが、慣れると非常に心地よくなります。

おまけでもう少し。

Go言語では、timeパッケージを使って経過時間(Duration)も表す事が出来ます。 time.Sleep(1 * time.Second) // 1秒
time.Sleep(2 * time.Minute) // 2分
time.Sleep(3 * time.Hour)   // 3時間
1時間と61秒は、61分 (1 * time.Hour + 61 * time.Second).Minutes()
日付の加算も出来る。閏年も完璧。 package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Date(20122281213140, time.UTC).AddDate(001))
    // 2012-02-29 12:13:14 +0000 UTC
    fmt.Println(time.Date(20122291213140, time.UTC).AddDate(001))
    // 2012-03-01 12:13:14 +0000 UTC
}
このDurationを使って、数秒後に発動するイベント(channel)も作れます。例えば、3秒以内に CTRL-C を押すかのイベントループ package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    s := make(chan os.Signal)
    signal.Notify(s, syscall.SIGINT)

    select {
    case <- time.After(3 * time.Second):
        fmt.Println("Timeout")
        break
    case <- s:
        fmt.Println("Pressed CTRL-C")
        break
    }
}
3秒後にコールバック package main

import (
    "fmt"
    "os"
    "time"
)

func main() {
    time.AfterFunc(3 * time.Second, func() {
        fmt.Println("Timeout")
        os.Exit(0)
    })

    select {}
}
とまぁ、とても便利になっています。Go言語触った事ないとか、かなりヤバいです。

「えーマジGo言語童貞!?」
「Go言語童貞が許されるのは小学生までだよね」
「キモーイ」
「キャハハハハハハ」
Posted at by



2013/06/19


Google Chrome を使っていて「新しいタブ」を開き、右下にある「最近閉じたタブ」で閉じてしまったタブを復帰させる事が良くあります。
recent closed tab
Google Chrome Dev Channel を使っているのですが、どうやら Google Chrome をアップデートするとこのツールバー自身が消えてなくなる様なのです。この機能のヘビーユーザだったのでかなり困りました。
いろいろ調べたところ、どうやら以下の設定で直す事が出来る事が分かりました。
  1. chrome://flags/#enable-instant-extended-api を開く
  2. 設定を「規定」から「無効」に変更する。
  3. ブラウザを再起動する。
この3手順で、以前と同じツールバーで「最近閉じたタブ」が表示される様になりました。
Posted at by