ゆとりーなの日記

日記的な事を書いて行くと思はれる

爆死

発吸の二択を見誤ったのが痛いですね。まあ或る意味予定通りなところもあるんですが。

C++マイナー予約語特集

なんか急にこれやりたくなったのでやっちゃいます。C++入門書には余り出てこない子達の特集です。といっても私は独習しか読んだことないので他の入門書では当然の如く載っているのかもしれませんが。あと私は頭が弱いので独習に載っていたことすら忘れている可能性もあります(追記:案の定忘れていただけでした)。

explicit

引数が一つのコンストラクタには基本的に付けることが推奨されます。これが附いているかどうかは、Cプラーのステータスの一つです。

class DameObject {
public:
  DameObject(int i); 
};

class IiObject {
public:
  explicit IiObject(int i);
};

何が違うんでしょうか。以下の関数の以下の呼び出し時にコンパイラが返す態度に違いが生じます。

void DameInt(const DameObject &obj);
void IiInt(const IiObject &obj);
int main() {
  DameInt(4); //コンパイル通る
  IiInt(4);   //コンパイルエラー
  return 0;
}

DameIntはコンパイル通ります。IiIntは通りません。これだけの違いです。DameInt呼び出しではDameObject(4)で作られたオブジェトが飛んで行ったと考えればいいです。多分。explicitをつけないとint型の値をぶち込んだ時に勝手にintを引数に取るコンストラクタが呼ばれるという謎の拡張が行われます。なんか便利そうですが、実際は紛らわしいだけなので、何も気にせずexplicitをつけましょう。実際色々な所で言われているので最近は有名になってきましたexplicit。付けてなかった人はこれを機に是非。あと間違ってもコピーコンストラクタには付けてはいけません。

virtual

仮想関数ではありません。仮想継承です。まあこれは流石にどこにも載ってますね。実際使われているのは殆ど見ませんが。

class File {
};

class IFile : public File {
};

class OFile : public File {
};

class IOFile : public IFire, public OFile {
};

上みたいなこと、誰でも一度はやろうと思ったことがあるのではないでしょうか。やるとなんと、IOFileに対してFileが二つも出来てしまいます。所謂ダライアス継承*1の弊害です。
そこでC++では以下の様な対策をします。

class File {
};

class IFile : public virtual File {
};

class OFile : public virtual File {
};

class IOFile : public IFire, public OFile {
};

publicの後ろにvirtualを付けるとIOFile内でFileは一つしかできません。このことからpublic継承には全てvirtual付けようぜという人もいますが、仮想継承のオーバーヘッドはやばいらしいのと、普通滅多に多重継承はしないので、したくなったら渋々付けるといった程度で良いでしょう。因みにこの例で言うとFIleにデータメンバを持たない方がいいみたいです。初期化やコピーが複雑になるからです。ところがこの例だとFileにファイル操作を管理するデータメンバを持たせたくなります。もしかするとこの例は余り良い設計とはいえないのかもしれません。

const

constは流石に有名ですが、それは定数や引数についているやつのこと。メンバ関数のお尻についているのはあまり見かけない気がします。

class Toki {
public:
  Toki();
  bool canFaitalKO();
};

void checkFaital(const Toki &toki) {
  if (toki.canFaitalKO()) {
  }
}

上の様なコードを書いたとします。Tokiオブジェクトを渡すcheckFaital関数。値渡しは頭悪いので参照、内容書き換える予定もないのでconstを付ける。ここまでの流れは順当です。しかしtoki.canFaitalKO()呼び出し、ここでコンパイルエラー。残当です。フェイタルK.O.可能か調べるだけなのになんでだと思うものの、諦めてconstを外すのは間違いです。canFaitalKOのお尻にconstを付ければいいんです。

class Toki {
public:
  Toki();
  bool canFaitalKO() const;
};

これで問題はなくなります。所謂constメンバ関数です。このメンバ関数内ではメンバ変数を書き換えるコードはコンパイラにはじかれます。

class Toki {
public:
  Toki();
  bool canFaitalKO() const {
    canFaitalKO_ = true; //コンパイルエラー
  }
private:
  bool canFaitalKO_;
};

取り敢えずメンバ変数を書き換えないメンバ関数のお尻にはconstを付けておくと幸せになれます。まあ厳密にはメンバ変数は変わっていないけどオブジェクトの見た目が変わるメンバ関数にconstを付けるべきか等色々あるんですけど。

mutable

これは存在意義が未だによくわかりません。先のconstメンバ関数内でも書き換えられるメンバ変数であることを示します。

class Toki {
public:
  Toki();
  bool canFaitalKO() const {
    canFaitalKO_ = true; //コンパイルエラーではない
  }
private:
  mutable bool canFaitalKO_;
};

折角constにしたのに何で書き換えられるんだというのが個人的な印象です。我こそはmutableの真髄と思われるコードは是非コメント欄に出現してください。

protected

今日の最後です。protectedです。この子、当然継承にも使えます。private継承はコピー禁止クラスの手法等で割と有名ですが、protected継承は何に使うのか未だにわかりません。一応纏めておくと

class Parent {
public:
  Parent();
  int a();
private:
  int a_;
};

class Child : protected Parent {
public:
  Child();
  int b() {
    int c = a(); //呼べる
  }
};

class GrandChild : public Child {
public:
  GrandChild();
  int c() {
    int d = a(); //呼べる
  }
};

int main() {
  Child child;
  child.a(); //呼べない
  return 0;
}

こんな感じです。継承元のクラスのpublicメンバが継承後のクラスではprotectedになります。因ってmainからa()は呼べなくなります。これだけです。そもそもprotectedメンバすら滅多に使わない私にはprotected継承を使わなければいけない状況が思いつきません。こちらの方も是非コメント欄に出現してくれると有難いです。

ふぅ。おしまいです。

追記:mutableについて。
昔以下のコードでは諦めてconst取ってました。

class StreamingPlayer {
public:
  float volume() const;
private:
  float volume_;
  boost::mutex mutex_;
};

float StreamingPlayer::volume() const {
  boost::scoped_lock(mutex_); //コンパイルエラー
  //volume_にボリューム値を書き込む処理
  return volume_;
}

再生中のBGMのボリュームを取得するメソッドで当然このようなメソッドはconstを付けることが推奨されます。しかしストリーミング再生ではマルチスレッドを実装で使っているため安全の為にboost::mutexを使ってロックしてやろうと考えました。と、ここでコンパイルエラーです。constメンバ関数内ではboost::scoped_lock(mutex_);がエラるんです。諦めてconst取って対処していたのですが、さっき調べたところこういう時こそのmutableらしいです。

class StreamingPlayer {
public:
  float volume() const;
private:
  float volume_;
  mutable boost::mutex mutex_;
};

これでエラーはなくなります。

*1:菱形継承のこと