C++ でエラー処理書いてたら死にそうになったので Scala の Try パクってみた。
ポエム
業務で Scala を書いてるせいか、趣味で C++ 書いていると C++ のエラー処理のヤバさにつらみが溜まるし発狂したい。こういうのほんとうに辛い。
/** * path のファイルに書かれた文字列を返します。 * エラーがあった場合は err に値が書き込まれます。 */ string load(const string &path, int *err);
異常系の処理が貧弱すぎてつらい。何が嫌かって、きっとこの後ファイルの内容をパーズするわけで、 こんな感じの関数が作られるに違いない。
/** * str を json に変換します。 * エラーがあった場合は err に値が書き込まれます。 */ json parse(const string &str, int *err);
実際に使われるのはきっとこんな感じになるに違いない。
void main() { int err; string str(load("hoge.txt", &err)); if(err != 0) { printf("File load error!!\n"); exit(-1); } json j(parse(str, &err)); switch(err) { case ... } }
もうこんなの絶対発狂したい。でも例外使って try catch のハンドリングも面倒くさい。
Scala の Try パクった
上の例だとこんな感じになる。
Try<string> load(const string &path); Try<json> parse(const string &str); void main() { Try<json> result(load("hoge.txt").flatMap([](const string&str) -> Try<json>{ return parse(str); })); if(result.isFailure()) { exit(-1); } ... }
面倒くさいけどまあ伝統的な形式よりは…
一応テストも書いたし動く模様だ。でもあんまり効率的じゃなくて重そうだな、とは思った。 あと、例外投げるところでメモリリーク発生してるよねこれ。まあいっか。
qHash 関数と名前空間
概要
名前空間内で定義したクラスの qHash 関数の定義で手ひどい罠を踏んだので書いておく。
罠
これはダメなんだってさ。
namespace hoge { class Hoge { public: int foo; } } bool operator == (const hoge::Hoge &left, const hoge::Hoge &right) { return left.foo == right.foo; } unsigned int qHash(const hoge::Hoge& h) { return qHash(h); }
こうすると行ける。
namespace hoge { class Hoge { public: int foo; } bool operator == (const Hoge &left, const Hoge &right) { return left.foo == right.foo; } unsigned int qHash(const Hoge& h) { return qHash(h); } }
そっか。
参考資料
[QTBUG-34912] QHash compile error when using a key that is a class in a namespace - Qt Bug Tracker
音源のフォーマット考えている
Corpus-+--CorpusMetaInfo-+-name 音源名(言語別) | +-version バージョン名 | +-iconPath アイコンファイル名 | +-samplePath サンプル波形のファイル名 | +-author 製作者(言語別) | +-web 公開ページ | +-license ライセンス文(言語別) | +-description 自由記述(言語別) +--PhonemeSet-+-phoneme[0] 音素片 -+-pronounce 発音 | +-label なんか識別子 | +-path ファイル名 | +-type 音素片種別 CVCとか | +-offset 左ブランク | +-length 音素長 | +-preutterance 位置補正 | +-loopBeginMs 左固定長 | +-koopEndMs 右固定長 | +-MusicalContext-+- noteNumber | +- brightness | +- velocity | +- tempo | +- duration +-phoneme[1] 音素片 -+-... | +-... ... ...
こういう構造を考えた。
追記:@maruLoop氏に指摘されてライセンス・バージョン追加
nginx の reqtime が 5 秒のところにピークがある。
応答遅延と思ったら…
5 秒のとこでピークあってどこかで詰まって返せてないのかなと思っていろいろ調べてたんだが、 User-Agent にもばらつきはないし、パスにもばらつきもないし原因が分からずずっと悩んでいた。 もう分からないので人に聞いたところ nginx には lingering_timeout という設定があるんだけどそれじゃね?と言われた。
見たとこクライアントからのデータ送信が途切れてから、lingering_timeout の分だけ待ったのち、 接続を遮断するような設定に読める。これのデフォルト設定が 5 秒。 内部的な話は調べていないのでわからないのだが、 クライアントが不意に切断された、みたいなときに nginx 的に reqtime が lingering_timeout + αみたいになるのだろうか。
とりあえず僕が悩んでいた件は指摘を受けてこれだったのでメモ書きにでも残す。
C++ の参照型で多態は効くのか。
経緯
ブログも久々だが C++ も久々で、書いているとどうにもポインタを触りたくないために、できるだけ参照型で物事を済ませたかった。のだが、そういえば参照型って多態効くんだっけと思って調べたがググらビリティが低かったので自分で調べた。
結果
効く。
test.cpp
#include <stdio.h> class Base { public: virtual ~Base() {} virtual void func() { printf("Base class function.\n"); } }; class ChildA : public Base { public: ~ChildA() {} void func() { printf("ChildA class function\n"); } }; class ChildB : public Base { public: ~ChildB() {} void func() { printf("ChildB class function\n"); } }; int main() { Base base; ChildA a; ChildB b; Base &baseRef(base); Base &aRef(a); Base &bRef(b); baseRef.func(); aRef.func(); bRef.func(); }
result
>g++ test.cpp >a.exe Base class function. ChildA class function ChildB class function
へー。まあでもそれもそうか。一安心。
Specs2 の Mockito でデフォルト引数つきのメソッドの動作定義がメソッドの呼び出し回数にカウントされる。
経緯
Scala でモックを使用したテストコードを書くときにに Mockito を使っている。Mockitoは大変便利で、 Specs2 で使う場合は以下のように簡単にメソッドの動作を定義できる。
val mockedHoge = mock[Hoge] val bar: Argument = new Argument() val result: Result = new Result() mockedHoge.foo(bar) returns result // Write a test with mockedHoge there was one(mockedHoge).foo(bar)
これはHoge
クラスをモックにして、Hoge#foo
が引数bar
で呼び出されたとき、result
を返すモックを作成している。また、テスト後にmockedHoge.foo(bar)
が一度だけ呼ばれていることを確認するテストである。とても簡単に書けて良い。
問題点
しかしデフォルト引数が入ると話がややこしくなる。以下のコードは適切な引数を受け取ると、そこからエンティティを作成し expire time つきのレポジトリに登録するサービスクラスである。
- EntityRegisterService.scala
package org.hal.stand import scala.concurrent.Future case class ExampleEntityId(value: Int) case class ExampleEntity(id: ExampleEntityId) class ExampleRepository { def set(entity: ExampleEntity, expiresInSecond: Int = 600): Future[Unit] = { Future.successful() } } class EntityRegisterService(repository: ExampleRepository) { def register(id: Int): Future[Unit] = { // 今回は引数で与えているが本来は何らかの処理で ID とエンティティを生成する。 val entityId = ExampleEntityId(id) val entity = ExampleEntity(entityId) repository.set(entity) } }
以上のサービスクラスのテストを以下のように書く。
- EntityRegisterServiceSpec.scala
package org.hal.stand import org.specs2.mock.Mockito import org.specs2.mutable.Specification import scala.concurrent._ import scala.concurrent.duration._ class EntityRegisterServiceSpec extends Specification with Mockito { val fakeExampleEntityId = ExampleEntityId(1234) val fakeEntity = ExampleEntity(fakeExampleEntityId) "register" should { "set the entity with the ID specified by the argument to the repository" in { val mockedRepository = mock[ExampleRepository] mockedRepository.set(fakeEntity) returns Future.successful() val service = new EntityRegisterService(mockedRepository) val result = service.register(fakeExampleEntityId.value) Await.result(result, Duration(100, MILLISECONDS)) mustEqual () there was one(mockedRepository).set(fakeEntity) } } }
一見これでうまく行きそうだがうまくいかない。以下の様なエラーが吐かれる。
[info] register should [info] x set the entity with the ID specified by the argument to the repository [error] The mock was not called as expected: [error] exampleRepository.set$default$2(); [error] Wanted 1 time: [error] -> at org.hal.stand.EntityRegisterServiceSpec$$anonfun$1$$anonfun$apply$3$$anonfun$apply$5.apply(MockitoTrapSpec.scala:20) [error] But was 2 times. Undesired invocation: [error] -> at org.hal.stand.EntityRegisterService.register(MockitoTrap.scala:19) (MockitoTrapSpec.scala:20)
どうもモックの動作定義時に一度メソッドが呼び出されたことになるらしく二回呼び出しされてエラーになってしまう。
解決方法
デフォルト引数やめよう。
- EntityRegisterService.scala
package org.hal.stand import scala.concurrent.Future case class ExampleEntityId(value: Int) case class ExampleEntity(id: ExampleEntityId) class ExampleRepository { def set(entity: ExampleEntity, expiresInSecond: Int): Future[Unit] = { Future.successful() } } class EntityRegisterService(repository: ExampleRepository) { final private val expireInSecond = 600 def register(id: Int): Future[Unit] = { // 今回は引数で与えているが本来は何らかの処理で ID とエンティティを生成する。 val entityId = ExampleEntityId(id) val entity = ExampleEntity(entityId) repository.set(entity, expireInSecond) } }
- EntityRegisterServiceSpec.scala
package org.hal.stand import org.specs2.mock.Mockito import org.specs2.mutable.Specification import scala.concurrent._ import scala.concurrent.duration._ class EntityRegisterServiceSpec extends Specification with Mockito { val fakeExampleEntityId = ExampleEntityId(1234) val fakeEntity = ExampleEntity(fakeExampleEntityId) "register" should { "set the entity with the ID specified by the argument to the repository" in { val mockedRepository = mock[ExampleRepository] mockedRepository.set(fakeEntity, 600) returns Future.successful() val service = new EntityRegisterService(mockedRepository) val result = service.register(fakeExampleEntityId.value) Await.result(result, Duration(100, MILLISECONDS)) mustEqual () there was one(mockedRepository, 600).set(fakeEntity) } } }
今回のはサービス層で与えるべき情報がレポジトリ層でデフォルト引数で与えられていて例が大変悪いけど、呼び出し回数がうまくカウントできずに詰んだので備忘録代わりに。
libsndfile + QIODevice で WAVE ファイルの読み書き
経緯
趣味で音声合成っぽいソフトウェアを作っている。いい加減音声ファイルの読み書き部を作らねばならなくなったのだが、C++ WAVE 読み込み 無料
と Google 先生に聞いてもマッチョな結果しか返ってこない。これはこれで興味はあるが、音声合成のドメインに信号列の処理は含まれても信号の入出力自体は含まれないのでもっと手軽に読み書きしたい。
なので OSS 、ただし GPL は除く、で良いライブラリがあるのが理想である。と言うかなんでみんなフォーマットとバイナリ読むことばっかに興味あるんだ。普通にいろいろ簡単に読み込めるライブラリをくれよ。
libsndfile インストール
いくつか探したのだが libsndfile が良さそう。ライセンスは LGPL で配布はバイナリとソースコード。以下からダウンロードできる。
- mega-nerd - libsndfile http://www.mega-nerd.com/libsndfile/
Windows のバイナリは VC++ 用なので MINGW 使いはソースコードのコンパイルをしないといけない。
MINGW + Windows 7 の組み合わせでは MSYS からインストールすれば良い。
適当に MSYS のコンソールから伝統的な./configure && make && make install
でコンパイルしてインストールできるので、 Windows でも簡単に使える。
libsndfile の使い方
普通に使う分には以下のブログ記事が役に立つ。
読んでいただければ分かるが以下のように読み込みを行う。
SndfileHandle sndfile("sample.wav", SFM_READ); sndfile.samplerate(); // サンプリング周波数 sndfile.channels(); // チャンネル数 short int* input = new short int [sndfile.frames()]; sndfile.readf(input, sndfile.frames()); // 16bit 符号付き整数の信号
ファイルから読み込むのであればこれだけで多くのフォーマットに対応できる。ライセンスも良い感じなのだが、しかしながら基本的なインタフェースはファイルしか無い。別にそれほど不自由はないが、できればQIODevice
のようなインタフェースから読める方が拡張性が高いし安心できる。
QIODevice からファイルを読み込む。
libsndfile 的にはQIODevice
からの読み込みは仮想ファイルからの読み込みという扱いになる。この機能は残念ながら C++ 版の libsndfile にはなく、 C 版のインタフェースを使うしか無い。
SF_INFO info{0, 0, 0, 0, 0, 0}; // ファイルの情報が入る構造体 SF_VIRTUAL_IO io = // 仮想 IO のコールバック関数の設定 { callbackLength, callbackSeek, callbackRead, callbackWrite, callbackTell }; QIODevice device; device.open(QIODevice::ReadOnly); // somehow open the device! SNDFILE *file = sf_open_virtual(&io, SFM_READ, &info, &data);
読み込む部分はこれだけで良い。ただしsf_open_virtual
を使うためにはSF_VIRTUAL_IO
に記述されたコールバック関数を実装せねばならない。すなわち以下の部分になる。
sf_count_t callbackLength(void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->bytesAvailable(); } sf_count_t callbackSeek(sf_count_t offset, int whence, void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; switch(whence) { case SEEK_CUR: device->seek(device->pos() + offset); break; case SEEK_SET: device->seek(offset); break; case SEEK_END: device->seek(offset + callbackLength(voidDevice)); break; } return device->pos(); } sf_count_t callbackRead(void *ptr, sf_count_t count, void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->read((char *)ptr, count); } sf_count_t callbackWrite(const void *ptr, sf_count_t count, void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->write((const char *)ptr, count); } sf_count_t callbackTell(void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->pos(); }
ただしこのやり方はストリーミングは一切考慮していない。とはいえQIODevice
から読み込むことができるのでそれなりに恩恵がありそう。あとは適当にインタフェースを整えてやればよい。僕の実装は以下にあるのでご参考までに。