忘れたときに備えた記録

トップ 最新 追記
2005|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|11|12|
2009|01|02|03|04|05|06|10|12|
2010|06|07|08|12|
2011|07|09|
2012|09|11|
2013|02|03|09|
2015|10|11|
2016|01|08|11|
2017|02|08|10|
2018|11|

2008-01-10(Thursday)

Hiki XHTML化キット0.21.1を公開しました

以下の問題を解決しました。

  • 内部で不適切な正規表現を使用していたことで、ページの表示に非常に長い時間がかかる場合がありました
  • mathwikiスタイルがパッケージに収録されていませんでした

ダウンロードはこちらからどうぞ。

Tags: 更新

2008-01-11(Friday)

Hiki XHTML化キット0.21.2を公開しました

不適切な正規表現を使用していたことが原因で、処理に時間がかかりすぎる場合があった問題を解決しました。

ダウンロードはこちらからどうぞ。

Tags: 更新

正規表現怖い

まんじゅう怖いとか、そういう話ではないのです。

昨日から発覚したx-hikiの問題は、突き詰めると次のような正規表現が原因でした。

#!/usr/bin/ruby
def speed(m="Time")
   t = Time.now
   yield if block_given?
   print "#{m}:" if m
   puts Time.now-t
end

r = /(?:a+)*c/
1000.times do |n|
   s = "a"*n+"bc"
   puts n
   puts s
   speed do
      puts s=~r, $&
   end
   $stdout.flush
end

このスクリプトは、"aaaaaaaaabc"という文字列と /(?:a+)*c/という正規表現とのマッチを、文字列前半の"aaa..."の部分を1文字ずつ大きくして試し、かかった時間を表示するものです。

実行すると、こうなります。

~$ ./test.rb 
0
bc
1
c
Time:9.4e-05
1
abc
2
c
Time:7.1e-05
2
aabc
3
c
Time:6.7e-05

この辺は、特に問題ありません。

aaaaaaabc
8
c
Time:0.000119
8
aaaaaaaabc
9
c
Time:0.000162
9
aaaaaaaaabc
10
c
Time:0.00026
10
aaaaaaaaaabc
11
c
Time:0.000451
11
aaaaaaaaaaabc
12
c
Time:0.000831

段々恐ろしい気配が出てきます。

aaaaaaaaaaaaaaaaaaaabc
21
c
Time:0.351611
21
aaaaaaaaaaaaaaaaaaaaabc
22
c
Time:0.710038
22
aaaaaaaaaaaaaaaaaaaaaabc
23
c
Time:1.573933
23
aaaaaaaaaaaaaaaaaaaaaaabc
24
c
Time:3.230857
24
aaaaaaaaaaaaaaaaaaaaaaaabc
25
c
Time:6.429239

なんと、たった25文字の文字列のマッチを判定するにのに、6秒以上もかかっています。

それより何より、1文字増える毎に所要時間が2倍になっています!

オチも無いままに終わります。

aaaaaaaaaaaaaaaaaaaaaaaaaaaaabc
30
c
Time:199.959477

どっとはらいヽ(´ー`)丿

Tags: Ruby

正規表現怖いの続き

ふと思いついて、perlで同様の正規表現を使ってみた。

#!/usr/bin/perl
$s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabc";
if($s =~ /(?:a+)*c/){
   print $&, "\n";
}

実行すると

hiraku@hirakuro:~/tmp$ perl -v

This is perl, v5.8.8 built for i486-linux-gnu-thread-multi

Copyright 1987-2006, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

hiraku@hirakuro:~/tmp$ time perl test.pl
c

real    0m0.004s
user    0m0.004s
sys     0m0.000s

おや。簡単に終わったしまった。

そこで、さっきのスクリプトを、異なるバージョンのRubyで試してみた。

さっき日記に書いたときに試したのはUbuntu7.10の標準のRubyで

hiraku@hirakuro:~$ ruby -v
ruby 1.8.6 (2007-06-07 patchlevel 36) [i486-linux]

これは、文字列が1文字分長くなる毎に実行時間が2倍になっていた。

で、最新安定版を入れて試してみた。

hiraku@hirakuro:~$ ~/opt/ruby1.8.6p111/bin/ruby -v
ruby 1.8.6 (2007-09-24 patchlevel 111) [i686-linux]
hiraku@hirakuro:~$ ~/opt/ruby1.8.6p111/bin/ruby test.rb 
0
bc
1
c
Time:7.6e-05
(中略)
Time:0.404212
21
aaaaaaaaaaaaaaaaaaaaabc
22
c
Time:0.802581
22
aaaaaaaaaaaaaaaaaaaaaabc
23
c
Time:1.704644
(以下略)

これも倍々になってしまった。

最後に最新版1.9.0をやってみると

hiraku@hirakuro:~$ ~/opt/ruby1.9/bin/ruby -v
ruby 1.9.0 (2007-12-25 revision 14709) [i686-linux]
hiraku@hirakuro:~$ ~/opt/ruby1.9/bin/ruby test.rb
0
bc
1
c
Time:1.7495e-05
(中略)
999
a(略)abc
1000
c
Time:0.014532279

お、おおおお!素晴らしい!!鬼車&1.9.0万歳!!!

Tags: Ruby

例外型カスタムマッチャ

今度はカスタム抹茶が怖い。という話しではないのです。

最近、Rubyist Magazine - スはスペックのス 【第 1 回】 RSpec の概要と、RSpec on Rails (モデル編)を参考にRSPecを使い始めました。 で、普通の

1.should > 0

みたいに使うカスタムマッチャはこのへんを参考に作れたのですが、

assert_raise(){}

の代わりになる

lambda{}.should raise_error()

のようなカスタムマッチャの作り方がすこし手間取ったので、出来たサンプルを忘れたときに備えて公開です。

前半がカスタムマッチャの定義で、後半が使用例です。

class RaiseCustom
   def initialize(expected)
      @expected = expected
   end

   def matches?(proc)
      begin
         proc.call
      rescue => @actual
         return @actual.is_a?(@expected)
      else
         return false
      end
   end

   def failure_message
      if @actual
         "raise #{@expected} expected but #{@actual} raised"
      else
         "raise #{@expected} expected but nothing raised"
      end
   end

   def negative_failure_message
      if @actual
         "NOT raise #{@expected} expected but #{@actual} raised"
      else
         raise "Must not happen"
      end
   end
end

def raise_custom(expected)
   RaiseCustom.new(expected)
end

describe "raise_error-type matcher" do
   it "should raise StandardError but nothing raised" do
      lambda{}.should raise_error(StandardError)
   end

   it "should raise ArgumentErroror but RuntimeError" do
      lambda{raise "hoge"}.should raise_error(ArgumentError)
   end

   it "should not raise StandardError but raised" do
      lambda{raise StandardError}.should_not raise_error(StandardError)
   end

   it "should raise(custom) StandardError but nothing raised" do
      lambda{}.should raise_custom(StandardError)
   end

   it "should raise(custom) ArgumentError but RuntimeError raise" do
      lambda{raise "hoge"}.should raise_custom(ArgumentError)
   end

   it "should not raise(custom) StandardError but raised" do
      lambda{raise StandardError}.should_not raise_custom(StandardError)
   end

   it "should not raise(custom) ArgumentError and RuntimeError raised" do
      lambda{raise "hoge"}.should_not raise_custom(ArgumentError)
   end
end
Tags: RSpec

RSpecのドキュメント

RubyForgeにあるRSpecのドキュメント、例えば http://rspec.rubyforge.org/documentation/tools/spec.html なんかを表示すると、少し変な風に表示されてしまうなと思っていたのですが、どうもドキュメントページは http://rspec.info/documentation/tools/spec.html に移っていたようです。

こっちは、スタイルシートがきちんと適用されて綺麗に表示されています。

Tags: RSpec
本日のツッコミ(全2件) [ツッコミを入れる]

_ g5n [万歳なのは鬼車と思われ>1.9.0]

_ ひらく [言われてみればその通りですね。訂正しました]


2008-01-12(Saturday)

宇宙

そこは最後のフロンティア。という話ではないのです。

飛行船の復活 - スラッシュドット・ジャパンあるコメント|で読んで初めて知ったのですが、「宇宙」という言葉は

  • 宇 : 空間のこと
  • 宙 : 時間のこと

なのだそうです。へえぇぇぇぇぇ。

Tags: 雑談

GRUBでRAIDなWindowsXPとUbuntuをデュアルブートする

僕のパソコンには、次の順番でSATAにHDDをつないでいます

  1. WindowsXP用RAID0 1台め
  2. WindowsXP用RAID0 2台め
  3. Ubuntu 7.10用

マザーボードのチップセットはP965+ICH8Rで、WindowsXPのRAID0にはICH8Rの機能を使っています。

インストールは WindowsXP -> Ubuntu 7.10 の順番で行い、パソコン起動時にF8キーを押すことで出てくる「どのドライブからブートするか」のメニューで起動するドライブを選んで使い分けていました。

このやり方の不便な点は、パソコン起動時の最初にF8を連打し損なうとメニューが表示されなくなることで、そろそろWindowsXPの方をRAID0で使うのは止めようかなと思っていたのです。そうすれば、GRUBからWindowsXP用のパーティションが認識できるので、GRUBのメニュー(タイムアウトを5秒くらいに設定する)で選べるようになりますから。

ところが最近になって、RAID0のデバイスに直接GRUBの起動部分をインストールできればうまく行くのではないかと考えつきました。

調べてみたら同じ事をやっている人がいて、その人の解説を参考に試したところ実際にうまく行ったので、忘れたときに備えてまとめる次第です。

参考にしたのはATA RAIDでのgrub インストール - ribbon の日記です。

また、起動したUbuntuからWindows用のRAID0を認識してマウントする方法はRAID by dmraid - tuchiya.org Wikiを参考にしました。

RAID0ディスクの認識

# dmraid -ay

とすると、次のデバイスファイルが作られます。

  1. /dev/mapper/isw_bcihaahfac_HIRAID0
  2. /dev/mapper/isw_bcihaahfac_HIRAID01

1番目がRAID0によるディスク全体で、2番目はそこに作られたWindowsXPのパーティションです。

# mount /dev/mapper/isw_bcihaahfac_HIRAID01 /mnt

とかすると、UbuntuからRAID0のWindowsXPのディスクを使うこともできてとても便利です。

GRUBのRAID0ディスクへのインストール

# grub --device-map=/dev/null

としてgrubを起動して、次のように入力します。

# GRUBにRAID0を(hd0)として登録する
grub> device (hd0,0) /dev/mapper/isw_bcihaahfac_HIRAID01
grub> device (hd0) /dev/mapper/isw_bcihaahfac_HIRAID0 

# Ubuntu用ディスクを(hd2)として登録する
grub> device (hd2,0) /dev/sdc1
grub> device (hd2) /dev/sdc 

# Ubuntuの/bootがあるパーティションを指定する
grub> root (hd2,0)

# GRUBのインストール先をRAID0に指定してインストール
grub> setup (hd0)
 Checking if "/boot/grub/stage1" exists... yes
 Checking if "/boot/grub/stage2" exists... yes
 Checking if "/boot/grub/e2fs_stage1_5" exists... yes
 Running "embed /boot/grub/e2fs_stage1_5 (hd0)"...  17 sectors are embedded.
succeeded
 Running "install /boot/grub/stage1 d (hd0) (hd0)1+17 p (hd2,0)/boot/grub/stage
2 /boot/grub/menu.lst"... succeeded
Done.

/dev/sdcを番号を詰めて(hd1)にするとGRUB自体が正常に起動しなくなります。 Ubuntu起動後のGRUBの認識に合わせて/dev/sdcを(hd2)にすると、パソコン起動時の認識と食い違って起動しなくなります((hd2)にして起動したと思ったのは間違いでした)。 (やはり(hd2)が正解でした。)

menu.lst書き換え

/boot/grub/menu.lstに次の記述を追加します。

title		Windows XP
root		(hd0,0)
savedefault
makeactive
chainloader	+1

これで、起動時に(F8を連打しなくても)GRUBのメニューが表示されて、WindowsXPとUbuntuを選べるようになりました。どっとはらい

Hiki XHTML化キット0.21.3を公開しました

正規表現部分にTypoがあって止まらなくなる場合があったバグを解決しました。

ダウンロードはこちらからどうぞ。

Tags: 更新

describeとitのスコープ

RSpecのdescribeメソッドとitメソッドに渡されるブロックのスコープを調べてみました。

test1_spec.rb, test2_spec.rb という2つのファイルを、まったく同じ以下の内容で作成します。

puts "outside: (#{self.class})#{self}"
describe "HOGE" do
   puts "HOGE: (#{self.class})#{self} < #{superclass}"
   it do
      puts "it1 of HOGE: (#{self.class})#{self}"
   end
   it do
      puts "it2 of HOGE: (#{self.class})#{self}"
   end
end

describe "FUGA" do
   puts "FUGA: (#{self.class})#{self} < #{superclass}"
   it do
      puts "it1 of FUGA: (#{self.class})#{self}"
   end
   it do
      puts "it2 of FUGA: (#{self.class})#{self}"
   end
end
hiraku@shako:~/tmp$ ls
test1_spec.rb  test2_spec.rb

で、実行。

hiraku@shako:~/tmp$ spec .
outside: (Object)main
HOGE: (Class)Spec::Example::ExampleGroup::Subclass_1 < Spec::Example::ExampleGroup
FUGA: (Class)Spec::Example::ExampleGroup::Subclass_2 < Spec::Example::ExampleGroup
outside: (Object)main
HOGE: (Class)Spec::Example::ExampleGroup::Subclass_3 < Spec::Example::ExampleGroup
FUGA: (Class)Spec::Example::ExampleGroup::Subclass_4 < Spec::Example::ExampleGroup
it1 of HOGE: (Spec::Example::ExampleGroup::Subclass_1)#<Spec::Example::ExampleGroup::Subclass_1:0xb7b4ca00>
.it2 of HOGE: (Spec::Example::ExampleGroup::Subclass_1)#<Spec::Example::ExampleGroup::Subclass_1:0xb7b4c9c4>
.it1 of FUGA: (Spec::Example::ExampleGroup::Subclass_2)#<Spec::Example::ExampleGroup::Subclass_2:0xb7b4c514>
.it2 of FUGA: (Spec::Example::ExampleGroup::Subclass_2)#<Spec::Example::ExampleGroup::Subclass_2:0xb7b4c4d8>
.it1 of HOGE: (Spec::Example::ExampleGroup::Subclass_3)#<Spec::Example::ExampleGroup::Subclass_3:0xb7b4b1c8>
.it2 of HOGE: (Spec::Example::ExampleGroup::Subclass_3)#<Spec::Example::ExampleGroup::Subclass_3:0xb7b4b18c>
.it1 of FUGA: (Spec::Example::ExampleGroup::Subclass_4)#<Spec::Example::ExampleGroup::Subclass_4:0xb7b4acdc>
.it2 of FUGA: (Spec::Example::ExampleGroup::Subclass_4)#<Spec::Example::ExampleGroup::Subclass_4:0xb7b4aca0>
.

Finished in 0.008873 seconds

8 examples, 0 failures

この結果で、次の事が分かりました。

  • describe に渡したブロックは独自のクラス(インスタンスではない)のスコープで実行される
    • クラスはSpec::Example::ExampleGroupのサブクラス
  • itのブロックはdescribeのクラスのインスタンス
    • it毎に新しいインスタンスが作られる

ということは、describeの中でincludeとかしまくって良かったわけかな。

Tags: RSpec

2008-01-13(Sunday)

複数のexampleで共通するbefore/after

一時ディレクトリを作って消してというのを、複数のexampleで共通して行いたい場合にどうしたら良いのか考えてみました。

多分、こんな感じでmodule.includedを使えば良いんじゃないかと思います。

module M
   def self.included(ex)
      ex.before do
         puts "before in M"
      end
      ex.after do
         puts "after in M"
      end
   end
end

describe "hoge" do
   include M

   before do
      puts "before"
   end
   after do
      puts "after"
   end

   it do
      puts "In it"
   end

   it do
      puts "In it 2"
   end
end

describe "fuga" do
   include M
   it do
      puts "In fuga-it"
   end
end

実行結果

before in M
before
In it
after
after in M
.before in M
before
In it 2
after
after in M
.before in M
In fuga-it
after in M
.

Finished in 0.009692 seconds

3 examples, 0 failures

良いみたいです。

最初はSpec::Example::ExampleMethods#before_exampleというのを見つけて、これかとも思ったんですが

module M
   def before_example
      puts "M#before"
      super
   end

   def after_example
      puts "M#after"
      super
   end
end

superを使わないといけない(describeブロックのbefore/afterを実行するため)し、多分違うでしょう。

Tags: RSpec

2008-01-14(Monday)

「モック/スタブ」と「状態中心/振る舞い中心のテスト」

あるプログラムのテストを書いていて、既存のクラスの動作を真似するダミークラスをモックと呼ぶべきなのかスタブと呼ぶべきなのか分からなくなり、少し調べてみたところ、そのものずばりの文書が見つかりました。

  1. Ruby on Rails でのモックとスタブの作成
  2. Mocks Aren't Stubs (和訳 モックとスタブの違い)

特に2の文書で述べられている「状態中心のテストと振る舞い中心のテスト」という概念がとても興味深かったので、さっそく自分のプログラムのテストに試してみました。

それをさらに抽象化したサンプルを作ったので、それを解説してみます。

サンプルクラス

以下が、今回のテストで扱うクラスです。

### sample.rb ###
class C
   @@value = "default"
   def self.global_control(value)
      @@value = value
   end

   def get
      @@value
   end
end

module D
   def self.control
      C.global_control("from D")
   end
end

クラスCは使われる側のクラス、Dが使う側のクラスです。

C#get は通常は"default"という文字列を返しますが、C.global_control("hoge")とすると、指定した文字列を返すようになります。

D.controlがC.global_controlを使っています。

状態中心のテスト

このようなクラスDのテストを書くとすると、次のようになります(もちろん、本来はテストを先に書いてからDの実装を行うべきでしょう)。

### sample_test.rb ###
require "test/unit"
require "sample"
class Test_D < Test::Unit::TestCase
   def test_control
      assert_equal("default", C.new.get)
      D.control
      assert_equal("from D", C.new.get)
   end
end

このテストでは、C#getの返り値が変化していることを使ってD.controlの動作をテストしています。これが状態中心のテスト。Cのクラス変数@@valueという状態に主眼を置いてテストしているわけです。

振る舞い中心のテスト

一方RSpecのスタブの機能を使うと、次のようなテストを書くことができます。

### sample_spec.rb ###
describe D do
   it "call C.global_control" do
      C.should_receive(:global_control).with("from D")
      D.control
   end
end

このテストではC.global_controlをフックして、直接、このメソッドが想定したパラメータで呼ばれているかを確かめています。「クラスDがC.global_controlを呼ぶ」という振る舞いに主眼を置いて記述しているので振る舞い中心のテストというわけです。

考察

振る舞い中心のテストを使えば、他のクラスを使うクラス(D)の振る舞いその物を記述したテストを書くことができます。使われる側のクラス(C)のリファクタリングを試みて正しい動作をしなくなったときに、Cの為のテストだけが失敗するので、原因の追求がしやすくなるでしょう。

一方、Cの仕様を変更すると決めてそのように実装を変えたときに、振る舞い中心のテストだけではその変更がDに影響するかどうかが、このテストだけでは分からなくなります。極端な例を考えると、C.global_controlが削除されたとしても、sample_spec.rbに書いたDの振る舞い中心テストは通ってしまいます。

Tags: RSpec

stubされた元のメソッドの呼び出し

何か方法があるんじゃないかと思って少し調べてみたら、次のような書き方で元のメソッドを呼べると分かりました。

### sample_spec.rb ###
describe D do
   it "call C.global_control" do
      C.should_receive(:global_control){|arg|
         C.proxied_by_rspec__global_control(arg)
      }.with("from D")
      D.control
      C.new.get.should == "from D"
   end
end

つまり、should_receiveメソッドにブロックを渡せるのですが、そこで proxied_by_rspec__original_methodとすれば、元のメソッドが(存在するなら)呼び出されるわけです。"proxied_by_rspec__"が元メソッド名のprefixで、最後のアンダースコアは2つです。

これで、C.global_controlが削除されたりしたらこのテストもredになります。

ただし、これはソースから見つけた方法なので、将来においても使えるかどうかは分かりません。 やはりshould_receiveで指定したメソッドは別にテストを作るべきなんでしょうか?

Tags: RSpec

2008-01-17(Thursday)

Rubyのソース

実はまだ読んだことがないのです。でもそろそろ読まないとやって行けなさそう。この間の正規表現の話とか。

今から読むんだと、1.8.6系列と1.9系列、どっちが良いでしょうね(と、もう一人の自分(誰)に聞いてみるテスト)

Tags: 独り言

C++でBDD

RSpecのC++版って無いものかなと調べてみたら、CppSpecというものを見つけました。

Implementations - Behaviour-Driven Development経由です。

xUnitではなくxSpecとでも言うべきものが、DelphiやJavaScriptの為のものまであったとは驚きました。

Tags: RSpec

2008-01-21(Monday)

bashで標準エラー出力をパイプする方法

rubyの起動オプションで -y を付けると、スクリプトを解析する様子が細かく表示されます(が、その意味を把握できるかどうかは別問題(汗))が、その出力が標準エラー出力に行われているので、エラー出力をパイプする方法を調べてみました。

やり方は簡単で、

~$ ruby -ye 'lambda{puts (1), 2}' 2>&1 |lv

これだけ。

参考

UNIXの部屋 コマンド検索:リダイレクト (*BSD/Linux)

Tags: メモ
本日のツッコミ(全5件) [ツッコミを入れる]

Before...

_ ひらく [おお、ありがとうございます。さっそく読んでみました]

_ ひらく [ちなみにSPAM判定されたツッコミを後から設定画面でHAM化させると、この通り投稿順は保存されませんが時間は保存され..]

_ g5n [なるほど、そんな機能も。]


2008-01-22(Tuesday)

うっかり

C++のテンプレートクラスのフレンド関数について少し調べて書いていたのですが、結論が間違っていたので全部キャンセルしてしまいました。(´・ω・`)ショボーン

やっぱり残しておきます

途中の考察を後で使うような気がしてきたので、やっぱり書いてしまうことにします(汗

というわけで、改めて…

templateクラスのfriend関数

昨日、C++入門セミナーにTAっぽい役割で参加したのですが、その時に次のような例が話題になりました。

テンプレートクラスのプライベート変数にアクセスできるフレンド関数は、どのように宣言するか?

例えば

/// test1.cpp
#include <iostream>

template <class C> class Test
{
    C v;
public:
    Test(const C &a){
        v = a;
    }
    friend std::ostream& operator<<(std::ostream &os, const Test<C> &t);
};

template<class C> std::ostream& operator<<(std::ostream &os, const Test<C> &t)
{
    os << t.v;
    return os;
}

int main()
{

    Test<int> o(1);

    std::cout << o << std::endl;

    return 0;
}

というソースをコンパイルするにはどうした良いか?という話です。

ざっとググったところ、

あたりが引っかかります。

Tags: C++

test1.cppのコンパイル

まず、上の例(test1.cpp)をそのままコンパイルしようとすると、

test1.cpp:10: 警告: friend declaration ‘std::ostream& operator<<(std::ostream&, const Test<C>&)’ declares a non-template function
test1.cpp:10: 警告: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here) -Wno-non-template-friend disables this warning
/tmp/ccOP5KR2.o: In function `main':
test1.cpp:(.text+0xab): undefined reference to `operator<<(std::basic_ostream<char, std::char_traits<char> >&, Test<int> const&)'
collect2: ld はステータス 1 で終了しました

というコンパイル時の警告とリンク時のエラーが出ます。

警告の方は、friend宣言した関数がテンプレート関数ではないと言ってきており、テンプレート関数にするには関数テンプレートをあらかじめ宣言し、ここ(friend宣言)の関数名の直後に<>を追加せよとアドバイスしてきています。

また、リンク時のエラーによると、

std::cout << o

の部分を実行するのに必要な、Test<int>を引数にとるoperator<<関数がないと言ってきています。

アドバイスに従って書き換えてみる

そこで警告メッセージに従って、冒頭にテンプレート関数の宣言を追加します。関数でテンプレートクラスの名前が必要なので、テンプレートクラスの宣言も追加します。

できたソースはこんな感じ(main関数は同じなので省略)。

/// test2.cpp
#include <iostream>

template <class C> class Test;
template <class C> std::ostream& operator<<(std::ostream &os, const Test<C> &t);

template <class C> class Test
{
    C v;
public:
    Test(const C &a){
        v = a;
    }
    friend std::ostream& operator<<<>(std::ostream &os, const Test<C> &t);
};

template<class C> std::ostream& operator<<(std::ostream &os, const Test<C> &t)
{
    os << t.v;
    return os;
}

これでコンパイル時の警告が消えて、リンクも無事行われ、問題なく動作します。

でも

template <class C> class Test;
template <class C> std::ostream& operator<<(std::ostream &os, const Test<C> &t);

これは美しくないでしょう!?クラス定義の前に、まずクラス名だけ宣言して、friendする関数は2回も宣言するなんて。あんまりです。(しかしやっぱり、この方法しかないみたいです。残念無念)

もちろん、関数宣言の代わりに、そこに直接、関数operator<<の実装を記述すれば関数宣言はいらなくなります。それでも、最初のクラス宣言は残ってしまって、美しくないことに違いはありません。

と言うわけで、このクラス定義の前の宣言がいらない書き方がないものか、調べてみました。

宣言だけ省いてみる

まずは、コンパイルが成功したtest2.cppから、美しくない宣言だけ削ってコンパイルしてみます。削ったのは先頭のこの2行です。

template <class C> class Test;
template <class C> std::ostream& operator<<(std::ostream &os, const Test<C> &t);

するとこんなエラーが出てきました。

test2.cpp:10: error: declaration of ‘operator<<’ as non-function
test2.cpp:10: error: expected ‘;’ before ‘<’ token
test2.cpp: In function ‘std::ostream& operator<<(std::ostream&, const Test<C>&) [with C = int]’:
test2.cpp:24:   instantiated from here
test2.cpp:5: error: ‘int Test<int>::v’ is private
test2.cpp:15: error: within this context

つまり

friend std::ostream& operator<<<>(std::ostream &os, const Test<C> &t);

この宣言が関数ではないと見なされて、そのせいでoperator<<関数の実相の中でTest<C>.vを参照しようとしてもprivateだから参照できませんよと言うわけです。

test1.cppのリンクが成功するようにしてみる

そもそも、なぜtest1.cppのリンクが失敗するのかを調べてみました。色々試した挙句、次の関数をtest1.cppに追加すると、コンパイル時の警告は残ったままですがリンクにも成功するようになりました。

std::ostream& operator<<(std::ostream &os, const Test<int> &t)
{
    os << t.v+1;
    return os;
}

つまり、

friend std::ostream& operator<<(std::ostream &os, const Test<C> &t);

の宣言は、テンプレート関数をfriend登録するのではなく、普通の関数をfriend登録する千件として解釈されているわけです。

察するに、このソースのコンパイルは次のような手順で行われているのでしょう

  1. テンプレートクラスTestの実装を読み込んで解析する(この時点でfriend行の警告が出る)
  2. Test<int> o(1); の変数宣言によって<int>版のTestクラスが実体化され、それと同時にTest<int>クラスのfriendとしてoperator<<(... Test<int> &t)が登録される
    1. テンプレートクラスoperator<<は、実体化されないためにエラーにならない
    2. std::cout << o(1) では、普通の関数版のoperator<<が呼び出される(ので、最初のtest1.cppではリンクエラーになる)

template関数をfriend宣言する

そんなわけで、関数のフレンド宣言で直接テンプレート関数が指定できればいいのだろうと考え、あれこれ試した挙句

#include <iostream>

template <class C> class Test
{
   C v;
public:
   Test(const C &a){
      v = a;
   }
   template<class D> friend std::ostream& operator<<(std::ostream &os, const Test<D> &t);
};

template<class C> std::ostream& operator<<(std::ostream &os, const Test<C> &t)
{
   os << t.v;
   return os;
}


int main()
{

   Test<int> o(1);
   std::cout << o << std::endl;

   return 0;
}

というソースを書くに至りました。

template<class D> friend std::ostream& operator<<(std::ostream &os, const Test<D> &t);

がポイントで、こう書くことで、test2.cppのような美しくない宣言が不要になります。

やったぜ!えうれか!!




・・・・・と思っていたのですが。

改めてよくよく参考リンク先を再読してみたら、[cppll:10716] Re: instantiate されない friend-function-templateですでに言及されている上に、 Test<int>のインスタンスで呼び出された関数からTest<double>のプライベート変数にアクセスできるという深刻な問題まで指摘されていました。

結論: もっとよく落ち着いて調べましょうorz

テンプレート関数と普通の関数の優先順位

せっかくなので、もうちょっと考察を書き留めてみましょう。

#include <iostream>

template <class C> class Test
{
    C v;
public:
    Test(const C &a){
        v = a;
    }
    template<class D> friend std::ostream& operator<<(std::ostream &os, const Test<D> &t);
    friend std::ostream& operator<<(std::ostream &os, const Test<int> &t);
};
Test<double> gd(1000);

template<class C> std::ostream& operator<<(std::ostream &os, const Test<C> &t)
{
    os << t.v;
    return os;
}
template<> std::ostream& operator<<(std::ostream &os, const Test<int> &t)
{
    os << t.v+gd.v;
    return os;
}

std::ostream& operator<<(std::ostream &os, const Test<int> &t)
{
    os << t.v*100+gd.v;
}

int main()
{

    Test<int> o(1);
    std::cout << o << std::endl;

    return 0;
}

このソースで、さっきの「Test<int>からTest<double>のプライベート変数にアクセスできる」問題が再現できます(確認するには、普通の関数の方のfriend宣言と実装をコメントアウトする必要があります)。

もう一つ。このソースをコンパイルして実行すると、

1100

と出てきます。つまり、テンプレート関数(およびその特別版)と、(引数にテンプレートクラスの特別版をとる)普通の関数とを用意しておくと、後者が優先して使われることが分かります。

宣言を減らす方向で書き直してみる

せっかくだから、俺はこの赤の扉を選ぶぜ!もう少し色々試してみます。

まず、宣言を記述する場所を変えてみました。

#include <iostream>

template <class C> class Test
{
    C v;
public:
    Test(const C &a){
        v = a;
    }
};

template<class C> std::ostream& operator<<(std::ostream &os, const Test<C> &t)
{
    os << t.v;
    return os;
}

template <class C> class Test
{
    friend std::ostream& operator<<<>(std::ostream &os, const Test<C> &t);
};

関数の定義を書いてから、その関数のfriend宣言をするわけです。rubyのprivateの書き方に似ててちょっと良いかなあと。

で、コンパイル。

test3.cpp:19: error: redefinition of ‘class Test<C>’
test3.cpp:5: error: previous definition of ‘class Test<C>’

はい。Rubyじゃないんだから、クラスを複数箇所に分散して書けはしないのでした。

friend宣言したら負けかなと思っている

そもそもfriend関数が必要な構造が間違っているんだ!というわけで、次のように書いてみました。

// test4.cpp
#include <iostream>

template <class C> class Test
{
    C v;
public:
    Test(const C &a){
        v = a;
    }
    void to_stream(std::ostream &os) const
    {
        os << v;
    }
};

template<class C> std::ostream& operator<<(std::ostream &os, const Test<C> &t)
{
    t.to_stream(os);
    return os;
}

int main()
{

    Test<int> o(1);

    std::cout << o << std::endl;

    return 0;
}

出力に使うためのメンバ関数を用意して、operator<<関数でそれを呼ぶわけです。RubyのObject#to_strみたいなノリで。

少なくともoperator<<については、これが一番良いんじゃないかなという気がしてきましたが、どうでしょうか?

本日のツッコミ(全3件) [ツッコミを入れる]

_ tueda [私も cout << 何か は、別途メンバー関数として std::ostream& print (std::os..]

_ ひらく [>「privateは使わず全部publicで宣言する」 これもまた思い切った手ですねぇ。メンバーの隠蔽とかは一切な..]

_ お幸 [素直に getter関数定義したほうが(ry public: const C& v() const { ..]


2008-01-25(Friday)

設定画面の修正 & パッケージの更新

  • Bayesフィルタ設定画面の、新着のリンク元やツッコミを表示するページにおいて
    • スパム率を表示するようにしました
    • label要素を使い、クリックしやすくしました
  • 新着リンク元のコーパスへの登録画面で、スパム・ハムに関わらず、デフォルトではチェックマークが付かないようにしました

また、ここまでの修正を反映させたパッケージを作って公開しました。こちらからダウンロード出来ます。


2008-01-26(Saturday)

メールの配信が遅い

起こったことをありのままに書きますが、参加しているメーリングリストで、投稿された質問メールに30分後くらいに回答メールを出してみたら、さらにその30分程後になって「最初の質問メールの10分後に投稿された別の回答メール」が配信されてくるという現象がありました。超スピードとかそういうチャチなものでは断じてないようです。

何が起きているのか調べるために法王の緑で結界をメールヘッダを見てみたら、プロバイダのスパム対策サーバで足止めを喰らっている模様です。

とりあえずプロバイダ(ASAHIネット)の「マイフィルタ」が有効になっていたのでを無効にしてみたので様子見です。

Time.localの引数の範囲

usecを指定するときに1000000以上の値を指定すると、きちんと秒や分に繰り上がってくれます。

> puts t = Time.local(2001, 2, 3, 4, 5, 6, 1000012), t.usec
Sat Feb 03 04:05:07 +0900 2001
12
>  puts t = Time.local(2001, 2, 3, 4, 5, 6, 60000001), t.usec
Sat Feb 03 04:06:06 +0900 2001
1

しかし、秒などでは範囲が限定されています。

> Time.local(2001, 2, 3, 4, 5, 61)
ArgumentError: argument out of range
Tags: Ruby

2008-01-27(Sunday)

バグ修正

  • 'ー'(長音記号)をカタカナに含めていませんでしたが、含めるようにしました。「バーゲン」とかがきちんとトークン化されるようになります。
  • 設定画面で、「SPAM/HAMとして登録」とするべき個所に「SPAM/HAMのまま」と表示していたバグを修正しました。

ダウンロードはこちらからどうぞ


2008-01-28(Monday)

スパムフィルタ始めました@Hiki

Hikiにスパムフィルタの機能を組み込むパッチを作りました。ここからダウンロード出来ます。

以下、主な特徴です

  • とりあえず、ベイズフィルタとブラックリストで弾く単語フィルタの2つを用意してあります。設定画面で有効無効を選べます
    • 別のフィルタを作って追加することも出きるようにしたつもりです。APIがちょっと怪しいので、意見とか頂けると嬉しいです
  • ページ編集の他に、commentプラグイン、BBSプラグイン、trackbackプラグインや、itsプラグインでも機能するようにしてあります
  • tDiary版と違って、「SPAM扱いしてしまったHAMを設定画面から登録しなおす」機能がありません
  • 学習データベースの再構築時には、既存のページからHAM用のトークンを取り出すようにしています
  • 既存ページの編集(commentプラグインなども含む)の場合は、追加分の行だけからSPAM判定するようにしています

なんかもっと色々あったようななかったような気がするのですが、徹夜明けで良く分かりません。とりあえず、どんどん学習させてみたいのですぱむかむひあ!!

あ、もうひとつ思い出した。

当然ながら、最初はデータベースが空っぽのおバカさんなので、ほとんどすべてのスパムを通してしまうと思います。しばらくトップページのRSS等の更新が見苦しくなりますが、お見逃しください。


2008-01-30(Wednesday)

C++でSpec

ショートトークにそろそろまた当たりそうな気がしてきたので*1そのネタにしようと、C++のユニットテストツール CPPUNIT を試していました。

ついでに、以前見つけたC++用のSpecライブラリCppSpecも合わせて試そうと思っていたのですが、どちらも

    EmptyStackSpec() {
        REGISTER_BEHAVIOUR(EmptyStackSpec, emptyStackShouldRaiseException);
        REGISTER_BEHAVIOUR(EmptyStackSpec, stackIsEmpty);
        REGISTER_BEHAVIOUR(EmptyStackSpec, stackEqualsWithEmpty);
        REGISTER_BEHAVIOUR(EmptyStackSpec, countShouldBeZero);
    }

だの

  CPPUNIT_TEST_SUITE( ExampleTestCase );
  CPPUNIT_TEST( example );
  CPPUNIT_TEST( anotherExample );
  CPPUNIT_TEST( testAdd );
  CPPUNIT_TEST( testEquals );
  CPPUNIT_TEST_SUITE_END();

だのと、テスト用の関数を、関数定義その物とは別にマクロで登録しないといけないのが気に入らなくなってきました。

必要なヘッダーをインクルードしたら、後はRubyのRSpecのように

DESCRIBE(int)
{
    VERIFY{
        THAT{
            IT(1+1)SHOULD(==2);
            IT(2*3)SHOULD(==6);
            IT(1/2)SHOULD(==0.5);
        }
    }
};
REGISTER(int)

とだけ書けたら最高なのにと思っていたら、なんだか作れそうな気がしてきたので、半日がかりで試作品を作ってしまいました。

上のテストや、

DESCRIBE(int_vector)
{
    std::vector<int> *v;
    BEFORE{
        v=new std::vector<int>;
    }
    AFTER{
        delete v;
    }

    VERIFY{
        THAT{
            IT(v->empty())
                SHOULD(BE_TRUE);
            v->push_back(1);
            IT(v->at(0))SHOULD(==1);
        }

        AND_THAT{
            IT(v->empty())
                SHOULD(BE_TRUE);
            v->push_back(2);
            IT(v->at(0))SHOULD(==3);
        }
    }
};
REGISTER(int_vector);

のようなものまで、きちんとコンパイルできて、

Test(109) OK : v->empty()BE_TRUE
Test(112) OK : v->at(0)==1
Test(116) OK : v->empty()BE_TRUE
Test(119) NG : v->at(0)==3
Test(129) OK : 1+1==2
Test(130) OK : 2*3==6
Test(131) NG : 1/2==0.5

Failure at 119
    by v->at(0)==3
Failure at 131
    by 1/2==0.5

といった具合に出力出来ます。

Tags: C++

*1 所属している研究室で毎週行われるセミナーの一環で、ライトニングトークのようなもの。当日朝8時にランダムに発表者が選ばれる。

C++の便利なマクロ : Cソースを文字列化する

なお、上で行っている「ソース中の式を文字列にする方法」は、CPPUNITのソースを読んでいて知りました。

#define EXP2STR(_exp) puts(#_exp)

とマクロ定義しておくと

EXP2STR(1+2==2);

といった具合に使うと、文字列 "1+1==2" が出力されます。

C++用RSpec

名前は何がいいかなぁ。CppSpecとRSpecの中間だからQSpecとか?

それはさておき、寝る前に2つほどアイデアが浮かんだので入れてみました。

まず、もう少し記述を簡略化できるようにしてみました。具体的には、

DESCRIBE(symbol)
{
};
REGISTER(symbol)

と、クラス名とするシンボルをDESCRIBEとREGISTERの両方に記述しないと駄目だったのを、

DESCRIBE("string")
{
} END_DESCRIBE;

と、一ヶ所だけで済むようにしました。

それと

THAT{
// ここでテスト失敗すると...
} AND_THAT
{
// ここは実行されなかった
}

となっていたのを、

THAT{
// ここでテスト失敗しても
} AND_THAT
{
// ここも実行する
} OVER // ただし、最後にOVERと書く必要がある

としてみました。

まとめてサンプルにすると

DESCRIBE("int")
{
    VERIFY{
        THAT{
            IT(1+1)SHOULD(==2);
            IT(2*3)SHOULD(==6);
            IT(1/2)SHOULD(==0.5);
        }
        AND_THAT{
            IT(!)SHOULD("This is executed and be failed");
        }
        AND_THAT{
            IT(!)SHOULD("...and this is executed too and be failed");
        }
        OVER
    }
} END_DESCRIBE;

てな感じです。

実行するとこの部分は

//// int ////
Test(155) OK : 1+1==2
Test(156) OK : 2*3==6
Test(157) NG : 1/2==0.5
Test(160) NG : !"This is executed and be failed"
Test(163) NG : !"...and this is executed too and be failed"

と表示されます。

なかなか良さげです(と自画自賛)

あとは

LAMBDA{
}SHOULD_THROW(...)

みたいなのを入れられたら、もう自分で使う分には充分だなぁ。