PPPUC++#10

8 Technicalities: Functions, etc.

ようやく言語の説明系

8.1 Technicalities

  • これまで可能な限り、言語ではなく、プログラミング自身に焦点を当ててきた
    • 自然言語風に言うなら、文法やなんかはとりあえずおいておいて、良い小説の内容とか書き方についてやってきたような感じ
  • この本は言語が主役の本じゃあないです。しかし、そろそろプロとして、C++を「外国語」じゃあ無くしたい。
    • 結局プログラミングとはアイディアを表現する事であって、プログラミング言語なんてのは単なる道具でしかないのだ、これ重要。
    • 唯一の正解なんて無い
    • C++だって完璧じゃない。どんな言語でもそう
    • でも、ここで習う事は他の言語にも使えるよー、という話
      • この周辺(手続き型とかオブジェクト指向とか)の知識と言うのは大抵どの言語でも共通で、しかも大抵の言語がCの影響を少なからず受けているってのはあるなあ
      • 実際そのせいか、ある一定量プログラミング言語を覚えると途端に学習速度が上がるってのはある。たとえ「なでしこ」や「ドリトル」であっても。
      • ああ、あの言語のあれみたいなもんか、という理解ができるからか
  • そうそう、この章の例で出てくる変数名とかは全くの無意味なのがおおいけど、それは言語の説明に集中したいから。
  • ちなみにここでC++フルセットを説明する訳ではありません。

8.2 Declarations and definitions

あとは退屈なのが続くのでかなりはしょって・・・

  • 宣言は「その名前」を「現在のスコープに入れる」操作
  • 定義はその名前の内容(実装)を書く操作
  • 定義は宣言でもある
  • 同じ名前宣言は何度でもできるけど(無意味だが)定義はダメ
    • というわけで動的言語みたいに後からクラスに追加、とかできない。したければ継承必要。
      • きっと悲劇を生む一子相伝の(嘘
      • そう考えると Ojbective-Cのカテゴリっていうのは上手い方法だなあとか。自由度高すぎてどれ使っていいかわからなくなるけど
  • 定義と宣言はなぜわかれているか?
    • それはI/Fと 実装を分けられるようにするため。
  • externとか滅多に使わねー(とのこと。グローバルいうな方向ですね)
    • extern "C"ってのは やっぱ Mr.S的には Bad なんだろう
  • 宣言はフットプリントに影響しない。定義はメモリを消費する。
  • 分割コンパイルの話
  • なぜ前方参照必須なのか(使用前に宣言が必要なのか)
    • 技術的な問題。逃げた(とは書いてませんが・・・)
      • クラスメンバでは大丈夫なのに・・・
    • 本読むときもそうだよねとか、ちょっと苦しいかも。まあ外部関数と内部関数が混ざる場合にカオス、というのはわかるけれど(クラスの場合スコープが限定される・・・けれども結局はグローバル空間を考えなきゃいけないわけで同じだよね???)、今でもコンパイラの効率ってのはそこまで気にしなければいけないものなのかな
8.2.1 Kinds of declarations

変数とか定数とか関数とか名前空間とかクラスとかテンプレートとか。

8.2.2 Variable and constant declarations
  • 変数で宣言するもの: 名前、型、(オプションで)初期化値
    • さりげなくまた自著の宣伝 ^^;
  • 定数も一緒。 const つけるだけ。
    • 定数の場合、初期化値が必須
  • 変数でもなるべくなら初期化をつける習慣づけを。初期化漏れはバグの温床
  • 故意にやる事は無いだろうけれど、こういうミスはよくあります。ので習慣にすべし、と。
      • 指に覚えさせろ、というやつですね
    • 繰り返しですが、こういう「アホですか」ミスは疲れてるときとか忙しいときに起こります。必ず初期化。習慣にしてください。
8.2.3 Default Initialization
  • 今までの例でも初期化してない奴あったじゃんとか
  • これは例外?  いえいえそんなことはありません。
  • 明示しなかった場合でもデフォルト値での初期化が起こるのです。
    • たとえば string なら "" 。 vector だったら空っぽのリスト
      • この仕組み、デフォルトコンストラクタについては9.7.3にて
  • 組み込み型にはそんな保証は無いのだ。いかんねえ
  • グローバル変数はデフォルトで0に初期化されるけれども、なるべくグローバルは使わないほうが良い
    • うーん0に初期化されるのはUN*X系のみじゃないかな???
  • 最も使用頻度の高いローカル変数やクラスメンバは基本的にノー初期化。明示的にやるようにしよう!

8.3 Header files

  • 宣言だとか定義の管理をどうするか?
    • 何百もの宣言を毎回ソースに入れるのですかそうですか? しかも他人が書いた奴までコピペですかそうですか?
  • というわけで分離しましょうと。
  • C系統ではヘッダファイルですね。よく*.hが使われます。これに宣言を全て入れておいて、実装はcppファイルで。
    • 拡張子 .h は必須じゃなくなんでもいいんですが(実際標準ライブラリはそうなってませんし、boostなんぞhppですね)けれども、いくつかのコンパイラおよび、多くの統合環境とかではそれを前提としている場合があります。
    • #include がやっているのは単にその場所に指定されたファイルを展開しているだけなので、実はヘッダの位置じゃなく、どこでも使えます。配列の内容定義とかで使っている邪悪な例をいくつか・・・
  • includeはコンパイル前に起こるのでpre-processingとも。
    • ヘッダは実装者と利用者でおなじものを使うのに意味がある。一貫性チェックになる。
      • これにテストコードをつけると検証可能な仕様書のできアガリなわけですね
  • ヘッダファイルは多数のソースからインクルードされるので、複数ファイルに存在しても良い宣言のみを入れること
    • 関数宣言、クラス宣言、数値定数などの定義

8.4 Scope

  • スコープとはプログラムテキストのある領域のこと
  • 変数や定数などの名前は、宣言されたところから有効(in scope)でスコープを出たところで無効になる
    • スコープはネストの内側まで届くので、 {{{の中からでも外見えますよー}}}
  • スコープは名前の衝突を防ぐため
  • 大きい順に
  1. global scope
  2. namespace scope
  3. class scope
  4. local scope
  5. statement scope
    • statementスコープってなんだ?と思ったらfor (int i = ...)のスコープのことらしい local scopeの一種だと思い込んでたよ。たしかに {}なしでもスコープもつものね。
      • scopeって何?っていうと参照可能なシンボルテーブルの場所とその階層なのですよね。グローバルは単なるヒープのアドレス空間名前空間はグループ化されたアドレス空間, クラス空間はヒープ上のthis(オブジェクトのアドレス)からの相対アドレス空間, ローカルは伝統的にはスタックに作られたスタックフレーム上の相対アドレス空間、statementもおそらくローカルと同じ
    • グローバルいうな! は結局犯人の特定が難しくなるからなわけで、あなたが名探偵なら問題ないわけですね
    • nested block についてはCの時にはブロック単位でしかスタックフレームの構築(というかローカル変数)ができなかったので必要だったと思うのですが、C++になってから何か意味あんの? というのがわかりません

8.5 Function call and return

  • 手続きの抽象化・関数化。名前つけるだけの意味がある手続き・動作があったら関数化
    • c++の要素として、結果を出す演算、と、その順番を決める制御文があるけれど、これらをひとつにまとめるものが関数
8.5.1 Declaring arguments and return type

まあそのまんま特に書くこともないんだけど

  • 宣言でも定義でも引数の宣言時に名前をつけるのは任意。どっちでもイイ。

というのが、あー定義でもなのかー、と驚いた。何に使うんよ、と思ったら

  • 例えば myfind(vector vs, string s, int hint) とかいう関数があったとして、このhint情報で大体の位置を教えてあげることでパフォーマンスアップする事になっていたとする
    • しかし実際に使ってみるとほとんどのコードでこのhintは使われていない。こうなるともう単なるダメ機能なので削りたい
    • すでに多数のコードがこのhintがある物としてコーディングされてしまっているのでI/Fは変えたくない。

というようなとき、単純にこのhintを使わなくすればいいんだけど、こんな時に最後の引数を intだけにしておく、というような使い方ができるらしい。

    • ええっこれが何の役に??? というと1つは間違いなくこの値を使ってない(アクセスのしようがないんだから)、という保証ができる事と、うるさい警告を黙らせる事に成功することくらいか。
Buddha:~ tkuro11$ g++ -Wunused-parameter unused_and_named.cpp 
pppp.cpp:4: warning: unused parameter ‘c’

Buddha:~ tkuro11$ g++ -Wunused-parameter unused_but_not_named.cpp 
Buddha:~ tkuro11$ 
    • おおおおおお、ウォーニング消えたぞー
      • 誰がつけるんだこんなスイッチ・・・
8.5.2 Returning a value

これもなんのこっちゃな節。

  • 戻り値ある関数はちゃんと返しましょうね、とか。 if がこんがらかっているとなかなかにハードになるかも
  • Tと宣言してあって、ある型Vで返した場合、初期化と同じ事が起きる
  • main()は歴史的理由で 何も書いてない場合は 0 が返る、とか

そりゃそうだよなー、と言う話。

8.5.3 Pass-by-value
  • いわゆる値渡し
  • コピーするから関数内で書き換えても大丈夫だよ、とか
    • あとコストもコピー分だけだよとか

うーん

8.5.4 Pass-by-const-reference
  • でかい vector返そうとしたら???
    • 数百MBの構造を関数呼ぶたびにコピーってどうなんよ
    • というわけでポインタ・・・
    • ではなくて 参照 を使いましょう
      • それも constです。これは。。。良いものだ
  • みつけにっくいバグというのは基本的に 嫁無い、見栄無い コード(ん?)なわけで、constで切り分けておけば嫁無くとも見栄無くとも大丈夫なのでがんばりましょう(?)
8.5.5 Pass-by-reference

うきゃークドい

  • 初期化関数なんかでは結構参照先を書き換えるケースも出てくるよね。という話
  • 参照は複雑な式の aliasとしてもつかえますよーとか。
    • 関数型の let みたいですね
  • 気をつけないとわかめなバグの大量生産ですごめん
  • ありがちなんだけど swap()とかの話
    • stdlibにもあるよーって
8.5.6 Pass-by-value vs. pass-by-reference
  • pass-by-value , pass-by-reference, pass-by-const-referenceの三つ巴
    • いつ、どれ、つかう?
  • 小さいオブジェクトなら value
  • でかいオブジェクトなら const-reference
  • どうしても書き換えたい時だけ reference
    • 普通は戻り値を使いましょう
  • しかしここで値を変えるとは?
    1. 副作用系:呼び出し元の値を変える?
    2. テンポラリ系:単に一時記憶として値を変える?
  • rvalue, lvalueの話, referenceは lvalueを要求するが(locateが必要なので)、 const refはlvalueじゃなくても良い
    • ただし、その場合一時オブジェクトができちゃうのでそのつもりで
8.5.7 Argument checking and conversion
  • 引数を渡す際には必ず初期化が行われる
    • その際に暗黙の変換が怒ってしまう事があるので注意、という話
  • double -> int とかダウン方向の場合は明示的にコードに書くべき
    • それが他人が読む事を前提にコードを書くプロの姿勢だ