フェルマータ

個人用のメモ。ソフトウェアの導入とかが多くなる予定。

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 で配布はバイナリとソースコード。以下からダウンロードできる。

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から読み込むことができるのでそれなりに恩恵がありそう。あとは適当にインタフェースを整えてやればよい。僕の実装は以下にあるのでご参考までに。