ゆとりーなの日記

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

アリス・マーガトロイドはロマサガで初めて見た

上の事実に帰宅途中に気付きました。因みに今日は昨日安定してカルドセプトをやるという事態に陥ったので、結果はひどかったですね。残当です。帰宅途中と言えば、帰宅に利用した3路線が全て7〜8分遅れるという事態に直面しました。こんな日もあるんですね。

C++のどっち派特集

特集第3弾です。なんか特集と銘打ったからには連続で3回くらいやらないとまずいかなとか勝手に思ったので。今回はC++のどっち派です。

const char* p VS char const *p

次の中仲間はずれはどれでしょう?

const char* p; // 1
char const *p; // 2
char* const p; // 3

答えは3ですね。こいつだけポインタの値自体が固定だという決意表明、他はポインタが指している物が固定だという決意表明です。つまり、1と2は同じです。さて、先ず最初のどっち派はこれです。個人的なには前者の方が圧倒的に多い気がします。というか後者の書き方を解説記事以外のソース内で見た記憶がありません。

char* p VS char *p (char& p VS char &p)

これは結構割れるんじゃないんですかね。これもどちらで書いても同じ意味になります。どっちの方が見かけるかと聞かれても5分5分位ですかね。因みにCプラーの聖典である「Effective」シリーズは前者の表記ですね。個人的にはchar*の方が*まで含めた型名として考えることが出来るので好きだったりするのですが、実はこの考えは全然正しくないので、以下の様なコードで危険が。

char* p, c;
// pはポインタ
// cはただのchar

cもポインタだと思ってしまいそうです。余談ですが、この*の文法がポインタを分かり難くしている元凶な気がします。*が色んな意味でつかわれるのが厄介です。宣言の仕方もこんな感じで分かりにくいですし。ソース見ても慣れるまで*追っかけるのが大変で、肝心なポインタの概念が分からないという時代が私にもありました。因みに最近は後者で統一して書くことの方が多いです。

char* str = "The World!" VS const char* str = "The World!"

文字列リテラル宣言のどっち派です。どっちを良く見かけるかと言われると、雰囲気char* str = "The World";の方が多い気がします。ですがこれに関してはconst付けた方が幸せになれると思います。というのも環境によってはchar* str = "The World!";を読み込み専用領域に確保する処理系があるらしいのです。strを使って添え字アクセスして文字列を書き換えようものならまあお察しください。というわけでそもそも添え字アクセスで文字列を書き換えられないconst char* str = "The World!";を使った方が絶対幸せになれます。噂によると0xではconst附いてないとコンパイラが怒ってはじくらしいですよ。

++i VS i++

インクリメントは前置か後置かのどっち派です。これも文字列リテラルの時と同様若干意味が変わってくるのですが、そこまで気にして使うことはほぼないんじゃないですかね。特に変数を評価してからインクリメントするか、インクリメントしてから変数を評価するかなどを一々考えて使い分ける人は殆どいないのではないでしょうか。コーディングの統一を掲げてどちらかに統一する場合が圧倒的に多いと思います。個人的な印象ではC系列のソースでは後置が、C++系列のソースでは前置が多いような気がします。Cの場合はインクリメントするのは組み込み型かポインタ位のものなのでどっちでも大して変わらないのですが、C++ではインクリメント演算子オーバーロードされていると、まさかのクラスがインクリメント出来ます。このとき前置と後置では効率に大きく差が出るとされます。後置だと余分なコピーが発生するんです。これ、実は評価してからインクリメントするとかの話と絡んでくるんですが、取り敢えず後置インクリメントは余分なコピーが発生するんです。前置では発生しません。というわけで効率を重視するCプラーは当然前置一択になるわけです。
このことはオーバーロードの宣言と前置、後置インクリメントの仕様を見れば分かりやすいと思います。

//前置インクリメント
//インクリメントしてから変数を評価するので
//インクリメント後の*thisを返すことになる
Object& Object::operator ++() {
//Objectの何かをインクリメント
  return *this;
}

//後置インクリメント
//変数を評価してからインクリメントするので
//インクリメントする前の値を別に取っておいてから
//インクリメントしてインクリメント前の値を返すことになる
Object Object::operator ++(int) {
  Object temp = *this;
//Objectの何かをインクリメント
  return temp;
}
int a(0) VS int a = 0

変数初期化のどっち派です。int a(0)とかあまりなじみないかもしれませんが、これでもちゃんと0で初期化されます。因みに私はint a(0)という形式で初期化しているソースを見たことないです。でも私はこちらの構文の方が好きです。クラス初期化の構文と合わせられるからです。個人的には=で初期化するのは代入と構文が同じなのに呼ばれるのが代入演算子ではなくてコンストラクタというのに凄く違和感があるんですよね。だからstd::stringなんかでも

std::string str("The World!");

の方しか使わないですね。組み込み型の初期化もこれに合わせようと最近になって思い始めた次第です。しかし違和感はやっぱりありますね。組み込み型の=初期化に関してはC時代から馴染みのある構文ですから。そもそもはコピーコンストラクタと代入演算子が使い分けられるC++の方に問題が、っと確か効率を重視した結果こういう仕様になったんでしたっけ。

引数なし関数の()内にvoidを書く VS 書かない

両方見かけますね。void書かない方が多いですかね。C++では()内に何も書かないと(void)と同じ意味になります(厳密には違うという説も)。よってタイプ量を減らしたいCプラーは省略します。私なんかはC++で(void)って書いてあると気持ち悪いですね。コンストラクタとかデストラクタに書いてあるとなんだかな〜と思います。
注意しなきゃいけないのがCだと()のみだと引数省略という扱いになることですね。(void)の省略ではないのです。つまり、次のように関数が宣言されていると、

void func();

int main(void) {
  func();
  func(0);
  return 0;
}

どっちの呼び出しもコンパイラに怒られません。自分でしっかり管理しなければいけません。因みにこの事実を使った怪しい技法があります。DLL関数の動的呼び出しです。"toki.dll"に

int __stdcall HokutoUzyoHaganken(int sityousei, int hp, double aura);

という関数があったとします。こいつを動的に呼び出すときには次のようになります。

int a, b;
HMODULE dll = LoadLibrary("toki.dll");
FARPROC func = GetProcAddress(dll, "HokutoUzyoHaganken");
a = func(0, 10000, 2.0); // CだとこれでOK
b = reinterpret_cast<int (__stdcall *)(int, int, float)>(func)(0, 10000, 2.0); // C++だと要キャスト

FARPROCの宣言は、

typedef int (*FARPROC)();

ですので、Cであれば引数省略なので何入れてもOK。沢山の種類の関数を呼ぶ時一々キャストしなくてもいいのは結構便利です。一方C++であれば、

typedef int (*FARPROC)(void);

になってしまうのでキャストしなければいけなくなります。これに関してはCの方が便利な気がします。引数間違えてても何も言われませんけど。

caseを {}でくるる VS くくらない

これつい最近知ったんですが、switch-case構文をこんな風に括弧で囲う書き方があるみたいです。変数名が普通に日本語ですが脳内でアルファベット全大文字なりに適宜変換してください。

switch (トキ必殺技) {
  case 北斗有情破顔拳: {
    // なんか処理
    break;
  }
  case 北斗砕破拳: {
    // なんか処理
    break;
  }
  case 北斗有情断迅拳: {
    // なんか処理
    break;
  }
  default: {
    // なんか処理
    break;
  }
}

この書き方だととある恩恵を受けられます。通常の括弧なしだと、

switch (トキ必殺技) {
  case 北斗有情破顔拳:
    int a; // コンパイルエラー
    // なんか処理
    break;
  case 北斗砕破拳:
    // なんか処理
    break;
  case 北斗有情断迅拳:
    // なんか処理
    break;
  default:
    // なんか処理
    break;
}

case構文内で変数の宣言が出来ません。ところが括弧で括るとあら不思議、

switch (トキ必殺技) {
  case 北斗有情破顔拳: {
    int a; // コンパイルエラーにならない
    // なんか処理
    break;
  }
  case 北斗砕破拳: {
    // なんか処理
    break;
  }
  case 北斗有情断迅拳: {
    // なんか処理
    break;
  }
  default: {
    // なんか処理
    break;
  }
}

なんか宣言できます。これ結構便利だと思うんですが、実際に使われているソースは矢張り見たことないですね。

クラスの最初はprivate VS public

classのデフォルトアクセス指定子はprivateです。というわけで何も書かずにメンバを連ねると自動的にprivateメンバになります。publicメンバはprivateメンバの後に書いていこうというのが前者です。一方後者はわざわざ最初にpublicを書いてpublicメンバを並べて後に、再度privateを書いてprivateメンバを並べるという方針です。前者の主張は、せっかくのデフォルトを活かしてタイプ量を減らそうぜという感じですかね。後者と比較すると8文字ほど打つ文字数が減りますかね。後者はクラス利用者が知りたいのはpublicメソッドなんだから上に書いといてあげようという有情なスタイルです。最近はこちらの方が圧倒的に流行ってますかね。前者の書き方は入門書位でしか見たことありません。

初期化リスト VS コンストラクタ内代入

恐らく今日最後のどっち派です。メンバ変数の初期化を行う場所の話です。とはいえ普通は初期化リストが使われますかね。なんせクラスは代入で初期化しようとすると、デフォルトコンストラクタで初期化した後代入するという二度手間になるので効率を気にするCプラーは敬遠することが多いはずです。代入に頼るとしたら、コンストラクタとメンバ変数が大量にあるが、どれも殆どのメンバ変数は同じ値で初期化するという場合ですかね。そういう時はinitメソッド内で代入処理を纏めたくなります。因みに組み込み型は初期化リスト使っても代入で初期化しても差はありません。しかし「Effective」では全メンバ変数は初期化リストで初期化しておくことが推奨されてますね。これに関しては従っておいて損はないと思います。あとはメンバ変数の初期化順は初期化リストに並んだ順番ではなく宣言順だということに注意しておけば、幸せな初期化リストライフが送れるはずです。要は宣言順に初期化リストを並べましょうということです。
第3回もおしまいです。あと何回続くんですかね。もう流石に終わりそうですよ。このシリーズ。