diff --git a/TeX/000-preface.tex b/TeX/000-preface.tex new file mode 100644 index 0000000..ff4c79c --- /dev/null +++ b/TeX/000-preface.tex @@ -0,0 +1,27 @@ +\hyperchapter{ch00}{序}{序} + +本書はプログラミングの経験はあるがC++は知らない読者を対象にしたC++を学ぶための本である。本書はすでに学んだことのみを使って次の知識を説明する手法で書かれた。C++コンパイラーをC++で書く場合、C++コンパイラーのソースコードをコンパイルする最初のC++コンパイラーをどうするかというブートストラップ問題がある。本書はいわばC++における知識のブートストラップを目指した本だ。これにより読者は本を先頭から読んでいけば、まだ学んでいない概念が突如として無説明のまま使われて混乱することなく読み進むことができるだろう。 + +C++知識のブートストラップを意識した入門書の執筆はなかなかに難しかった。ある機能Xを教えたいが、そのためには機能Yを知っていなければならず、機能Yを理解するためには機能Zの理解が必要といった具合に、C++の機能の依存関係の解決をしなければならなかったからだ。著者自身も苦しい思いをしながらできるだけ今までに説明した知識のみを使って次の知識を教えるように書き進めていった結果、意外な再発見をした。ポインターを教えた後はC++のほとんどの機能を教えることに苦労しなくなったのだ。けっきょくC++ではいまだにポインターの機能はさまざまな機能の土台になっているのだろう。 + +本書の執筆時点でC++は現在、C++20の規格制定に向けて大詰めを迎えている。C++20では\texttt{\#include}に変わるモジュール、軽量な実行媒体であるコルーチン、高級なassert機能としてのコントラクトに加え、とうとうコンセプトが入る。ライブラリとしてもコンセプトを活用したレンジ、\texttt{span}、\texttt{flat\_map}などさまざまなライブラリが追加される。その詳細は、次に本を出す機会があるならば『江添亮の詳説C++17』と似たようなC++20の参考書を書くことになるだろう。C++はまだまだ時代に合わせて進化する言語だ。 + +\pagebreak +本書の執筆はGitHub上で公開した状態で行われた。 + +\vskip 1.0zw +\url{https://github.com/EzoeRyou/cpp-intro} +\vskip 1.0zw + +本書のライセンスはGPLv3である。ただし、本書の著者近影はGPLv3ではなく撮影者が著作権を保持している。 + +\vskip 1.0zw +本書の著者近影の撮影は、著者の古くからの友人でありプロのカメラマンである三浦大に撮影してもらった。 + +\vskip 1.0zw +三浦大のWebサイト: \url{http://www.masarumiura.jp/} + +\vskip 1.0zw +\begin{flushright} +江添亮 +\end{flushright} diff --git a/TeX/001-intro.tex b/TeX/001-intro.tex new file mode 100644 index 0000000..af7d0c4 --- /dev/null +++ b/TeX/001-intro.tex @@ -0,0 +1,43 @@ +\hyperchapter{ch01}{C++の概要}{C++の概要} + +C++\index{C++}とは何か。C++の原作者にして最初の実装者であるBjarne Stroustrup\index{Stroustrup, Bjarne}は、以下のように簡潔にまとめている。 + +\begin{quote} +C++は、Simulaのプログラム構造化のための機構と、Cのシステムプログラミング用の効率性と柔軟性を提供するために設計された。C++は半年ほどで現場で使えることを見込んでいた。結果として成功した。 +\begin{flushright} +Bjarne Stroustrup, A History of C++: 1979--1991, HOPL2 +\end{flushright} +\end{quote} + +プログラミング言語史に詳しくない読者は、Simula\index{Simula}というプログラミング言語について知らないことだろう。Simulaというのは、初めてオブジェクト指向プログラミングを取り入れたプログラミング言語だ。当時と言えばまだ高級なプログラミング言語はほとんどなく、\texttt{if else}, \texttt{while}などのIBMの提唱した構造化プログラミング\index{こうぞうかぷろぐらみんぐ@構造化プログラミング}を可能にする文法を提供しているプログラミング言語すら、多くは研究段階であった。いわんやオブジェクト指向など、当時はまだアカデミックにおいて可能性の1つとして研究されている程度の地に足のついていない夢の機能であった。そのような粗野な時代において、Simulaは先進的なオブジェクト指向プログラミング\index{おぶじえくとしこうぷろぐらみんぐ@オブジェクト指向プログラミング}を実現していた。 + +オブジェクト指向は現代のプログラミング言語ではすっかり普通になった。データの集合とそのデータに適用する関数を関連付けることができる便利なシンタックスシュガー、つまりプログラミング言語の文法上の機能として定着した。しかし、当時のオブジェクト指向というのはもっと抽象度の高い概念であった。本来のオブジェクト指向をプログラミング言語に落とし込んだ最初の言語として、SimulaとSmalltalkがある。 + +Simula\index{Simula}ではクラスのオブジェクト1つ1つが、あたかも並列実行しているかのように振る舞った。Smalltalkでは同一プログラム内のオブジェクトごとのデータのやり取りですらあたかもネットワーク越しに通信をするかのようなメッセージパッシングで行われた。 + +問題は、そのような抽象度の高すぎるSimulaやSmalltalkのようなプログラミング言語の設計と実装では実行速度が遅く、大規模なプログラムを開発するには適さなかった。 + +Cの効率性と柔軟性というのは、要するに実行速度が速いとかメモリー消費量が少ないということだ。ではなぜCはほかの言語に比べて効率と柔軟に優れているのか。これには2つの理由がある。 + +1つ、Cのコードは直接ハードウェアがサポートする命令にまでマッピング可能であるということ。現実のハードウェアにはストレージがあり、メモリーがあり、キャッシュがあり、レジスターがあり、命令は投機的に並列実行される泥臭い計算機能を提供している。 + +1つ、使わない機能のコストを支払う必要がないというゼロオーバーヘッドの原則。例えばあらゆるメモリー利用がGC(ガベージコレクション)\index{GC@GC(ガベージコレクション)}によって管理されている言語では、たとえメモリーをすべて明示的に管理していたとしても、GCのコストを支払わなければならない。GCではプログラマーは確保したメモリーの解放処理を明示的に書く必要はない。定期的に全メモリーを調べて、どこからも使われていないメモリーを解放する。この処理には余計なコストがかかる。しかし、いつメモリーを解放すべきかがコンパイル時に決定できる場合では、GCは必要ない。GCが存在する言語では、たとえGCが必要なかったとしても、そのコストを支払う必要がある。また実行時にメモリーレイアウトを判定して実行時に分岐処理ができる言語では、たとえコンパイル時にメモリーレイアウトが決定されていたとしても、実行時にメモリーレイアウトを判定して条件分岐するコストを支払わなければならない。 + +C++は、「アセンブリ言語をおいて、C++より下に言語を置かない」と宣言するほど、ハードウェア機能への直接マッピング\index{ちよくせつまつぴんぐ@直接マッピング}とゼロオーバーヘッド\index{ぜろおばへつど +@ゼロオーバーヘッド}の原則を重視している。 + +C++のほかの特徴としては、委員会方式による国際標準規格\index{こくさいひようじゆんきかく@国際標準規格}を定めていることがある。特定の一個人や一法人が所有する言語は、個人や法人の意思で簡単に仕様が変わってしまう。短期的な利益を追求するために長期的に問題となる変更をしたり、単一の実装が仕様だと言わんばかりの振る舞いをする。特定の個人や法人に所有されていないこと、実装が従うべき標準規格があること、独立した実装が複数あること、言語に利害関係を持つ関係者が議論して投票で変更を可決すること、これがC++が長期に渡って使われてきた理由でもある。 + +委員会方式の規格制定では、下位互換性の破壊は忌避される。なぜならば、既存の動いているコードを壊すということは、それまで存在していた資産の価値を毀損することであり、利害関係を持つ委員が反対するからだ。 + +下位互換性を壊した結果何が起こるかというと、単に言語が新旧2つに分断される。Python 2とPython 3がその最たる例だ。 + +C++には今日の最新で高級な言語からみれば古風な制約が数多く残っているが、いずれも理由がある。下位互換性を壊すことができないという理由。効率的な実装方法が存在しないという理由。仮に効率的な実装が存在するにしても、さまざまな環境で実装可能でなければ規格化はできないという理由。 + +C++には善しあしがある。Bjarne StroustrupはC++への批判にこう答えている。 + +\begin{quote} +言語には2種類ある。文句を言われる言語と、誰も使わない言語。 +\end{quote} + +C++は文句を言われる方の言語だ。 diff --git a/TeX/002-build.tex b/TeX/002-build.tex new file mode 100644 index 0000000..7f4e301 --- /dev/null +++ b/TeX/002-build.tex @@ -0,0 +1,814 @@ +\hyperchapter{ch02}{C++の実行}{C++の実行} + +プログラミング言語を学ぶには、まず書いたソースコードをプログラムとして実行できるようになることが重要だ。自分が正しく理解しているかどうかを確認するために書いたコードが期待どおりに動くことを確かめてこそ、正しい理解が確認できる。 + +\hypersection{ch0201}{C++の実行の仕組み} + +C++は慣習的に、ソースファイルをコンパイルしてオブジェクトファイルを生成し、オブジェクトファイルをリンクして実行可能ファイルを生成し、実行可能ファイルを直接実行することで実行する言語だ。 +\index{C++!じつこう@実行}\index{C++!こんぱいる@コンパイル}\index{C++!そすふあいる@ソースファイル}\index{C++!おぶじえくとふあいる@オブジェクトファイル}\index{C++!じつこうかのうふあいる@実行可能ファイル} + +ほかの言語では、ソースファイルをそのままパースし、解釈して実行するインタープリター形式\index{いんたぷりたけいしき@インタープリター形式}の言語が多い。もっとも、いまとなってはソースファイルから中間言語に変換して、VM(Virtual Machine)\index{VM@VM(Virtual Machine)}と呼ばれる中間言語を解釈して実行するソフトウェア上で実行するとか、JIT(Just-In-Time)コンパイル\index{JIT@JIT(Just-In-Time)コンパイル}してネイティブコードを生成して実行するといった実装もあるため、昔のように単純にインタープリター型の言語ということはできなくなっている事情はある。ただし、最終的にJITコンパイルされてネイティブコードが実行される言語でも、コンパイルやコード生成はプログラマーが意識しない形で行われるため、プログラマーはコンパイラーを直接使う必要のない言語も多い。 + +C++はプログラマーが直接コンパイラーを使い、ソースファイルをプログラムに変換する言語だ。 + +\hypersection{ch0202}{簡単な1つのソースファイルからなるプログラムの実行} + +ここでは、典型的なC++のソースファイルをどのようにコンパイルし実行するか、一連の流れを学ぶ。 + +\hypersubsection{ch020201}{サンプルコード} + +以下のC++のソースファイル\index{C++!そすふあいる@のソースファイル}は標準出力に\texttt{hello}と出力するものだ。 + +\begin{lstlisting}[language={C++}] +#include + +int main() +{ + std::cout << "hello" ; +} +\end{lstlisting} + +コードの詳細な意味はさておくとして、このサンプルコードを使ってC++の実行までの流れを見ていこう。 + +まずは端末から作業用の適当な名前のディレクトリーを作る。ここでは\texttt{cpp}としておこう。ディレクトリーの作成は\texttt{mkdir}コマンド\index{mkdir@\texttt{mkdir}コマンド}で行える。 + +\begin{lstlisting}[style=terminal] +$ mkdir cpp +$ cd cpp +\end{lstlisting} + +好きなテキストエディターを使って上のサンプルコードをテキストファイルとして記述する。ファイル名は\texttt{hello.cpp}\index{hellocpp@\texttt{hello.cpp}}としておこう。 + +\begin{lstlisting}[style=terminal] +$ vim hello.cpp +\end{lstlisting} + +C++のソースファイルの名前は何でもよいが、慣習で使われている拡張子がいくつかある。本書では\texttt{.cpp}\index{.cpp@\texttt{.cpp}}を使う。 + +無事にソースファイルが作成できたかどうか確認してみよう。現在のカレントディレクトリー下のファイルの一覧を表示するには\texttt{ls}\index{ls@\texttt{ls}コマンド}、ファイルの内容を表示するには\texttt{cat}\index{cat@\texttt{cat}コマンド}を使う。 + +\begin{lstlisting}[style=terminal] +$ ls +hello.cpp +$ cat hello.cpp +#include + +int main() +{ + std::cout << "hello" ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +\hypersubsection{ch020202}{コンパイル} + +さて、ソースファイルが用意できたならば、いよいよコンパイル\index{こんぱいる@コンパイル}だ。 + +C++のソースファイルから、実行可能ファイル\index{じつこうかのうふあいる@実行可能ファイル}を生成するソフトウェアをC++コンパイラー\index{C++こんぱいら@C++コンパイラー}という。C++コンパイラーとしては、GCC(GNU Compiler Collection)\index{GCC@GCC(GNU Compiler Collection)}とClang(クラン)\index{Clang@Clang(クラン)}がある。使い方はどちらもほぼ同じだ。 + +GCCを使って先ほどの\texttt{hello.cpp}をコンパイルするには以下のようにする。 + +\begin{lstlisting}[style=terminal] +$ g++ -o hello hello.cpp +\end{lstlisting} + +GCCという名前のC++コンパイラーなのに\texttt{g++}\index{g++@\texttt{g++}}なのは、\texttt{gcc}\index{gcc@\texttt{gcc}}はC言語コンパイラーの名前としてすでに使われているからだ。この慣習はClangも引き継いでいて、ClangのC++コンパイラーは\texttt{clang++}\index{clang++@\texttt{clang++}}だ。 + +サンプルコードを間違いなくタイプしていれば、カレントディレクトリーに\texttt{hello}とぃう実行可能ファイルが作成されるはずだ。確認してみよう。 + +\begin{lstlisting}[style=terminal] +$ ls +hello hello.cpp +\end{lstlisting} + +\hypersubsection{ch020203}{実行} + +さて、いよいよ実行だ。通常のOSではカレントディレクトリーが\texttt{PATH}に含まれていないため、実行するにはカレントディレクトリーからパスを指定する必要がある。 +\index{C++!じつこう@実行} + +\begin{lstlisting}[style=terminal] +$ ./hello +hello +\end{lstlisting} + +上出来だ。初めてのC++プログラムが実行できた。さっそくC++を学んでいきたいところだが、その前にC++プログラミングに必要なツールの使い方を学ぶ必要がある。 + +\clearpage +\hypersection{ch0203}{GCC: C++コンパイラー} + +GCC\index{GCC@GCC(GNU Compiler Collection)}はC++のソースファイルからプログラムを生成するC++コンパイラー\index{C++こんぱいら@C++コンパイラー}だ。 + +GCCの基本的な使い方は以下のとおり。 + +\begin{lstlisting}[style=grammar] +g++ その他のオプション -o 出力するファイル名 ソースファイル名 +\end{lstlisting} + +ソースファイル名は複数指定することができる。 + +\begin{lstlisting}[style=terminal] +$ g++ -o abc a.cpp b.cpp c.cpp +\end{lstlisting} + +これについては分割コンパイルの章で詳しく解説する。 + +コンパイラーはメッセージを出力することがある。コンパイルメッセージ\index{こんぱいるめつせじ@コンパイルメッセージ}には、エラーメッセージと警告メッセージとがある。 + +エラーメッセージ\index{えらめつせじ@エラーメッセージ}というのは、ソースコードに文法上、意味上の誤りがあるため、コンパイルできない場合に生成される。エラーメッセージはエラーの箇所も教えてくれる。ただし、文法エラーは往々にして適切な誤りの箇所を指摘できないこともある。これは、C++の文法としては正しくないテキストファイルから、妥当なC++であればどういう間違いなのかを推測する必要があるためだ。 + +警告メッセージ\index{けいこくめつせじ@警告メッセージ}というのは、ソースコードにコンパイルを妨げる文法上、意味上の誤りは存在しないが、誤りの可能性が疑われる場合に出力される。 + +\hypersubsection{ch020301}{コンパイラーオプション} + +GCCのコンパイラーオプション\index{GCC@GCC(GNU Compiler Collection)!こんぱいらおぷしよん@コンパイラーオプション}をいくつか学んでいこう。 + +\texttt{-std=}\,\index{GCC@GCC(GNU Compiler Collection)!-std=@\texttt{-std=}}はC++の規格を選択するオプションだ。C++17に準拠したいのであれば\,\texttt{-std=c++17}を指定する。読者が本書を読むころには、C++20や、あるいはもっと未来の規格が発行されているかもしれない。常に最新のC++規格を選択するオプションを指定するべきだ。 + +\texttt{-Wall}\index{GCC@GCC(GNU Compiler Collection)!-Wall@\texttt{-Wall}}はコンパイラーの便利な警告メッセージのほとんどすべてを有効にするオプションだ。コンパイラーによる警告メッセージはプログラムの不具合を未然に発見できるので、このオプションは指定すべきだ。 + +\texttt{{-}{-}pedantic-errors}\index{GCC@GCC(GNU Compiler Collection)!--pedantic-errors@\texttt{{-}{-}pedantic-errors}}はC++の規格を厳格に守るオプションだ。規格に違反しているコードがコンパイルエラー扱いになる。 + +これをまとめると、GCCは以下のように使う。 + +\begin{lstlisting}[style=grammar] +g++ -std=c++17 -Wall --pedantic-errors -o 出力ファイル名 入力ファイル名 +\end{lstlisting} + +ところで、GCCのオプションはとても多い。すべてを知りたい読者は、以下のようにしてGCCのマニュアルを読むとよい。 + +\begin{lstlisting}[style=terminal] +$ man gcc +\end{lstlisting} + +手元にマニュアルがない場合、GCCのWebサイトにあるオンラインマニュアルも閲覧できる。 + +\begin{itemize} +\item + \url{https://gcc.gnu.org/} +\item + \url{https://gcc.gnu.org/onlinedocs/} +\end{itemize} + +\hypersubsection{ch020302}{ヘッダーファイルの省略} + +先ほどのソースコードをもう一度見てみよう。冒頭に以下のような行がある。 + +\begin{lstlisting}[language={C++}] +#include +\end{lstlisting} + +これは\,\texttt{\#includeディレクティブ}(\#include directive)\index{\#include@\texttt{\#include}ディレクティブ}といい、プリプロセッサー(preprocessor)\index{ぷりぷろせつさ@プリプロセッサー}の一部だ。プリプロセッサーについて詳しくは煩雑になるので巻末資料を参照してもらうとして、このコードは\texttt{iostream}ライブラリを使うために必要で、その意味としてはヘッダーファイル\texttt{iostream}の取り込みだ。 + +C++の標準ライブラリを使うには、ライブラリごとに対応した\,\texttt{\#includeディレクティブ}を書かなければならない。それはあまりにも煩雑なので、本書では標準ライブラリのヘッダーファイルをすべて\,\texttt{\#include}した\texttt{ヘッダーファイル}(header file)\index{へつだふあいる@ヘッダーファイル}\index{.h@\texttt{.h}}を作成し、それを\,\texttt{\#include}することで、\texttt{\#include}を書かなくて済むようにする。 + +そのためにはまず標準ライブラリのヘッダーファイルのほとんどすべてを\,\texttt{\#include}したヘッダーファイル、\texttt{all.h}\index{all.h@\texttt{all.h}}を作成する。 + +\begin{lstlisting}[language={C++}] +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __has_include() +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __has_include() +# include +#endif + +#include +#include + + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals ; +\end{lstlisting} + +このようなヘッダーファイル\texttt{all.h}を作成したあとに、ソースファイルで以下のように書けば、ほかのヘッダーファイルを\,\texttt{\#include}する必要がなくなる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +#include "all.h" + +// その他のコード +\end{lstlisting} + +\texttt{//}\index{//@\texttt{//}}から行末まではコメント\index{こめんと@コメント}で、好きなテキストを書くことができる。 + +しかし、この最初の1行の\,\texttt{\#include}も面倒だ。そこでGCCのオプション\,\texttt{-include}\index{GCC@GCC(GNU Compiler Collection)!-include@\texttt{-include}}を使い、\texttt{all.h}を常に\,\texttt{\#include}した扱いにする。 + +\begin{lstlisting}[style=terminal] +$ g++ -include all.h -o program main.cpp +\end{lstlisting} + +このようにすると、\texttt{main.cpp}が以下のコードでもコンパイルできるようになる。 + +\begin{lstlisting}[language={C++}] +// main.cpp +// 面倒な#includeなどなし + +int main() +{ + std::cout << "hello" ; +} +\end{lstlisting} + +これでヘッダーファイルが省略できるようになった。 + +\hypersubsection{ch020303}{コンパイル済みヘッダー(precompiled header)} + +C++はソースファイルをコンパイルする必要がある言語だ。コンパイルには時間がかかる。コンパイルにどれだけ時間がかかっているかを計測するには、以下のようにするとよい。 + +\begin{lstlisting}[style=terminal] +$ time g++ -std=c++17 -Wall --pedantic-errors -include all.h -o program main.cpp +\end{lstlisting} + +どうだろうか。読者の環境にもよるが、知覚できるぐらいの時間がかかっているのではないだろうか。プログラミングの習得にはコードを書いてから実行までの時間が短い方がよい。そこで本格的にC++を学ぶ前に、コンパイル時間を短縮する方法を学ぶ。 + +プログラムで変更しないファイルを事前にコンパイルしておくと、変更した部分だけコンパイルすればよいので、コンパイル時間の短縮になる。GCCでは、ヘッダーファイルを事前にコンパイルする特別な機能がある。標準ライブラリのヘッダーファイルは変更しないので、事前にコンパイルしておけばコンパイル時間の短縮になる。 + +事前にコンパイルしたヘッダーファイルのことをコンパイル済みヘッダー(precompiled header)\index{こんぱいるずみへつだ@コンパイル済みヘッダー}\index{へつだふあいる@ヘッダーファイル!こんぱいるずみへつだ@コンパイル済みヘッダー}という。 + +すでに作成した\texttt{all.h}はコンパイル済みヘッダーとするのに適切なヘッダーファイルだ。 + +コンパイル済みヘッダーファイルを作成するには、ヘッダーファイル単体をGCCに与え、出力するファイルを\texttt{ヘッダーファイル名.gch}\index{.gch@\texttt{.gch}}とする。ヘッダーファイル名が\texttt{all.h}の場合、\texttt{all.h.gch}となる。 + +GCCのオプションにはほかのソースファイルをコンパイルするときと同じオプションを与えるほか、ヘッダーファイルがC++で書かれていることを示すオプション\,\texttt{-x c++-header}\index{GCC@GCC(GNU Compiler Collection)!-x c++-header@\texttt{-x c++-header}}を与える。 + +\begin{lstlisting}[style=terminal] +$ g++ -std=c++17 -Wall --pedantic-errors -x c++-header -o all.h.gch all.h +\end{lstlisting} + +こうすると、コンパイル済みヘッダーファイル\texttt{all.h.gch}が生成できる。 + +GCCはヘッダーファイルを使うときに、同名の\texttt{.gch}ファイルが存在する場合は、そちらをコンパイル済みヘッダーファイルとして使うことで、ヘッダーファイルの処理を省略する。 + +\begin{lstlisting}[style=terminal] +$ g++ -std=c++17 -Wall --pedantic-errors -include all.h -o program main.cpp +\end{lstlisting} + +コンパイル済みヘッダーは1回のコンパイルにつき1つしか使うことができない。そのため、コンパイル済みヘッダーとするヘッダーファイルを定め、そのヘッダーファイル内にほかのヘッダーをすべて記述する。本書ではコンパイル済みヘッダーファイルとする元のヘッダーファイルの名前を\texttt{all.h}とする。 + +さっそくコンパイル時間の短縮効果を確かめてみよう。 + +\begin{lstlisting}[style=terminal] +$ ls +all.h main.cpp +$ g++ -std=c++17 -Wall --pedantic-errors -x c++-header -o all.h.gch all.h +$ ls +all.h all.h.gch main.cpp +$ time g++ -std=c++17 -Wall --pedantic-errors -include all.h -o program main.cpp +\end{lstlisting} + +\clearpage +\hypersection{ch0204}{Make: ビルドシステム} +\index{GNU Make} + +\hypersubsection{ch020401}{コンパイルと実行のまとめ} + +ここまで、我々はソースファイルをコンパイルして実行可能ファイルを生成し、プログラムを実行する方法について学んできた。これまでに学んできたことを一連のコマンドで振り返ってみよう。 + +\begin{lstlisting}[style=terminal] +$ ls +all.h main.cpp +$ cat all.h +#include +$ cat main.cpp +int main() { std::cout << "hello"s ; } +\end{lstlisting} + +まず、カレントディレクトリーには\texttt{all.h}と\texttt{main.cpp}がある。この2つのファイルは実行可能ファイルを生成するために必要なファイルだ。今回、その中身は最小限にしてある。本当の\texttt{all.h}は、実際には前回書いたように長い内容になる。 + +\begin{lstlisting}[style=terminal] +$ g++ -std=c++17 -Wall --pedantic-errors -x c++-header -o all.h.gch all.h +$ ls +all.h all.h.gch main.cpp +\end{lstlisting} + +次に、ソースファイルのコンパイルを高速化するために、ヘッダーファイル\texttt{all.h}から、コンパイル済みヘッダーファイル\texttt{all.h.gch}を生成する。 + +\begin{lstlisting}[style=terminal] +$ g++ -std=c++17 -Wall --pedantic-errors -include all.h -o program main.cpp +$ ls +all.h all.h.gch main.cpp program +\end{lstlisting} + +プリコンパイル済みヘッダーファイル\texttt{all.h.gch}とC++ソースファイル\texttt{main.cpp}から、実行可能ファイル\texttt{program}を生成する。 + +\begin{lstlisting}[style=terminal] +$ ./program +hello +\end{lstlisting} + +実行可能ファイル\texttt{program}を実行する。 + +これで読者はC++のプログラミングを学び始めるにあたって必要なことはすべて学んだ。さっそくC++を学んでいきたいところだが、その前にもう1つ、ビルドシステムを学ぶ必要がある。 + +\hypersubsection{ch020402}{依存関係を解決するビルドシステム} + +以上のC++のソースファイルからプログラムを実行するまでの流れは、C++のプログラムとしてはとても単純なものだが、それでも依存関係\index{いぞんかんけい@依存関係}が複雑だ。 + +プログラムの実行にあたって最終的に必要なのはファイル\texttt{program}だが、このファイルはGCCで生成しなければならない。ところでGCCでファイル\texttt{program}を生成するには、事前に\texttt{all.h}, \texttt{all.h.gch}, \texttt{main.cpp}が必要だ。\texttt{all.h.gch}は\texttt{all.h}からGCCで生成しなければならない。 + +一度コンパイルしたプログラムのソースファイルを書き換えて再びコンパイルする場合はどうすればいいだろう。\texttt{main.cpp}だけを書き換えた場合、\texttt{all.h}は何も変更されていないので、コンパイル済みヘッダーファイル\texttt{all.h.gch}の再生成は必要ない。\texttt{all.h}だけを書き換えた場合は、\texttt{all.h.gch}を生成するだけでなく、\texttt{program}も再生成しなければならない。 + +プログラムのコンパイルには、このような複雑な依存関係の解決が必要になる。依存関係の解決を人間の手で行うのはたいへんだ。例えば読者が他人によって書かれた何千ものソースファイルと、プログラムをコンパイルする手順書だけを渡されたとしよう。手順書に従ってコンパイルをしたとして、ソースファイルの一部だけを変更した場合、いったいどの手順は省略できるのか、手順書から導き出すのは難しい。するとコンパイルを最初からやり直すべきだろうか。しかし、1つのソースファイルのコンパイルに1秒かかるとして、何千ものソースファイルがある場合、何千秒もかかってしまう。たった1つのソースファイルを変更しただけですべてをコンパイルし直すのは時間と計算資源の無駄だ。 + +この依存関係の問題は、ビルドシステムによって解決できる。本書ではGNU Make\index{GNU Make}というビルドシステムを学ぶ。読者がこれから学ぶビルドシステムによって、以下のような簡単なコマンドだけで、他人の書いた何千ものソースファイルからなるプログラムがコンパイル可能になる。 + +何千ものソースファイルから実行可能ファイルを生成したい。 + +\begin{lstlisting}[style=terminal] +$ make +\end{lstlisting} + +これだけだ。\texttt{make}\index{make@\texttt{make}コマンド}というコマンド1つでプログラムのコンパイルは自動的に行われる。 + +何千ものソースファイルのうち、1つのソースファイルだけを変更し、必要な部分だけを効率よく再コンパイルしたい。 + +\begin{lstlisting}[style=terminal] +$ make +\end{lstlisting} + +これだけだ。\texttt{make}というコマンド1つでプログラムの再コンパイルは自動的に行われる。 + +ところで、生成される実行可能ファイルの名前はプログラムごとにさまざまだ。プログラムの開発中は、共通の方法でプログラムを実行したい。 + +\begin{lstlisting}[style=terminal] +$ make run +\end{lstlisting} +\index{make@\texttt{make}コマンド!run@\texttt{run}} + +これでどんなプログラム名でも共通の方法で実行できる。 + +ソースファイルから生成されたプログラムなどのファイルをすべて削除したい。 + +\begin{lstlisting}[style=terminal] +$ make clean +\end{lstlisting} +\index{make@\texttt{make}コマンド!clean@\texttt{clean}} + +これで生成されたファイルをすべて削除できる。 + +テキストエディターにはVimを使っているがわざわざVimからターミナルに戻るのが面倒だ。 + +\begin{lstlisting}[style=terminal] +:make +\end{lstlisting} + +VimはノーマルモードからMakeを呼び出すことができる。もちろん、\texttt{:make run}や\texttt{:make clean}もできる。 + +\hypersubsection{ch020403}{依存関係を記述するルール} + +依存関係はどのように表現したらいいのだろうか。GNU Makeでは\texttt{Makefile}\index{Makefile@\texttt{Makefile}}\index{GNU Make!Makefile@\texttt{Makefile}}という名前のファイルの中に、\texttt{ターゲット}(targets)\index{GNU Make!たげつと@ターゲット}、\texttt{事前要件}(prerequisites)\index{GNU Make!じぜんようけん@事前要件}、\texttt{レシピ}(recipes)\index{GNU Make!れしぴ@レシピ}という3つの概念で依存関係を\texttt{ルール}(rules)\index{GNU Make!るる@ルール}として記述する。\texttt{ルール}は以下の文法だ。 + +\begin{lstlisting}[style=grammar] +ターゲット : 事前要件 +[TAB文字]レシピ +\end{lstlisting} + +レシピは必ず\texttt{TAB文字}を直前に書かなければならない。スペース文字ではだめだ。これは\texttt{make}の初心者を混乱させる落とし穴の1つとなっている。忘れずに\texttt{TAB文字}を打とう。 + +問題を簡単に理解するために、以下のような状況を考えよう。 + +\begin{lstlisting}[style=terminal] +$ ls +source +$ cat source > program +\end{lstlisting} + +この例では、ファイル\texttt{program}を生成するためにはファイル\texttt{source}が必要だ。ファイル\texttt{source}はすでに存在している。 + +\texttt{ターゲット}は生成されるファイル名だ。この場合\texttt{program}となる。 + +\begin{lstlisting}[language=make] +program : 事前要件 + レシピ +\end{lstlisting} + +\texttt{事前要件}は\texttt{ターゲット}を生成するために必要なファイル名だ。この場合\texttt{source}となる。 + +\begin{lstlisting}[language=make] +program : source + レシピ +\end{lstlisting} + +\texttt{レシピ}は\texttt{ターゲット}を生成するために必要な動作だ。この場合、\texttt{cat source > program}となる + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language=make] +program : source + cat source > program +\end{lstlisting} + +さっそくこのルールを、ファイル\texttt{Makefile}に書き込み、\texttt{make}を呼び出してみよう。 + +\begin{lstlisting}[style=terminal] +$ ls +Makefile source +$ cat Makefile +program : source + cat source > program +$ make +cat source > program +$ ls +Makefile program source +\end{lstlisting} + +これがMakeの仕組みだ。\texttt{ターゲット}の生成に必要な\texttt{事前要件}と、\texttt{ターゲット}を生成する\texttt{レシピ}を組み合わせた\texttt{ルール}で依存関係を記述する。\texttt{make}を実行すると、実行した\texttt{レシピ}が表示される。 + +もう少しMakeの\texttt{ルール}を追加してみよう。例えばファイル\texttt{source}はあらかじめ存在するのではなく、ファイル\texttt{source01}, \texttt{source02}, \texttt{source03}の中身をこの順番で連結して生成するとしよう。以下のように書ける。 + +\begin{lstlisting}[language=make] +program : source + cat source > program + +source : source01 source02 source03 + cat source01 source02 source03 > source +\end{lstlisting} + +GNU Makeはカレントディレクトリーにあるファイル\texttt{Makefile}の一番上に書かれたルールを実行しようとする。\texttt{program}を生成するには\texttt{source}が必要だが、\texttt{source}の生成には別のルールの実行が必要だ。\texttt{Makefile}はこの依存関係を自動で解決してくれる。 + +\begin{lstlisting}[style=terminal] +$ touch source01 source02 source03 +$ ls +Makefile source01 source02 source03 +$ make +cat source01 source02 source03 > source +cat source > program +$ ls +Makefile program source source01 source02 source03 +\end{lstlisting} + +すでに\texttt{make}を実行したあとで、もう一度\texttt{make}を実行するとどうなるだろうか。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=terminal] +$ make +make: 'program' is up to date. +\end{lstlisting} + +このメッセージの意味は「\texttt{program}は最新だ」という意味だ。\texttt{make}はファイルのタイムスタンプを調べ、もしファイル\texttt{program}より\texttt{source}のタイムスタンプの方が若い場合、つまり\texttt{program}が変更されたよりもあとに\texttt{source}が変更された場合、\texttt{ルール}を実行する。 + +試しにファイル\texttt{source02}のタイムスタンプを更新してみよう。 + +\begin{lstlisting}[style=terminal] +$ touch source02 +$ make +cat source01 source02 source03 > source +cat source > program +\end{lstlisting} + +ファイル\texttt{source}は\texttt{事前要件}に\texttt{source02}を含む。\texttt{source02}のタイムスタンプが\texttt{source}より若いので、\texttt{source}が再び生成される。すると、\texttt{source}のタイムスタンプが\texttt{program}のタイムスタンプよりも若くなったので、\texttt{program}も生成される。 + +もう1つ例を見てみよう。 + +\begin{lstlisting}[style=terminal] +$ touch a b c +$ ls +a b c Makefile +\end{lstlisting} + +あるディレクトリーにファイル\texttt{a}, \texttt{b}, \texttt{c}がある。 + +\texttt{Makefile}は以下の内容になっている。 + +\begin{lstlisting}[language=make] +D : A B C + cat A B C > D + +A : a + cat a > A + +B : b + cat b > B + +C : c + cat c > C +\end{lstlisting} + +この\texttt{Makefile}を呼び出したときに作られるのはファイル\texttt{D}だ。ファイル\texttt{D}を作るにはファイル\texttt{A}, \texttt{B}, \texttt{C}が必要だ。このファイルはそれぞれファイル\texttt{a}, \texttt{b}, \texttt{c}から生成されるルールが記述してある。 + +これを\texttt{make}すると以下のようにファイル\texttt{A}, \texttt{B}, \texttt{C}, \texttt{D}が作られる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=terminal] +$ ls +a b c Makefile +$ make +cat a > A +cat b > B +cat c > C +cat A B C > D +\end{lstlisting} + +ここで、ファイル\texttt{b}のタイムスタンプだけを更新して\texttt{make}してみよう。 + +\begin{lstlisting}[style=terminal] +$ touch b +$ make +cat b > B +cat A B C > D +\end{lstlisting} + +ファイル\texttt{b}のタイムスタンプがファイル\texttt{B}より若くなったので、ファイル\texttt{B}がターゲットとなったルールが再び実行される。ファイル\texttt{A}, \texttt{C}のルールは実行されない。そしてファイル\texttt{B}のタイムスタンプがファイル\texttt{D}より若くなったので、ファイル\texttt{D}がターゲットとなったルールが再び実行される。 + +\texttt{make}により、処理する必要のあるルールだけが部分的に処理されていることがわかる。 + +\texttt{make}は適切な\texttt{ルール}さえ書けば、依存関係の解決を自動的に行ってくれる。 + +\hypersubsection{ch020404}{コメント} + +\texttt{Makefile}\index{Makefile@\texttt{Makefile}}にはコメント\index{Makefile@\texttt{Makefile}!こめんと@コメント}を書くことができる。\texttt{\#}\,\index{Makefile@\texttt{Makefile}!\#@\texttt{\#}}で始まる行はコメント扱いされる。 + +\begin{lstlisting}[language=make] +# programを生成するルール +program : source + cat source > program + +# sourceを生成するルール +source : source01 source02 source03 + cat source01 source02 source03 > source +\end{lstlisting} + +\hypersubsection{ch020405}{変数} + +\texttt{Makefile}には\texttt{変数}\index{Makefile@\texttt{Makefile}!へんすう@変数}を書くことができる。 + +変数の文法は以下のとおり。 + +\begin{lstlisting}[style=grammar] +variable = foobar + +target : $(variable) +\end{lstlisting} + +これは、 +\begin{lstlisting}[language=make] +target : foobar +\end{lstlisting} +と書いたものと同じように扱われる。 + +変数は\,\texttt{=}\,の左側に変数名、右側に変数の内容を書く。 + +変数を使うときは、\texttt{\$(変数名)}のように、\texttt{\$()}\index{Makefile@\texttt{Makefile}!\$()@\texttt{\$()}}で変数名を包む。 + +\hypersubsection{ch020406}{自動変数} + +GNU Makeは便利なことに、いくつかの変数を自動で作ってくれる。\index{Makefile@\texttt{Makefile}!じどうへんすう@自動変数} + +\hypersubsubsection{ch02040601}{\texorpdfstring{\texttt{\$@} ターゲット}{\$@ ターゲット}} + +\texttt{\$@}\,\index{Makefile@\texttt{Makefile}!\$\protect{@}@\texttt{\$\protect{@}}}はルールのターゲットのファイル名になる。 + +\begin{lstlisting}[language=make] +target : + echo $@ +\end{lstlisting} + +この\texttt{Makefile}を実行すると以下のように出力される。 + +\begin{lstlisting}[style=terminal] +$ make +echo target +\end{lstlisting} + +\hypersubsubsection{ch02040602}{\texorpdfstring{\texttt{\$\textless{}} 最初の事前要件}{\$\textless{} 最初の事前要件}} + +\texttt{\$<}\,\index{Makefile@\texttt{Makefile}!\$<@\texttt{\$<}}はルールの最初の事前要件のファイル名になる。 + +\begin{lstlisting}[language=make] +target : A B C + echo $< +\end{lstlisting} + +この\texttt{Makefile}を実行すると以下のように出力される。 + +\begin{lstlisting}[style=terminal] +$ make +echo A +\end{lstlisting} + +\hypersubsubsection{ch02040603}{\texorpdfstring{\texttt{\$\^{}} すべての事前要件}{\$\^{} すべての事前要件}} + +\texttt{\${\textasciicircum}}\,\index{Makefile@\texttt{Makefile}!\${\textasciicircum}@\texttt{\${\textasciicircum}}}はすべての事前要件のファイル名が空白区切りされたものになる + +\begin{lstlisting}[language=make] +target : A B C + echo $^ +\end{lstlisting} + +この\texttt{Makefile}を実行すると以下のように出力される。 + +\begin{lstlisting}[style=terminal] +$ make +echo A B C +\end{lstlisting} + +\hypersubsubsection{ch02040604}{自動変数の組み合わせ} + +例えば\texttt{ターゲット}を生成するために\texttt{事前要件}と\texttt{ターゲット}のファイル名をレシピに書く場合、 +\begin{lstlisting}[language=make] +target : prerequisite + cat prerequisite > target +\end{lstlisting} +と書く代わりに、 +\begin{lstlisting}[language=make] +target : prerequisite + cat $< > $@ +\end{lstlisting} +と書ける。 + +\hypersubsection{ch020407}{PHONYターゲット} + +PHONYターゲット\index{Makefile@\texttt{Makefile}!PHONY@PHONYターゲット}とは、ファイル名を意味せず、単にレシピを実行するターゲット名としてのみ機能するターゲットのことだ。 + +\begin{lstlisting}[language=make] +hi : + echo hi + +hello : + echo hello +\end{lstlisting} + +これを実行すると以下のようになる。 + +\begin{lstlisting}[style=terminal] +$ make +echo hi +hi +$ make hi +echo hi +hi +$ make hello +echo hello +hello +\end{lstlisting} + +\texttt{make}を引数を付けずに実行すると、一番上に書かれたルールが実行される。引数としてターゲットを指定すると、そのターゲットのルールと、依存するルールが実行される。 + +ただし、ターゲットと同じファイル名が存在すると、ルールは実行されない。 + +\begin{lstlisting}[style=terminal] +$ touch hello +$ make hello +make: 'hello' is up to date. +\end{lstlisting} + +GNU Makeはこの問題に対処するため、\texttt{.PHONY}ターゲット\index{Makefile@\texttt{Makefile}!.PHONY@\texttt{.PHONY}ターゲット}という特殊な機能がある。これはPHONYターゲットを\texttt{.PHONY}ターゲットの事前要件とすることで、ターゲットと同じファイル名の存在の有無にかかわらずルールを実行させられる。 + +\begin{lstlisting}[language=make] +hello : + echo hello + +.PHONY : hello +\end{lstlisting} + +PHONYターゲットはコンパイルしたプログラムの実行や削除に使うことができる。 + +\begin{lstlisting}[language=make] +hello : hello.cpp + g++ -o $@ $< + +run : hello + ./hello + +clean : + rm -rf ./hello + +.PHONY : run clean +\end{lstlisting} + +\hypersection{ch0205}{入門用の環境構築} + +以上を踏まえて、C++入門用の環境構築をしてこの章のまとめとする。 + +今回構築する環境のファイル名とその意味は以下のとおり。 + +\begin{description} +\item[\texttt{main.cpp}] +C++のコードを書く \texttt{all.h} + +標準ライブラリのヘッダーファイルを書く \texttt{all.h.gch} + +コンパイル済みヘッダー \texttt{program} + +実行可能ファイル \texttt{Makefile} + +GNU Makeのルールを書く +\end{description} + +使い方は以下のとおり。 + +\begin{description} +\item[\texttt{make}] +コンパイルする \texttt{make run} + +コンパイルして実行 \texttt{make clean} + +コンパイル結果を削除 +\end{description} + +GCCに与えるコンパイラーオプションを変数にまとめる。 + +\begin{lstlisting}[language=make] +gcc_options = -std=c++17 -Wall --pedantic-error +\end{lstlisting} + +言語はC++17、すべての警告を有効にし、規格準拠ではないコードはエラーとする。 + +プログラムをコンパイルする部分は以下のとおり。 + +\begin{lstlisting}[language=make] +program : main.cpp all.h all.h.gch + g++ $(gcc_options) -include all.h $< -o $@ + +all.h.gch : all.h + g++ $(gcc_options) -x c++-header -o $@ $< +\end{lstlisting} + +実行可能ファイル\texttt{program}と、コンパイル済みヘッダー\texttt{all.h.gch}をコンパイルするルールだ。 + +PHONYターゲットは以下のとおり。 + +\begin{lstlisting}[language=make] +run : program + ./program + +clean : + rm -f ./program + rm -f ./all.h.gch + +.PHONY : run clean +\end{lstlisting} + +\texttt{make}でコンパイル。\texttt{make run}で実行。\texttt{make clean}でコンパイル結果の削除。 + +\texttt{Makefile}全体は以下のようになる。 + +\begin{lstlisting}[language=make] +gcc_options = -std=c++17 -Wall --pedantic-error + +program : main.cpp all.h all.h.gch + g++ $(gcc_options) -include all.h $< -o $@ + +all.h.gch : all.h + g++ $(gcc_options) -x c++-header -o $@ $< + +run : program + ./program + +clean : + rm -f ./program + rm -f ./all.h.gch + +.PHONY : run clean +\end{lstlisting} + diff --git a/TeX/003-guide-to-c++.tex b/TeX/003-guide-to-c++.tex new file mode 100644 index 0000000..256db37 --- /dev/null +++ b/TeX/003-guide-to-c++.tex @@ -0,0 +1,719 @@ +\hyperchapter{ch03}{C++ヒッチハイクガイド}{C++ヒッチハイクガイド} + +プログラミング言語の個々の機能の解説を理解するためには、まず言語の全体像を掴まなければならない。この章ではC++のさまざまなコードをひと通り観光していく。ここではコードの詳細な解説はしない。 + +\hypersection{ch0301}{最小のコード} + +以下はC++の最小のコードだ。 + +\begin{lstlisting}[language={C++}] +int main(){} +\end{lstlisting} + +暗号のようなコードで訳がわからないが、これが最小のコードだ。\texttt{main}というのは\texttt{main関数}\index{main@\texttt{main}関数}のことだ。C++ではプログラムの実行は\texttt{main}関数から始まる。 + +ソースコードにコメントを記述して、もう少しわかりやすく書いてみよう。 + +\begin{lstlisting}[language={C++}] +int // 関数の戻り値の型 +main // 関数名 +() // 関数の引数 +{ // 関数の始まり + // 実行される処理 +} // 関数の終わり +\end{lstlisting} + +\texttt{//}\,\index{//@\texttt{//}}から行末まではコメント\index{こめんと@コメント}だ。コメントには好きなことを書くことができる。 + +このコードと1つ前のコードは、コメントの有無を別にすれば何の違いもない。このコードで使っている、\texttt{int}とか\texttt{main}とか記号文字の1つ1つをトークン(token)\index{とくん@トークン}と呼ぶ。C++ではトークンの間に空白文字や改行文字をいくら使ってもよい。 + +なので、 +\begin{lstlisting}[language={C++}] +int main(){ } +\end{lstlisting} +と書くこともできるし、 +\begin{lstlisting}[language={C++}] +int main ( ) { } +\end{lstlisting} +と書くこともできるし、紙に印刷する都合上とても読みづらくなるかもしれないが +\begin{lstlisting}[language={C++}] +int +main +( +) +{ +} +\end{lstlisting} +と書くこともできる。 + +ただし、トークンの途中で空白文字や改行文字を使うことはできない。以下のコードは間違っている。 + +\begin{lstlisting}[language={C++}] +i +nt ma in(){} +\end{lstlisting} + +\hypersection{ch0302}{標準出力} + +\begin{lstlisting}[language={C++}] +// helloと改行を出力するプログラム +int main() +{ + std::cout << "hello"s ; +} +\end{lstlisting} + +標準出力\index{ひようじゆんしゆつりよく@標準出力}はプログラムの基本だ。C++で標準出力する方法はいくつもあるが、\texttt{}\,ライブラリ\index{iostream@\texttt{iostream}}を利用するものが最も簡単だ。 + +\texttt{std::cout}\index{cout@\texttt{cout}}は標準出力を使うためのライブラリだ。 + +\texttt{{<}{<}}\,\index{{<}{<}@\texttt{{<}{<}}}は\texttt{operator {<}{<}}という演算子だ。C++では演算子にも名前が付いていて、例えば\texttt{+}は\texttt{operator +}となる。\texttt{{<}{<}}\,も演算子の一種だ。 + +\texttt{"hello"s}というのは文字列\index{もじれつ@文字列}で、二重引用符で囲まれた中の文字列が標準出力に出力される。 + +セミコロン\texttt{;}\index{;@\texttt{;}}\index{せみころん@セミコロン}は文の区切り文字\index{くぎりもじ@区切り文字}だ。C++では文の区切りは明示的にセミコロンを書く必要がある。ほかの言語では改行文字を文脈から判断して文の区切りとみなすこともあるが、C++では明示的に文の区切り文字としてセミコロンを書かなければならない。 + +セミコロンを書き忘れるとエラーとなる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // エラー! セミコロンがない + std::cout << "error"s +} +\end{lstlisting} + +複数の文を書いてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << "one "s ; + std::cout << "two "s ; + std::cout << "three "s ; +} +\end{lstlisting} + +C++はほかの多くの言語と同じように、逐次実行される。つまり、コードは書いた順番に実行される。そして標準出力のような外部への副作用は、実行された順番で出力される。このコードを実行した結果は以下のとおり。 + +\begin{lstlisting}[style=terminal] +one two three +\end{lstlisting} + +\texttt{"three two one "}\,や\,\texttt{"two one three "}\,のような出力結果にはならない。 + +C++を含む多くの言語で\texttt{a + b + c}と書けるように、\texttt{operator {<}{<}}も\texttt{a {<}{<} b {<}{<} c}と書ける。\texttt{operator {<}{<}}で標準出力をするには、左端は\texttt{std::cout}でなければならない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << "aaa"s << "bbb"s << "ccc"s ; +} +\end{lstlisting} + +出力は\texttt{aaabbbccc}となる。 + +\clearpage +\hypersection{ch0303}{文字列} + +二重引用符で囲まれた文字列を、文字どおり\texttt{文字列}\index{もじれつ@文字列}という。文字列には末尾に\texttt{s}\index{もじれつ@文字列!s@\texttt{s}}が付くものと付かないものがある。これには違いがあるのだが、わからないうちは\texttt{s}を付けておいた方が便利だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // これは文字列 + std::cout << "hello"s ; + // これも文字列、 ただし不便 + std::cout << "hello" ; +} +\end{lstlisting} + +文字列リテラルの中にバックスラッシュ\index{ばつくすらすしゆ@バックスラッシュ}\index{{\textbackslash}@\texttt{{\textbackslash}}}を書くと、エスケープシーケンスとして扱われる。最もよく使われるのは改行文字\index{かいぎようもじ@改行文字}を表す\,\texttt{{\textbackslash}n}\index{{\textbackslash}n@{\texttt{{\textbackslash}n}}}だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << "aaa\nbbb\nccc"s ; +} +\end{lstlisting} + +これは以下のように出力される。 + +\begin{lstlisting}[style=terminal] +aaa +bbb +ccc +\end{lstlisting} + +バックスラッシュを文字列で使いたい場合は\,\texttt{{\textbackslash}}\,と書かなければならない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << "\\n is a new-line.\n"s ; +} +\end{lstlisting} + +文字列は演算子\texttt{operator +}\index{\protect{+}@\texttt{\protect{+}}}で「足す」ことができる。「文字列を足す」というのは、「文字列を結合する」\index{もじれつ@文字列!けつごう@結合}という意味だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << "hello"s + "world"s ; +} +\end{lstlisting} + +\hypersection{ch0304}{整数と浮動小数点数} + +\texttt{iostream}は文字列のほかにも、整数\index{せいすう@整数}や浮動小数点数\index{ふどうしようすうてんすう@浮動小数点数}を出力できる。さっそく試してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout + << "Integer: "s << 42 << "\n"s + << "Floating Point: "s << 3.14 ; +} +\end{lstlisting} + +\texttt{-123}や\texttt{0}や\texttt{123}といった数値を整数という。\texttt{3.14}のような数値を浮動小数点数という。 + +数値を扱えるのだから、計算をしてみたいところだ。C++は整数同士の演算子として、四則演算(\,\texttt{+-*/}\,)\index{しそくえんざん@四則演算}や剰余(\,\texttt{\%}\,)\index{じようよ@剰余}をサポートしている。 +\index{\protect{+}@\texttt{\protect{+}}} +\index{\protect{-}@\texttt{\protect{-}}} +\index{\protect{*}@\texttt{\protect{*}}} +\index{/@\texttt{/}} +\index{\%@\texttt{\%}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout + << 3 + 5 << " "s << 3 - 5 << " "s + << 3 * 5 << " "s << 3 / 5 << " "s + << 3 % 5 ; +} +\end{lstlisting} + +演算子は組み合わせて使うこともできる。その場合、演算子\,\texttt{*/\%}\,は演算子\,\texttt{+-}\,よりも優先される。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 7 + std::cout << 1 + 2 * 3 ; +} +\end{lstlisting} + +この場合、まず\texttt{2*3}が計算され6となり、\texttt{1+6}が計算され\texttt{7}となる。 + +\texttt{1+2}の方を先に計算したい場合、括弧\texttt{()}で囲むことにより、計算の優先度を変えることができる。 +\index{\protect{()}@\texttt{\protect{()}}} + +\begin{lstlisting}[language={C++}] +int main() +{ + // 9 + std::cout << (1 + 2) * 3 ; +} +\end{lstlisting} + +これは\texttt{1+2}が先に計算され\texttt{3}となり、\texttt{3*3}が計算され\texttt{9}となる。 + +浮動小数点数同士でも四則演算ができる。剰余はできない。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout + << 3.5 + 7.11 << " "s << 3.5 - 7.11 << " "s + << 3.5 * 7.11 << " "s << 3.5 / 7.11 ; +} +\end{lstlisting} + +では整数と浮動小数点数を演算した場合どうなるのだろう。さっそく試してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << 1 + 0.1 ; +} +\end{lstlisting} + +結果は\texttt{1.1}だ。整数と浮動小数点数を演算した結果は浮動小数点数になる。 + +そういえばC++には文字列もあるのだった。文字列と文字列は足すことができる。数値と数値も足すことができる。では数値と文字列を足すとどうなるのだろう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << 1 + "234"s ; +} +\end{lstlisting} + +この結果はエラーになる。 + +いや待て、C++には末尾に\texttt{s}を付けない文字列もあるのだった。これも試してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << 1 + "234" ; +} +\end{lstlisting} + +結果はなんと\texttt{34}になるではないか。C++では謎の数学により\,\texttt{1 + "234" = "34"}\,であることが判明した。この謎はいずれ解き明かすとして、いまは文字列には必ず末尾に\texttt{s}を付けることにしよう。その方が安全だ。 + +\clearpage +\hypersection{ch0305}{変数(variable)} + +さあどんどんプログラミング言語によくある機能を見ていこう。次は変数\index{へんすう@変数}だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 整数の変数 + auto answer = 42 ; + std::cout << answer << "\n"s ; + // 浮動小数点数の変数 + auto pi = 3.14 ; + std::cout << pi << "\n"s ; + + // 文字列の変数 + auto question = "Life, The Universe, and Everything."s ; + std::cout << question ; +} +\end{lstlisting} + +変数はキーワード\texttt{auto}\index{auto@\texttt{auto}}に続いて変数名を書き、\texttt{=}\,に続いて値を書くことで宣言できる。変数の宣言\index{へんすう@変数!せんげん@宣言}は文なので、文末にはセミコロンが必要だ。 + +\begin{lstlisting}[style=grammar] +auto 変数名 = 値 ; +\end{lstlisting} + +\texttt{変数名}\index{へんすうめい@変数名}はキーワード、アンダースコア(\,\texttt{\_}\,)で始まる名前、アンダースコア2つ(\,\texttt{\_\_}\,)を含む名前以外は自由に名付けることができる。 + +変数の最初の値は、\texttt{= 値}の代わりに\texttt{(値)}や\,\texttt{\{値\}}\,と書いてもよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto a = 1 ; + auto b(2) ; + auto c{3} ; +} +\end{lstlisting} + +この\,\texttt{=}, \texttt{()}, \texttt{\{\}}\,による変数の初期値の指定を、\texttt{初期化}\index{しよきか@初期化}\index{へんすう@変数!しよきか@初期化}という。 + +変数は使う前に宣言しなければならない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // エラー、 名前xは宣言されていない + std::cout << x ; + auto x = 123 ; +} +\end{lstlisting} + +変数の値は初期化したあとにも演算子\,\texttt{=}\,\index{=@\texttt{=}}で変更できる。これを\texttt{代入}\index{だいにゆう@代入}という。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 変数の宣言 + auto x + // 初期化 + = 123 ; + + // 123 + std::cout << x ; + + // 代入 + x = 456 ; + + // 456 + std::cout << x ; + + // もう一度代入 + x = 789 ; + // 789 + std::cout << x ; +} +\end{lstlisting} + +代入演算子\index{だいにゆうえんざんし@代入演算子}\,\texttt{operator =}\,\index{=@\texttt{=}}は左辺に変数名を、右辺に代入する値を書く。面白いこととして、右辺には代入する変数名そのものを書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto x = 10 ; + x = x + 5 ; + + // 15 + std::cout << x ; +} +\end{lstlisting} + +\texttt{operator =}\,は「代入」という意味で、「等号」という意味ではないからだ。\texttt{x=x+5}は、「\texttt{x}と\texttt{x+5}は等しい」という独創的な数学上の定義ではなく、「変数\texttt{x}に代入前の変数\texttt{x}の値に5を加えた数を代入する」という意味だ。 + +変数のいまの値に対して演算した結果を変数に代入するという処理はとてもよく使うので、C++には\texttt{x = x + a}と同じ意味で使える演算子、\texttt{operator +=}\,もある。 +\index{\protect{+=}@\texttt{\protect{+=}}} + +\begin{lstlisting}[language={C++}] +int main() +{ + auto x = 1 ; + // x = x + 5と同じ + x += 5 ; +} +\end{lstlisting} + +\texttt{operator +=}\,と同様に、\texttt{operator -=}, \texttt{operator *=}, \texttt{operator /=}, \texttt{operator \%=}\,もある。 +\index{-=@\texttt{-=}}\index{\protect{*=}@\texttt{\protect{*=}}}\index{/=@\texttt{/=}}\index{\%=@\texttt{\%=}} + +C++の変数は、専門用語を使うと「静的型付け」になる。静的型付けと対比されるのが「動的型付け」だ。もっと難しく書くと、動的型付け言語の変数は、C++で言えば型情報付きの\texttt{void *}\,型の変数のような扱いを受ける。 + +C++の変数には\texttt{型}\index{かた@型}\index{へんすう@変数!かた@型}がある。\texttt{型}というのは値の種類を表す情報のことだ。 + +例えば、以下は変数が動的型付けの言語JavaScriptのコードだ。 + +\begin{lstlisting}[language=JavaScript] +var x = 1 ; +x = "hello" ; +x = 2 ; +\end{lstlisting} + +JavaScriptではこのコードは正しい。変数\texttt{x}は数値型であり、文字列型に代わり、また数値型に戻る。 + +C++ではこのようなコードは書けない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto x = 1 ; + // エラー + x = "hello"s ; + x = 2 ; +} +\end{lstlisting} + +C++では、変数\texttt{x}は整数型であり、文字列型に変わることはない。整数型の変数に文字列型を代入しようとするとエラーとなる。 + +C++では型に名前が付いている。整数型\index{せいすうがた@整数型}は\texttt{int}\index{int@\texttt{int}型}、浮動小数点数型\index{ふどうしようすうてんすうがた@浮動小数点数型}は\texttt{double}\index{double@\texttt{double}型}、文字列型\index{もじれつがた@文字列型}は\texttt{std::string}\index{string@\texttt{string}型}だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // iはint型 + auto i = 123 ; + // dはdouble型 + auto d = 1.23 ; + // sはstd::string型 + auto s = "123"s ; +} +\end{lstlisting} + +実は変数の宣言で\texttt{auto}と書く代わりに、具体的な型を書いてもよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int i = 123 ; + double d = 1.23 ; + std::string s = "123"s ; +} +\end{lstlisting} + +整数型(\texttt{int})と浮動小数点数型(\texttt{double})はそれぞれお互いの型の変数に代入できる。ただし、変数の型は変わらない。単に一方の型の値がもう一方の型の値に変換\index{かた@型!へんかん@変換}されるだけだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 浮動小数点数型を整数型に変換 + int a = 3.14 ; + // 3 + std::cout << a << "\n"s ; + + // 整数型を浮動小数点数型に変換 + double d = 123 ; + // 123 + std::cout << d ; +} +\end{lstlisting} + +浮動小数点数型を整数型に変換すると、小数部が切り捨てられる。この場合、\texttt{3.14}の小数部\texttt{0.14}が切り捨てられ\texttt{3}となる。\texttt{0.9999}も小数部が切り捨てられ\texttt{0}になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int i = 0.9999 ; + // 0 + std::cout << i ; +} +\end{lstlisting} + +整数型を浮動小数点数型に変換すると、値を正確に表現できる場合はその値になる。正確に表現できない場合は近い値になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + double d = 1234567890 ; + // 正確に表現できるかどうかわからない + std::cout << d ; +} +\end{lstlisting} + +整数型と浮動小数点数型の挙動についてはあとの章で詳しく解説する。また、これ以外にも型はいくらでもあるし、読者が新しい型を作り出すこともできる。これもあとの章で詳しく解説する。 + +\hypersection{ch0306}{関数(function)} + +「変数ぐらい知っている。さっさと教えてもらいたい。どうせC++の関数は書きづらいのだろう」と考える読者の皆さん、お待たせしました。こちらがC++の関数\index{かんすう@関数}でございます。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 関数 + auto print = [](auto x) + { + std::cout << x << "\n"s ; + } ; + + // 関数呼び出し + print(123) ; + print(3.14) ; + print("hello") ; +} +\end{lstlisting} + +C++では関数も変数として扱える。\texttt{auto print =}\,までは変数だ。変数の初期化として関数を書いている。より正確にはラムダ式\index{らむだしき@ラムダ式}と呼ばれる関数を値として書くための文法だ。 + +ラムダ式は以下のような文法を持つ。 + +\begin{lstlisting}[style=grammar] +[] // ラムダ式導入部 +() // 引数 +{} // 本体 +\end{lstlisting} + +ラムダ式は\texttt{[]}で始まり、\texttt{()}の中に引数を書き、\texttt{\{\}}の中の文が実行される。 + +例えば以下は引数を2回標準出力する関数だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto twice = [](auto x) + { + std::cout << x << " "s << x << "\n"s ; + } ; + + twice(5) ; +} +\end{lstlisting} + +引数\index{ひきすう@引数}\index{かんすう@関数!ひきすう@引数}は\texttt{auto 引数名}で受け取れる。引数を複数取る場合は、カンマ\texttt{,}で区切る。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto print_two = []( auto x, auto y ) + { + std::cout << x << " "s << y << "\n"s ; + } ; + + print_two( 1, 2 ) ; + print_two( "Pi is", 3.14 ) ; +} +\end{lstlisting} + +引数を取らないラムダ式を書く場合は、単に\texttt{()}と書く。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto no_args = []() + { + std::cout << "Nothing.\n" ; + } ; + + no_args() ; +} +\end{lstlisting} + +関数は演算子\texttt{operator ()}\index{\protect{()}@\texttt{\protect{()}}}を関数の直後に書いて呼び出す。これが演算子であるというのは少し不思議な感じがするが、C++では紛れもなく演算子だ。\texttt{operator +}とか\texttt{operator -}\,などと同じ演算子だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 何もしない関数 + auto func = [](){} ; + + // operator ()の適用 + func() ; + // これもoperator () + func ( ) ; +} +\end{lstlisting} + +演算子\texttt{operator ()}は、ラムダ式そのものに対して適用することもできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 変数fをラムダ式で初期化 + auto f = [](){} ; +(@\ifTombow\pagebreak\fi@) + // 変数fを関数呼び出し + f() ; + + // ラムダ式を関数呼び出し + [](){}() ; +} +\end{lstlisting} + +このコードを見ると、\texttt{operator ()}が単なる演算子であることがよくわかるだろう。\texttt{[]()\{\}}\,がラムダ式でその直後の\texttt{()}が関数呼び出し演算子だ。 + +関数は値を返すことができる。関数から値を返すには、\texttt{return文}\index{return@\texttt{return文}}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto plus = []( auto x, auto y ) + { return x + y ; } ; + + std::cout + << plus( 1, 2 ) << "\n"s + << plus( 1.5, 0.5 ) << "\n"s + << plus( "123"s, "456"s) ; +} +\end{lstlisting} + +関数は\texttt{return}文を実行すると処理を関数の呼び出し元に返す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto f = []() + { + std::cout << "f is called.\n" ; + return 0 ; // ここで処理が戻る + std::cout << "f returned zero.\n" ; + } ; + + auto result = f() ; +} +\end{lstlisting} + +これを実行すると以下のようになる。 + +\begin{lstlisting}[style=terminal] +$ make +f is called. +\end{lstlisting} + +\texttt{return}文以降の文が実行されていないことがわかる。 + +\hypersection{ch0307}{本当の関数} + +実はラムダ式は本当のC++の\texttt{関数}\index{かんすう@関数}ではない。本当の\texttt{関数}はとても書きづらいので心して読むべきだ。 + +読者は本書の冒頭で使った\texttt{main関数}\index{main@\texttt{main}関数}という言葉を覚えているだろうか。覚えていないとしても、サンプルコードに必ずと言っていいほど出てくる\texttt{main}という名前は気になっていたことだろう。 + +\begin{lstlisting}[language={C++}] +int main(){} +\end{lstlisting} + +これを見ると、聡明な読者はラムダ式と似通ったところがあることに気付くだろう。 + +\begin{lstlisting}[style=grammar] +[](){} +\end{lstlisting} + +末尾の\,\texttt{()\{\}}\,が同じだ。これは同じ意味だ。\texttt{()}は関数の引数で、\texttt{\{\}}\,は関数の本体だ。 + +では残りの部分はどうだろうか。\texttt{int}は関数の戻り値の型、\texttt{main}は関数の名前だ。 + +C++の本当の関数は以下のような文法で定義される。 + +\begin{lstlisting}[style=grammar] +int // 戻り値の型 +main // 関数名 +() // 関数の引数 +{} // 関数の本体 +\end{lstlisting} + +試しに、\texttt{int}型の引数を2つ取り足して返す関数\texttt{plus}を書いてみよう。 + +\begin{lstlisting}[language={C++}] +int plus( int x, int y ) +{ + return x + y ; +} + +int main() +{ + auto x = plus( 1, 2 ) ; +} +\end{lstlisting} + +では次に、\texttt{double}型の引数を2つ取り足して返す関数\texttt{plus}を書いてみよう。 + +\begin{lstlisting}[language={C++}] +double plus( double x, double y ) +{ + return x + y ; +} + +(@\ifTombow\pagebreak\fi@) +int main() +{ + auto x = plus( 1.0, 2.0 ) ; +} +\end{lstlisting} + +最後の\texttt{std::string}型の引数を2つ取り足して返す関数\texttt{plus}は読者への課題とする。 + +これがC++の本当の関数だ。C++の関数では、型をすべて明示的に書かなければならない。型を間違えるとエラーだ。 + +しかも、C++の関数は、戻り値の型を正しく返さなければならない。 + +\begin{lstlisting}[language={C++}] +int f() +{ + // エラー、 return文がない +} +\end{lstlisting} + +もし、何も値を返さない関数を書く場合は、どの値でもないという特別な型、\texttt{void}型\index{void@\texttt{void}型}\index{かた@型!void@\texttt{void}}を関数の戻り値の型として書かなければならないという特別なルールまである。 + +\begin{lstlisting}[language={C++}] +void f() +{ + // OK +} +\end{lstlisting} + +ただし、戻り値の型については、具体的な型の代わりに\texttt{auto}\index{auto@\texttt{auto}}を書くこともできる。その場合、\texttt{return}文で同じ型さえ返していれば、気にする必要はない。 + +\begin{lstlisting}[language={C++}] +// void +auto a() { } +// int +auto b() { return 0 ; } +// double +auto c() { return 0.0 ; } +// std::string +auto d() { return ""s ; } + +// エラー +// return文の型が一致しない。 +auto e() +{ + return 0 ; + return 0.0 ; +} +\end{lstlisting} + diff --git a/TeX/004-debug-compile-error.tex b/TeX/004-debug-compile-error.tex new file mode 100644 index 0000000..f1a77d2 --- /dev/null +++ b/TeX/004-debug-compile-error.tex @@ -0,0 +1,419 @@ +\hyperchapter{ch04}{デバッグ:\\コンパイルエラーメッセージの読み方}{デバッグ:コンパイルエラーメッセージの読み方} + +やれやれ疲れた。この辺でひと休みして、デバッグ\index{でばつぐ@デバッグ}について考えよう。まずはコンパイルエラー\index{こんぱいるえら@コンパイルエラー}についてだ。 + +プログラムにはさまざまなバグ\index{ばぐ@バグ}があるが、コンパイルエラーは最も簡単なバグだ。というのも、プログラムのバグの存在が実行前に発覚したわけだから、手間が省ける。もしコンパイルエラーにならない場合、実行した結果から、バグがあるかどうかを判断しなければならない。 + +読者の中には、せっかく書いたソースコードをコンパイルしたらコンパイルエラーが出たので、運が悪かったとか、失敗したとか、怒られてつらい気持ちになったなどと感じることがあるかもしれない。しかしそれは大違いだ。コンパイラーによって読者はプログラムを実行することなくバグが発見できたのだから、読者は運が良かった、大成功した、褒められて最高の気持ちになったと感じるべきなのだ。 + +さあ皆さんご一緒に、 + +\begin{itemize} +\item + コンパイルエラーは普通 +\item + コンパイルエラーが出たらありがとう +\item + コンパイルエラーが出たら大喜び +\end{itemize} + +熟練のプログラマーは自分の書いたコードがコンパイルエラーを出さずに一発でコンパイルが通った場合、逆に不安になるくらいだ。 + +もしバグがあるのにコンパイルエラーが出なければ、バグの存在に気が付かないまま、読者の書いたソフトウェアは広く世の中に使われ、10年後、20年後に最もバグが発見されてほしくない方法で発見されてしまうかもしれない。すなわち、セキュリティ上問題となる脆弱性という形での発覚だ。しかし安心してほしい。いま読者が出したコンパイルエラーによって、そのような悲しい未来の可能性は永久に排除されたのだ。コンパイルエラーはどんどん出すとよい。 + +コンパイルエラーの原因は2つ。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 文法エラー +\item + 意味エラー +\item + コンパイラーのバグ +\end{enumerate} + +3つだった。コンパイルエラーの原因は3つ。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 文法エラー +\item + 意味エラー +\item + コンパイラーのバグ +\item + コンピューターの故障 +\end{enumerate} + +4つだった。ただ、3.と4.はめったにないから無視してよい。 + +\hypersection{ch0401}{文法エラー} + +文法エラー\index{ぶんぽうえら@文法エラー}とは、C++というプログラミング言語の文法に従っていないエラーのことだ。これはC++として解釈できないので、当然エラーになる。 + +よくある文法エラーとしては、文末のセミコロンを打ち忘れたものがある。例えば以下のコードには間違いがある。 + +\begin{lstlisting}[language=c++] +int main() +{ + auto x = 1 + 1 + auto y = x + 1 ; +} +\end{lstlisting} + +これをコンパイルすると以下のようにコンパイルエラーメッセージが出力される。 + +\begin{lstlisting}[style=terminal] +$ make +g++ -std=c++17 -Wall --pedantic-error -include all.h main.cpp -o program +main.cpp: In function ‘int main()’: +main.cpp:4:5: error: expected ‘,’ or ‘;’ before ‘auto’ + auto y = x + 1 ; + ^~~~ +main.cpp:3:10: warning: unused variable ‘x’ [-Wunused-variable] + auto x = 1 + 1 + ^ +Makefile:4: recipe for target 'program' failed +make: *** [program] Error 1 +\end{lstlisting} + +コンパイラーのメッセージを読み慣れていない読者はここで考えることを放棄してコンピューターの電源を落とし家を出て街を徘徊し夕日を見つめて人生、宇宙、すべてについての究極の質問への答えを模索してしまうことだろう。 + +しかし恐れるなかれ。コンパイラーのエラーメッセージを読み解くのは難しくない。 + +まず最初の2行を見てみよう。 + +\begin{lstlisting}[style=terminal] +$ make +g++ -std=c++17 -Wall --pedantic-error -include all.h main.cpp -o program +\end{lstlisting} + +1行目はシェルに\texttt{make}を実行させるためのコマンド、2行目は\texttt{make}が実行したレシピの中身だ。これはコンパイラーによるメッセージではない。 + +3行目からはコンパイラーによる出力だ。 + +\begin{lstlisting}[style=terminal] +main.cpp: In function ‘int main()’: +\end{lstlisting} + +コンパイラーはソースファイル\texttt{main.cpp}の中の、\texttt{int main()}という関数について、特に言うべきことがあると主張している。 + +言うべきこととは以下だ。 + +\begin{lstlisting}[style=terminal] +main.cpp:4:5: error: expected ‘,’ or ‘;’ before ‘auto’ + auto y = x + 1 ; + ^~~~ +\end{lstlisting} + +GCCというコンパイラーのエラーメッセージは、以下のフォーマットを採用している。 + +\begin{lstlisting}[style=grammar] +ソースファイル名:行番号:列番号: メッセージの種類: メッセージの内容 +\end{lstlisting} + +ここでのメッセージの種類は\texttt{error}、つまりこのメッセージはエラーを伝えるものだ。 + +ソースファイル名は\texttt{main.cpp}、つまりエラーは\texttt{main.cpp}の中にあるということだ。 + +行番号というのは、最初の行を1行目とし、改行ごとにインクリメントされていく。今回のソースファイルの場合、以下のようになる。 + +\begin{lstlisting}[language=c++] +1 int main() +2 { +3 auto x = 1 + 1 +4 auto y = x + 1 ; +5 } +\end{lstlisting} + +もし読者が素晴らしいテキストエディターであるVimを使っている場合、\texttt{:set nu}すると行番号を表示できる。 + +その上でエラーメッセージの行番号を確認すると\texttt{4}とある。つまりコンパイラーは4行目に問題があると考えているわけだ。 + +4行目を確認してみよう。 + +\begin{lstlisting}[language=c++] + auto y = x + 1 ; +\end{lstlisting} + +何の問題もないように見える。さらにエラーメッセージを読んでみよう。 + +列番号が\texttt{5}となっている。列番号というのは、行頭からの文字数だ。最初の文字を1文字目とし、文字ごとにインクリメントされていく。 + +\begin{lstlisting}[language=c++] +123456789... + auto y = x + 1 ; +\end{lstlisting} + +4行目は空白文字を4つ使ってインデントしているので、\texttt{auto}の\texttt{a}の列番号は\texttt{5}だ。ここに問題があるのだろうか。何も問題がないように見える。 + +この謎を解くためには、メッセージの内容を読まなければならない。 + +\begin{lstlisting}[style=terminal] +expected ‘,’ or ‘;’ before ‘auto’ + auto y = x + 1 ; + ^~~ +\end{lstlisting} + +これは日本語に翻訳すると以下のようになる。 + +\begin{lstlisting}[style=terminal] +‘auto’の前に','か';'があるべき + auto y = x + 1 ; + ^~~ +\end{lstlisting} + +1行目はエラー内容をテキストで表現したものだ。これによると、\texttt{'auto'}\,の前に\,\texttt{','}\,か\,\texttt{';'}\,があるべきとあるが、やはりまだわからない。 + +2行目は問題のある箇所のソースコードを部分的に抜粋したもので、3行目はそのソースコードの問題のある文字を視覚的にわかりやすく示しているものだ。 + +ともかく、コンパイラーの指示に従って\,\texttt{'auto'}\,の前に\,\texttt{','}\,を付けてみよう。 + +\begin{lstlisting}[language=c++] + ,auto y = x + 1 ; +\end{lstlisting} + +これをコンパイルすると、また違ったエラーメッセージが表示される。 + +\begin{lstlisting}[style=terminal] +main.cpp: In function ‘int main()’: +main.cpp:4:6: error: expected unqualified-id before ‘auto’ + ,auto y = x + 1 ; + ^~~~ +\end{lstlisting} + +では\,\texttt{';'}\,ならばどうか。 + +\begin{lstlisting}[language=c++] + ;auto y = x + 1 ; +\end{lstlisting} + +これはコンパイルが通るようだ。 + +しかしなぜこれでコンパイルが通るのだろう。そのためには、コンパイラーが問題だとした行の1つ上の行を見る必要がある。 + +\begin{lstlisting}[language=c++] + auto x = 1 + 1 + auto y = x + 1 ; +\end{lstlisting} + +コンパイラーにとって、改行は空白文字と同じくソースファイル中の意味のあるトークン(キーワードや名前や記号)を区切る文字でしかない。コンパイラーにとって、このコードは実質以下のように見えている。 + +\begin{lstlisting}[language=c++] +auto x=1+1 auto y=x+1; +\end{lstlisting} + +\texttt{"1 auto"}\,というのは文法エラーだ。なのでコンパイラーは文法エラーが発覚する最初の文字である\,\texttt{'auto'}\,の\,\texttt{'a'}\,を指摘したのだ。 + +人間にとって自然になるように修正すると、コンパイラーが指摘した行の1つ上の行の行末に\,\texttt{';'}\,を追加すべきだ。 + +\begin{lstlisting}[language=c++] + auto x = 1 + 1 ; + auto y = x + 1 ; +\end{lstlisting} + +さて、問題自体は解決したわけだが、残りのメッセージも見ていこう。 + +\begin{lstlisting}[style=terminal] +main.cpp:3:10: warning: unused variable ‘x’ [-Wunused-variable] + auto x = 1 + 1 +\end{lstlisting} + +これはコンパイラーによる警告メッセージ\index{けいこくめつせじ@警告メッセージ}だ。警告メッセージについて詳しくは、デバッグ:警告メッセージの章で解説する。 + +\begin{lstlisting}[style=terminal] +Makefile:4: recipe for target 'program' failed +make: *** [program] Error 1 +\end{lstlisting} + +これはGNU Makeによるメッセージだ。GCCがソースファイルを正しくコンパイルできず、実行が失敗したとエラーを返したので、レシピの実行が失敗したことを伝えるメッセージだ。 + +プログラムはどうやってエラーを通知するのか。\texttt{main}関数の戻り値によってだ。\texttt{main}関数は関数であるので、戻り値がある。\texttt{main}関数の戻り値は\texttt{int}型だ。 + +\begin{lstlisting}[language={C++}] +// 戻り値の型 +int +// main関数の残りの部分 +main() { } +\end{lstlisting} + +\texttt{main}関数が何も値を返さない場合、\texttt{return 0}したものとみなされる。\texttt{main}関数が\texttt{0}もしくは\texttt{EXIT\_SUCCESS}\index{EXIT\_SUCCESS@\texttt{EXIT\_SUCCESS}}を返した場合、プログラムの実行の成功を通知したことになる。 + +\begin{lstlisting}[language={C++}] +// 必ず実行が成功したと通知するプログラム +int main() +{ + return 0 ; +} +\end{lstlisting} + +プログラムの実行が失敗した場合、\texttt{main}関数は\texttt{EXIT\_FAILURE}\index{EXIT\_FAILURE@\texttt{EXIT\_FAILURE}}を返すことでエラーを通知できる。 + +\begin{lstlisting}[language={C++}] +// 必ず実行が失敗したと通知するプログラム +int main() +{ + return EXIT_FAILURE ; +} +\end{lstlisting} + +\texttt{EXIT\_SUCCESS}と\texttt{EXIT\_FAILURE}はマクロだ。 + +\begin{lstlisting}[language=c++] +#define EXIT_SUCCESS +#define EXIT_FAILURE +\end{lstlisting} + +その中身はC++標準規格では規定されていない。どうしても値を知りたい場合は以下のプログラムを実行してみるとよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout + << "EXIT_SUCCESS: "s << EXIT_SUCCESS << "\n"s + << "EXIT_FAILURE: "s << EXIT_FAILURE ; +} +\end{lstlisting} + +文法エラーというのは厄介なバグだ。というのも、コンパイラーというのは正しい文法のソースファイルを処理するように作られている。文法を間違えた場合、ソースファイル全体が正しくないということになる。コンパイラーは文法違反に遭遇した場合、なるべく人間がよく間違えそうなパターンをヒューリスティックに指摘することもしている。そのため、エラーメッセージに指摘された行番号と列番号は、必ずしも人間にとっての問題の箇所と一致しない。 + +もう1つ例を見てみよう。 + +\begin{lstlisting}[language=c++] +int main() +{ + // 引数を3つ取って足して返す関数 + auto f = [](auto a, auto b, auto c) + { return a + b + c ; } ; + +(@\ifTombow\pagebreak\fi@) + std::cout << f(1+(2*3),4-5,6/(7-8))) ; +} +\end{lstlisting} + +GCCによるコンパイルエラーメッセージだけ抜粋すると以下のとおり。 + +\begin{lstlisting}[style=terminal] +main.cpp: In function ‘int main()’: +main.cpp:7:40: error: expected ‘;’ before ‘)’ token + std::cout << f(1+(2*3),4-5,6/(7-8))) ; + ^ +\end{lstlisting} + +さてさっそく読んでみよう。すでに学んだように、GCCのメッセージのフォーマットは以下のとおりだ。 + +\begin{lstlisting}[style=grammar] +ソースファイル名:行番号:列番号: メッセージの種類: メッセージの内容 +\end{lstlisting} + +これに当てはめると、問題はソースファイル\texttt{main.cpp}の7行目の40列目にある。 + +エラーメッセージは、「\texttt{';'}\,がトークン\,\texttt{')'}\,の前にあるべき」だ。 + +トークン(token)というのは\,\texttt{'std'}\,とか\,\texttt{'::'}\,とか\,\texttt{'cout'}\,といったソースファイルの空白文字で区切られた最小の文字列の単位のことだ。 + +抜粋されたソースコードに示された問題の箇所、つまり7行目40列目にあるトークンは\,\texttt{')'}\,だ。この前に\,\texttt{';'}\,が必要とはどういうことだろう。 + +問題を探るため、7行目のトークンを詳しく分解してみよう。以下は7行目と同じソースコードだが、トークンをわかりやすく分解してある。 + +\begin{lstlisting}[language=c++] +std::cout << // 標準出力 +f // 関数名 + ( // 開き括弧 + 1+(2*3), // 第1引数 + 4-5, // 第2引数 + 6/(7-8) // 第3引数 + ) // 開き括弧に対応する閉じ括弧 + ) // ??? + ; // 終端文字 +\end{lstlisting} + +これを見ると、閉じ括弧が1つ多いことがわかる。 + +\clearpage +\hypersection{ch0402}{意味エラー} + +意味エラー\index{いみえら@意味エラー}とは、ソースファイルは文法的に正しいが、意味的に間違っているコンパイルエラーのことだ。 + +さっそく例を見ていこう。 + +\begin{lstlisting}[language=c++] +int main() +{ + auto x = 1.0 % 1.0 ; +} +\end{lstlisting} + +このコードをコンパイルすると出力されるエラーメッセージは以下のとおり。 + +\begin{lstlisting}[style=terminal] +main.cpp: In function ‘int main()’: +main.cpp:3:18: error: invalid operands of types ‘double’ and ‘double’ to binary + ‘operator%’ + auto x = 1.0 % 1.0 ; + ~~~~^~~~~ +\end{lstlisting} + +問題の箇所は3行目の18列目、\texttt{'\%'}\,だ。 + +エラーメッセージは、「二項\,\texttt{'operator\%'}\,に対して不適切なオペランドである型\,\texttt{'double'}\,と{\allowbreak}\,\texttt{'double'}」とある。 + +前の章を読み直すとわかるとおり、\texttt{operator \%}は剰余を計算する演算子だが、この演算子には\texttt{double}型を渡すことはできない。 + +このコードはどうだろう。 + +\begin{lstlisting}[language=c++] +// 引数を1つ取る関数 +void f( int x ) { } + +int main() +{ + // 引数を2つ渡す + f( 1, 2 ) ; +} +\end{lstlisting} + +このようなエラーメッセージになる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=terminal] +main.cpp: In function ‘int main()’: +main.cpp:7:13: error: too many arguments to function ‘void f(int)’ + f( 1, 2 ) ; + ^ +main.cpp:2:6: note: declared here + void f( int x ) { } + ^ +\end{lstlisting} + +問題の箇所は7行目。「関数\,\texttt{'void f(int)'}\,に対して実引数が多すぎる」とある。\texttt{関数f}は引数を1つしか取らないのに、2つの引数を渡しているのがエラーの原因だ。 + +2つ目のメッセージはエラーではなくて、エラーを補足説明するための注記(note)メッセージだ。ここで言及している\texttt{関数f}とは、2行目に宣言されていることを説明してくれている。 + +意味エラーはときとしておぞましいほどのエラーメッセージを生成することがある。例えば以下の一見無害そうなコードだ。 + +\begin{lstlisting}[language=c++] +int main() +{ + "hello"s << 1 ; +} +\end{lstlisting} + +このコードは文法的に正しいが、意味的に間違っているコードだ。このコードをコンパイルすると膨大なエラーメッセージが出力される。しかも問題の行番号特定以外、大して役に立たない。 + +\hypersection{ch0403}{コンパイラーのバグ} + +C++コンパイラーもソフトウェアであり、バグがある。コンパイラーにバグがある場合、正しいC++のソースファイルがコンパイルできないことがある。 + +読者がそのようなコンパイラーの秘孔を突くコードを書くことはまれだ。しかし、もしそのようなコードを偶然にも書いてしまった場合、GCCは、 +\begin{lstlisting}[style=terminal] +gcc: internal compiler error: エラー内容 +Please submit a full bug report, +with preprocessed source if appropriate. +See <ドキュメントへのファイルパス> for instructions. +\end{lstlisting} +のようなメッセージを出力する。 + +これはGCCのバグなので、見つけた読者は適切な方法でバグ報告をしよう。 diff --git a/TeX/005-the-restaurant-at-the-end-of-the-branch.tex b/TeX/005-the-restaurant-at-the-end-of-the-branch.tex new file mode 100644 index 0000000..0ddafa2 --- /dev/null +++ b/TeX/005-the-restaurant-at-the-end-of-the-branch.tex @@ -0,0 +1,1012 @@ +\hyperchapter{ch05}{条件分岐の果てのレストラン}{条件分岐の果てのレストラン} + +さてC++の勉強に戻ろう。この章では条件分岐について学ぶ。 + +\hypersection{ch0501}{複合文} + +条件分岐とループについて学ぶ前に、まず\texttt{複合文}(compound statement)や\texttt{ブロック}(block)と呼ばれている、複数の文をひとまとめにする文について学ばなければならない。 + +C++では\texttt{文}(statement)\index{ぶん@文}が実行される。\texttt{文}については詳しく説明すると長くなるが、\texttt{';'}\,\index{;@\texttt{;}}で区切られたものが\texttt{文}だ。 +\index{せみころん@セミコロン} + +\begin{lstlisting}[language={C++}] +int main() +{ + // 文 + auto x = 1 + 1 ; + // 文 + std::cout << x ; + + // 空文 + // 実は空っぽの文も書ける。 + ; +} +\end{lstlisting} + +複数の\texttt{文}を\,\texttt{\{\}}\,\index{\{\}@\texttt{\{\}}}で囲むことで、1つの文として扱うことができる。これを\texttt{複合文}\index{ふくごうぶん@複合文}という。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + // 複合文開始 + { + std::cout << "hello\n"s ; + std::cout << "hello\n"s ; + } // 複合文終了 + + // 別の複合文 + { std::cout << "world\n"s ; } + + // 空の複合文 + { } +} +\end{lstlisting} + +\texttt{複合文}には\,\texttt{';'}\,はいらない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // ;はいらない + { } + + // これは空の複合文に続いて + // 空文があるだけのコード + { } ; +} +\end{lstlisting} + +\texttt{複合文}の中に\texttt{複合文}を書くこともできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + {{{}}} ; +} +\end{lstlisting} + +\texttt{関数の本体}としての一番外側\,\texttt{'\{\}'}\,はこの\texttt{複合文}とは別のものだが、読者はまだ気にする必要はない。 + +\texttt{複合文}は複数の\texttt{文}をひとまとめにして、1つの\texttt{文}として扱えるようにするぐらいの意味しか持っていない。ただし、変数の見え方に影響する。変数は宣言された最も内側の複合文の中でしか使えない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto a = 0 ; + + { + auto b = 0 ; + { + auto c = 0 ; + // cはここまで使える + } + // bはここまで使える + } + // aはここまで使える +} +\end{lstlisting} + +これを専門用語では\texttt{変数}の\texttt{寿命}\index{へんすう@変数!じゆみよう@寿命}とか\texttt{ブロックスコープ}(block--scope)\index{ぶろつくすこぷ@ブロックスコープ}という。 + +内側のブロックスコープの変数が、外側のブロックスコープの変数と同じ名前を持っていた場合はエラーではない。外側の変数が内側の変数で隠される。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto x = 0 ; + { + auto x = 1 ; + { + auto x = 2 ; + // 2 + std::cout << x ; + } + // 1 + std::cout << x ; + x = 42 ; + // 42 + std::cout << x ; + } + // 0 + std::cout << x ; +} +\end{lstlisting} + +慣れないうちは驚くかもしれないが、多くのプログラミング言語はこのような挙動になっているものだ。 + +\clearpage +\hypersection{ch0502}{条件分岐} + +すでに読者はさまざまな数値計算を学んだ。読者は\texttt{12345 + 6789}の答えや、\texttt{8073 * 132 / 5}の答えを計算できる上、この2つの答えをさらに掛け合わせた結果だって計算できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto a = 12345 + 6789 ; + auto b = 8073 * 132 / 5 ; + auto sum = a + b ; + + std::cout + << "a=12345 + 6789=" << a << "\n"s + << "b=8073 * 132 / 5=" << b << "\n"s + << "a+b=" << sum << "\n"s ; +} +\end{lstlisting} + +なるほど、答えがわかった。ところで変数\texttt{a}と変数\texttt{b}はどちらが大きいのだろうか。大きい変数だけ出力したい。この場合は条件分岐\index{じようけんぶんき@条件分岐}を使う。 + +C++では条件分岐に\texttt{if文}\index{if@\texttt{if}文}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto a = 12345 + 6789 ; + auto b = 8073 * 132 / 5 ; + + + if ( a < b ) + { + // bが大きい + std::cout << b ; + } + else + { + // aが大きい + std::cout << a ; + } +} +\end{lstlisting} + +\texttt{if文}は以下のように書く。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=grammar] +if ( 条件 ) +文1 +else +文2 +\end{lstlisting} + +\texttt{条件}が真(\texttt{true})\index{しん@真}\index{true@\texttt{true}}のときは\texttt{文1}が実行され、偽(\texttt{false})\index{ぎ@偽}\index{false@\texttt{false}}のときは\texttt{文2}が実行される。 + +\texttt{else}\index{else@\texttt{else}}の部分は書かなくてもよい。 + +\begin{lstlisting}[style=grammar] +if ( 条件 ) +文1 +文2 +\end{lstlisting} + +その場合、\texttt{条件}が真のときだけ\texttt{文1}が実行される。条件の真偽にかかわらず\texttt{文2}は実行される。 + +\begin{lstlisting}[language={C++}] +int main() +{ + if ( 2 < 1 ) + std::cout << "sentence 1.\n" ; // 文1 + std::cout << "sentence 2.\n" ; // 文2 +} +\end{lstlisting} + +この例では、\texttt{2}が\texttt{1}より小さい場合は\texttt{文1}が実行される。\texttt{文2}は必ず実行される。 + +条件次第で複数の文を実行したい場合、\texttt{複合文}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + if ( 1 < 2 ) + { + std::cout << "yes!\n" ; + std::cout << "yes!\n" ; + } +} +\end{lstlisting} + +\texttt{条件}とか\texttt{真偽}についてはとてもとても深い話があるのだが、その解説はあとの章に回すとして、まずは以下の比較演算子\index{ひかくえんざんし@比較演算子}を覚えよう。 + +\vskip 1.0zw +\begin{small} +\begin{longtable}[]{@{\,\,}ll@{\,\,}} +\hline%\toprule +\textsf{演算子} & \textsf{意味}\tabularnewline +\hline%\midrule +\endhead +\texttt{a == b} & \texttt{a}は\texttt{b}と等しい\tabularnewline +\texttt{a != b} & \texttt{a}は\texttt{b}と等しくない\tabularnewline +\texttt{a < b} & \texttt{a}は\texttt{b}より小さい\tabularnewline +\texttt{a <= b} & \texttt{a}は\texttt{b}より小さい、もしくは等しい\tabularnewline +\texttt{a > b} & \texttt{a}は\texttt{b}より大きい\tabularnewline +\texttt{a >= b} & \texttt{a}は\texttt{b}より大きい、もしくは等しい\tabularnewline +\hline%\bottomrule +\end{longtable} +\end{small} +\index{==@\texttt{==}}\index{\protect{!}=@\texttt{\protect{!}=}}\index{<@\texttt{<}}\index{<=@\texttt{<=}}\index{>@\texttt{>}}\index{>=@\texttt{>=}} + +真(\texttt{true})というのは、意味が真であるときだ。正しい、成り立つ、正解などと言い換えてもよい。それ以外の場合はすべて偽(\texttt{false})だ。正しくない、成り立たない、不正解などと言い換えてもいい。 + +整数や浮動小数点数の場合、話は簡単だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 1は2より小さいか? + if ( 1 < 2 ) + { // 真、 お使いのコンピューターは正常です + std::cout << "Your computer works just fine.\n"s ; + } + else + { + // 偽、 お使いのコンピューターには深刻な問題があります + std::cout << "Your computer has serious issues.\n"s ; + } +} +\end{lstlisting} + +文字列の場合、内容が同じであれば等しい。違うのであれば等しくない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto a = "dog"s ; + auto b = "dog"s ; + auto c = "cat"s ; + + if ( a == b ) + { + std::cout << "a == b\n"s ; + } + else + { + std::cout << "a != b\n" ; + } + + if ( a == c ) + { + std::cout << "a == c\n" ; + } + else + { + std::cout << "a != c\n" ; + } +} +\end{lstlisting} + +では文字列に大小はあるのだろうか。文字列に大小はある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto cat = "cat"s ; + auto dog = "dog"s ; + + if ( cat < dog ) + { // 猫は小さい + std::cout << "cat is smaller.\n"s ; + } + else + { // 犬は小さい + std::cout << "dog is smaller.\n"s ; + } + + auto longcat = "longcat"s ; + + if ( longcat > cat ) + { // longcatは長い + std::cout << "Longcat is Looong.\n"s ; + } + else + { + std::cout << "Longcat isn't that long. Sigh.\n"s ; + } +} +\end{lstlisting} + +実行して確かめてみよう。ほとんどの読者の実行環境では以下のようになるはずだ。ほとんどの、というのは、そうではない環境も存在するからだ。読者がそのような稀有な環境を使っている可能性はまずないだろうが。 + +\begin{lstlisting}[style=terminal] +cat is smaller. +Longcat is Looong. +\end{lstlisting} + +なるほど。\texttt{"cat"s}は\,\texttt{"dog"s}よりも小さく(?)、\texttt{"longcat"s}は\,\texttt{"cat"s}よりも長い(大きい?)ようだ。なんだかよくわからない結果になった。 + +これはどういうことなのか。もっと簡単な文字列で試してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto x = ""s ; + + // aとbはどちらが小さいのだろうか? + if ( "a"s < "b"s ) + { x = "a"s ; } + else + { x = "b"s ; } + + // 小さい方の文字が出力される + std::cout << x ; +} +\end{lstlisting} + +これを実行すると\texttt{a}と出力される。すると\,\texttt{"a"s}は\,\texttt{"b"s}より小さいようだ。 + +もっと試してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto x = ""s ; + if ( "aa"s < "ab"s ) + { x = "aa"s ; } + else + { x = "ab"s ; } + + // 小さい文字列が出力される + std::cout << x ; +} +\end{lstlisting} + +これを実行すると、\texttt{aa}と出力される。すると\,\texttt{"aa"s}は\,\texttt{"ab"s}より小さいことになる。 + +文字列の大小比較は文字単位で行われる。まず最初の文字が大小比較される。もし等しい場合は、次の文字が大小比較される。等しくない最初の文字の結果が、文字列の大小比較の結果となる。 + +\hypersection{ch0503}{条件式} + +\hypersubsection{ch050301}{条件とは何だろう} + +\texttt{if文}\index{if@\texttt{if}文}の中で書く\texttt{条件}(condition)\index{じようけん@条件}は、\texttt{条件式}(conditional expression)\index{じようけんしき@条件式}とも呼ばれている\texttt{式}(expression)\index{しき@式}の一種だ。\texttt{式}というのは例えば\,\texttt{"1+1"}\,のようなものだ。\texttt{式}は\texttt{文}\index{ぶん@文}の中に書くことができ、これを\texttt{式文}(expression statement)\index{しきぶん@式文}という。 + +\begin{lstlisting}[language={C++}] +int main() +{ + 1 + 1 ; // 式文 +} +\end{lstlisting} + +\texttt{"a==b"}\,や\,\texttt{"a 2 ; +} +\end{lstlisting} + +先に説明した\texttt{if文}の\texttt{条件}が「正しい」というのは\texttt{true}のことで、「間違っている」というのは\texttt{false}のことだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 出力される + if ( true ) + std::cout << "true\n"s ; + + // 出力されない。 + if ( false ) + std::cout << "false\n"s ; +} +\end{lstlisting} + +\hypersection{ch0505}{bool型の演算} + +\texttt{bool}型にはいくつかの演算が用意されている。 + +\hypersubsection{ch050501}{論理否定: operator !} +\index{ろんりひてい@論理否定}\index{\protect{!}@\texttt{\protect{!}}} + +\texttt{"!a"}\,は\texttt{a}が\texttt{true}の場合\texttt{false}に、\texttt{false}の場合\texttt{true}になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << std::boolalpha ; + + // false + std::cout << !true << "\n"s ; + + // true + std::cout << !false << "\n"s ; +} +\end{lstlisting} + +論理否定演算子を使うと、\texttt{false}のときのみ実行されてほしい条件分岐が書きやすくなる。 + +\begin{lstlisting}[language={C++}] +// ロケットが発射可能かどうかを返す関数 +bool is_rocket_ready_to_launch() +{ + // まだだよ + return false ; +} + +(@\ifTombow\pagebreak\fi@) +int main() +{ + + // ロケットが発射可能ではないときに実行される + if ( !is_rocket_ready_to_launch() ) + { // もうしばらくそのままでお待ちください + std::cout << "Standby...\n" ; + } +} +\end{lstlisting} + +この例では、ロケットが発射可能でない場合のみ、待つようにアナウンスする。 + +同じように、\texttt{true}のときに実行されてほしくない条件分岐も書ける。 + +\begin{lstlisting}[language={C++}] +// ロケットが発射可能かどうかを返す関数 +bool is_rocket_ready_to_launch() +{ + // もういいよ + return true ; +} + +int main() +{ + // ロケットが発射可能なときに実行される + if ( !is_rocket_ready_to_launch() ) + { // カウントダウン + std::cout << "3...2...1...Hallelujah!\n"s ; + } + +} +\end{lstlisting} + +この2つの例では、ロケットの状態が実行すべき条件ではないので、正しく何も出力されない。 + +\hypersubsection{ch050502}{同値比較: operator ==, !=} +\index{どうちひかく@同値比較}\index{==@\texttt{==}}\index{\protect{!}=@\texttt{\protect{!}=}} + +\texttt{bool}型の値の同値比較はわかりやすい。\texttt{true}は\texttt{true}と等しく、\texttt{false}は\texttt{false}と等しく、\texttt{true}と\texttt{false}は等しくない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << std::boolalpha ; + auto print = [](auto b) + { std::cout << b << "\n"s ; } ; + + print( true == true ) ; // true + print( true == false ) ; // false + print( false == true ) ; // false + print( false == false ) ; // true + + print( true != true ) ; // false + print( true != false ) ; // true + print( false != true ) ; // true + print( false != false ) ; // false +} +\end{lstlisting} + +比較演算子の結果は\texttt{bool}値になるということを覚えているだろうか。\texttt{"1 < 2"}\,は\texttt{true}になり、{\break}\texttt{"1 > 2"}\,は\texttt{false}になる。 + +\texttt{bool}値同士も同値比較ができるということは、\texttt{"(1 < 2) == true"}\,のように書くことも可能だということだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + bool b = (1 < 2) == true ; +} +\end{lstlisting} + +\texttt{"(1<2)"}\,は\texttt{true}なので、\texttt{"(1<2)==true"}\,は\,\texttt{"true==true"}\,と同じ意味になる。この結果はもちろん\,\texttt{"true"}\,だ。 + +\hypersubsection{ch050503}{論理積: operator \&\&} +\index{ろんりせき@論理積}\index{\&\&@\texttt{\&\&}} + +\texttt{"a \&\& b"}\,は\texttt{a}と\texttt{b}がともに\texttt{true}のときに\texttt{true}となる。それ以外の場合は\texttt{false}となる。これを論理積という。 + +表にまとめると以下のようになる。 + +\vskip 1.0zw +\begin{small} +\begin{longtable}[]{@{\,\,}ll@{\,\,}} +\hline%\toprule +\textsf{式} & \textsf{結果}\tabularnewline +\hline%\midrule +\endhead +\texttt{false \&\& false} & \texttt{false}\tabularnewline +\texttt{false \&\& true} & \texttt{false}\tabularnewline +\texttt{true \&\& false} & \texttt{false}\tabularnewline +\texttt{true \&\& true} & \texttt{true}\tabularnewline +\hline%\bottomrule +\end{longtable} +\end{small} + +さっそく確かめてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << std::boolalpha ; + auto print = []( auto b ) + { std::cout << b << "\n"s ; } ; + + print( false && false ) ; // false + print( false && true ) ; // false + print( true && false ) ; // false + print( true && true ) ; // true +} +\end{lstlisting} + +論理積は、「AかつB」\index{かつ}を表現するのに使える。 + +例えば、人間の体温が平熱かどうかを判断するプログラムを書くとする。36.1{\,\textcelsius\,}以上、37.2{\,\textcelsius\,}以下を平熱とすると、\texttt{if}文を使って以下のように書くことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 体温 + double temperature = 36.6 ; + + // 36.1度以上 + if ( temperature >= 36.1 ) + if ( temperature <= 37.2 ) + { std::cout << "Good.\n"s ; } + else + { std::cout << "Bad.\n"s ; } + else + { std::cout << "Bad.\n"s ; } +} +\end{lstlisting} + +このコードは、\texttt{operator \&\&}\,を使えば簡潔に書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + double temperature = 36.6 ; + + if ( ( temperature >= 36.1 ) && ( temperature <= 37.2 ) ) + { std::cout << "Good.\n"s ; } + else + { std::cout << "Bad.\n"s ; } +} +\end{lstlisting} + +\hypersubsection{ch050504}{論理和: operator \textbar\textbar{}} +\index{ろんりわ@論理和}\index{\textbar\textbar{}@\texttt{\textbar\textbar{}}} + +\texttt{"a || b"}\,は\texttt{a}と\texttt{b}がともに\texttt{false}のときに\texttt{false}となる。それ以外の場合は\texttt{true}となる。これを論理和という。 + +表にまとめると以下のようになる。 + +\begin{small} +\begin{longtable}[]{@{\,\,}ll@{\,\,}} +\hline%\toprule +\textsf{式} & \textsf{結果}\tabularnewline +\hline%\midrule +\endhead +\texttt{false || false} & \texttt{false}\tabularnewline +\texttt{false || true} & \texttt{true}\tabularnewline +\texttt{true || false} & \texttt{true}\tabularnewline +\texttt{true || true} & \texttt{true}\tabularnewline +\hline%\bottomrule +\end{longtable} +\end{small} + +さっそく確かめてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << std::boolalpha ; + auto print = []( auto b ) + { std::cout << b << "\n"s ; } ; + + print( false || false ) ; // false + print( false || true ) ; // true + print( true || false ) ; // true + print( true || true ) ; // true +} +\end{lstlisting} + +論理和は、「AもしくはB」\index{もしくは}を表現するのに使える。 + +例えば、ある遊園地の乗り物には安全上の理由で身長が1.1m未満、あるいは1.9mを超える人は乗れないものとする。この場合、乗り物に乗れる身長かどうかを確かめるコードは、\texttt{if文}を使うと以下のようになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + double height = 1.3 ; + + if ( height < 1.1 ) + { std::cout << "No."s ; } + else if ( height > 1.9 ) + { std::cout << "No."s ; } + else + { std::cout << "Yes."s ; } +} +\end{lstlisting} + +論理和を使うと以下のように簡潔に書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + double height = 1.3 ; + + if ( ( height < 1.1 ) || ( height > 1.9 ) ) + { std::cout << "No."s ; } + else + { std::cout << "Yes."s ; } +} +\end{lstlisting} + +\hypersubsection{ch050505}{短絡評価} + +論理積と論理和は短絡評価\index{たんらくひようか@短絡評価}と呼ばれる特殊な評価が行われる。これは、左から右に最小限の評価をするという意味だ。 + +論理積では、\texttt{"a \&\& b"}\,とある場合、\texttt{a}と\texttt{b}がともに\texttt{true}である場合のみ、結果は\texttt{true}になる。もし、\texttt{a}が\texttt{false}であった場合、\texttt{b}の結果如何にかかわらず結果は\texttt{false}となるので、\texttt{b}は評価されない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto a = []() + { + std::cout << "a\n"s ; + return false ; + } ; + auto b = []() + { + std::cout << "b\n"s ; + return true ; + } ; + + bool c = a() && b() ; + std::cout << std::boolalpha << c ; +} +\end{lstlisting} + +これを実行すると以下のようになる。 + +\begin{lstlisting}[style=terminal] +a +false +\end{lstlisting} + +関数呼び出し\,\texttt{"a()"}\,の結果は\texttt{false}なので、\texttt{"b()"}\,は評価されない。評価されないということは関数呼び出しが行われず、当然標準出力も行われない。 + +同様に、論理和では、\texttt{"a || b"}\,とある場合、\texttt{a}と\texttt{b}のどちらか片方でも\texttt{true}であれば、結果は\texttt{true}となる。もし、\texttt{a}が\texttt{true}であった場合、\texttt{b}の結果如何にかかわらず結果は\texttt{true}となるので、\texttt{b}は評価されない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto a = []() + { + std::cout << "a\n"s ; + return true ; + } ; +(@\ifTombow\pagebreak\fi@) + auto b = []() + { + std::cout << "b\n"s ; + return false ; + } ; + + bool c = a() || b() ; + std::cout << std::boolalpha << c ; +} +\end{lstlisting} + +結果、 + +\begin{lstlisting}[style=terminal] +a +true +\end{lstlisting} + +\texttt{"b()"}\,が評価されていないことがわかる。 + +\hypersection{ch0506}{boolの変換} +\index{bool@\texttt{bool}型!へんかん@変換} + +\texttt{bool}型の値と演算はこれで全部だ。値は\texttt{true}/\texttt{false}の2つのみ。演算は\,\texttt{==}, \texttt{!=}, \texttt{!}\,と\,\texttt{\&\&}\,と\,\texttt{||}\,の5つだけだ。 + +読者の中には納得のいかないものもいるだろう。ちょっと待ってもらいたい。\texttt{bool}の大小比較できないのだろうか。\texttt{bool}の四則演算はできないのか。\texttt{"if(123)"}\,などと書けてしまうのは何なのか。 + +好奇心旺盛な読者は本書の解説を待たずしてすでに自分でいろいろとコードを書いて試してしまっていることだろう。 + +\texttt{bool}の大小比較はどうなるのだろうか。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << std::boolalpha ; + + bool b = true < false ; + std::cout << b ; +} +\end{lstlisting} + +このコードを実行すると、出力は\,\texttt{"false"}\,だ。\texttt{"true < false"}\,の結果が\,\texttt{"false"}\,だということは、\texttt{true}は\texttt{false}より大きいということになる。 + +四則演算はどうか? + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + auto print = [](auto x) + { std::cout << x << "\n"s ; } ; + + print( true + true ) ; + print( true + false ) ; + print( false + true ) ; + print( false + false ) ; +} +\end{lstlisting} + +結果、 + +\begin{lstlisting}[style=terminal] +2 +1 +1 +0 +\end{lstlisting} + +不思議な結果だ。\texttt{"true+true"}\,は\,\texttt{"2"}、\texttt{"true+false"}\,は\,\texttt{"1"}、\texttt{"false+false"}\,は\,\texttt{"0"}。これは\texttt{true}が\texttt{1}で\texttt{false}が\texttt{0}ならば納得のいく結果だ。大小比較の結果としても矛盾していない。 + +すでに見たように、\texttt{std::boolalpha}を出力していない状態で\texttt{bool}を出力すると\texttt{true}が\texttt{1}、\texttt{false}が\texttt{0}となる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << true << false ; +} +\end{lstlisting} + +結果、 + +\begin{lstlisting}[style=terminal] +10 +\end{lstlisting} + +これは\texttt{bool型}と\texttt{整数型}が変換されているのだ。 + +異なる型の値が変換されるというのは、すでに例がある。\texttt{整数型}と\texttt{浮動小数点数型}だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 3 + int i = 3.14 ; + std::cout << i << "\n"s ; + +(@\ifTombow\pagebreak\fi@) + // 123.0 + double d = 123 ; + std::cout << d << "\n"s ; +} +\end{lstlisting} + +\texttt{浮動小数点数型}は\texttt{整数型}に変換できる。その際に小数部は切り捨てられる。\texttt{整数型}は\texttt{浮動小数点数型}に変換できる。小数部はない。 + +これと同じように、\texttt{bool型}も\texttt{整数型}と変換ができる。 + +\texttt{bool}型の\texttt{true}を\texttt{整数型}に変換すると\texttt{1}になる。\texttt{false}は\texttt{0}になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 1 + int True = true ; + // 0 + int False = false ; +} +\end{lstlisting} + +同様に、\texttt{整数型}のゼロを\texttt{bool型}に変換すると\texttt{false}になる。非ゼロは\texttt{true}になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // false + bool Zero = 0 ; + + // すべてtrue + bool One = 1 ; + bool minus_one = -1 ; + bool OneTwoThree = 123 ; +} +\end{lstlisting} + +したがって、\texttt{"if (0)"}\,は\,\texttt{"if (false)"}\,と等しく、\texttt{"if (1)"}\,や\,\texttt{"if(-1)"}\,など非ゼロな値は{\break}\,\texttt{"if (true)"}\,と等しい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 出力されない + if ( 0 ) + std::cout << "No output.\n"s ; + + // 出力される + if ( 1 ) + std::cout << "Output.\n"s ; +} +\end{lstlisting} + +大小比較は単に\texttt{bool}を整数に変換した結果を比較しているだけだ。\texttt{"true < false"}\,は\,\texttt{"1 < 0"}\,と書くのと同じだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << std::boolalpha ; + + // 1 < 0 + std::cout << (true < false) ; +} +\end{lstlisting} + +同様に四則演算も\texttt{bool}型を整数型に変換した上で計算をしているだけだ。\texttt{"true + true"}\,は{\break}\,\texttt{"1 + 1"}\,と書くのと同じだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 1 + 1 + std::cout << (true + true) ; +} +\end{lstlisting} + +C++では、\texttt{bool型}と\texttt{整数型}の変換は暗黙に行われてしまうので注意が必要だ。 diff --git a/TeX/006-debug-compile-warning.tex b/TeX/006-debug-compile-warning.tex new file mode 100644 index 0000000..de04d5b --- /dev/null +++ b/TeX/006-debug-compile-warning.tex @@ -0,0 +1,138 @@ +\hyperchapter{ch06}{デバッグ:\\コンパイル警告メッセージ}{デバッグ:コンパイル警告メッセージ} + +やれやれ、条件分岐は難しかった。この辺でもう一度ひと休みして、息抜きとしてデバッグ\index{でばつぐ@デバッグ}の話をしよう。今回はコンパイラーの警告メッセージ(warning messages)\index{けいこくめつせじ@警告メッセージ}についてだ。 + +コンパイラーはソースコードに文法エラーや意味エラーがあると、エラーメッセージを出すことはすでに学んだ。 + +コンパイラーがエラーメッセージを出さなかったとき、コンパイラーはソースコードには文法エラーや意味エラーを発見できず、コンパイラーは意味のあるプログラムを生成することができたということを意味する。しかし、コンパイルが通って実行可能なプログラムが生成できたからといって、プログラムにバグがないことは保証できない。 + +たとえば、変数\texttt{x}と\texttt{y}を足して出力するプログラムを考える。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto x = 1 ; + auto y = 2 ; + + std::cout << x + x ; +} +\end{lstlisting} + +このプログラムにはバグがある。プログラムの仕様は変数\texttt{x}と\texttt{y}を足すはずだったが変数\texttt{x}と\texttt{x}を足してしまっている。 + +コンパイラーはこのソースコードをコンパイルエラーにはしない。なぜならば上のコードは文法的に正しく、意味的にも正しいコードだからだ。 + +警告メッセージはこのような疑わしいコードについて、エラーとまではいかないまでも、文字どおり警告を出す機能だ。例えば上のコードをGCCでコンパイルすると以下のような警告メッセージを出す。 + +\begin{lstlisting}[style=terminal] +$ make +g++ -std=c++17 -Wall --pedantic-error -include all.h main.cpp -o program +main.cpp: In function ‘int main()’: +main.cpp:5:10: warning: unused variable ‘y’ [-Wunused-variable] + auto y = 2 ; + ^ +\end{lstlisting} + +すでに説明したように、GCCのメッセージは +\begin{lstlisting}[style=grammar] +ソースファイル名:行番号:列番号:メッセージの種類:メッセージの内容 +\end{lstlisting} +というフォーマットを取る。 + +このメッセージのフォーマットに照らし合わせると、このメッセージはソースファイル\texttt{main.cpp}の5行目の10列目について何かを警告している。警告はメッセージの種類として\texttt{warning}が使われる。 + +警告メッセージの内容は、「未使用の変数\,\texttt{'y'}\,\texttt{[-Wunused-variable]}」だ。コード中で\,\texttt{'y'}\,という名前の変数を宣言しているにもかかわらず、使っている場所がない。使わない変数を宣言するのはバグの可能性が高いので警告しているのだ。 + +\texttt{[-Wunused-variable]}というのはGCCに与えるこの警告を有効にするためのオプション名だ。GCCに\,\texttt{-Wunused-variable}\index{GCC@GCC(GNU Compiler Collection)!-Wunused-variable@\texttt{-Wunused-variable}}というオプションを与えると、未使用の変数を警告するようになる。 + +\begin{lstlisting}[style=terminal] +$ g++ -Wunused-variable その他のオプション +\end{lstlisting} + +今回は\,\texttt{-Wall}というすべての警告を有効にするオプションを使っているので、このオプションを使う必要はない。 + +もう1つ例を出そう。以下のソースコードは変数\texttt{x}の値が\texttt{123}と等しいかどうかを調べるものだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // xの値は0 + auto x = 0 ; + + // xが123と等しいかどうか比較する + if ( x = 123 ) + std::cout << "x is 123.\n"s ; + else + std::cout << "x is NOT 123.\n"s ; +} +\end{lstlisting} + +これを実行すると、\texttt{"x is 123.{\textbackslash}n"}\,と出力される。しかし、変数\texttt{x}の値は\texttt{0}のはずだ。なぜか\texttt{0}と\texttt{123}は等しいと判断されてしまった。いったいどういうことだろう。 + +この謎は警告メッセージを読むと解ける。 + +\begin{lstlisting}[style=terminal] +g++ -std=c++17 -Wall --pedantic-error -include all.h main.cpp -o program +main.cpp: In function ‘int main()’: +main.cpp:5:12: warning: suggest parentheses around assignment used as truth value [-Wparentheses] + if ( x = 123 ) + ~~^~~~~ +\end{lstlisting} + +\texttt{main.cpp}の5行目の12列目、「真偽値として使われている代入は括弧で囲むべき」とある。これはいったいどういうことか。よく見てみると、演算子が同値比較に使う\,\texttt{==}\,ではなく、\texttt{=}\,だ。\texttt{=}\,は代入演算子だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto x = 0 ; + + // 代入 + // xの値は1 + x = 1 ; + + // 同値比較 + x == 1 ; +} +\end{lstlisting} + +実は\texttt{if文}の\texttt{条件}にはあらゆる\texttt{式}を書くことができる。代入というのは、実は\texttt{代入式}という式なので、\texttt{if文}の中にも書くことができる。その場合、式の結果の値は代入される変数の値になる。 + +そして思い出してほしいのは、整数型は\texttt{bool}型に変換されるということだ。\texttt{0}は\texttt{false}、非ゼロは\texttt{true}だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto x = 0 ; + // 1はtrue + bool b1 = x = 1 ; + if ( x = 1 ) ; + + // 0はfalse + bool b0 = x = 0 ; + if ( x = 0 ) ; +} +\end{lstlisting} + +つまり、\texttt{"if(x=1)"}\,というのは、\texttt{"if(1)"}\,と書くのと同じで、これは最終的に、\texttt{"if(true)"}\,と同じ意味になる。 + +警告メッセージの「括弧で囲むべき」というのは、括弧で囲んだ場合、この警告メッセージは出なくなるからだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto x = 0 ; + + if ( (x = 0) ) + std::cout << "x is 123.\n"s ; + else + std::cout << "x is NOT 123.\n"s ; +} +\end{lstlisting} + +このコードをコンパイルしても警告メッセージは出ない。 + +わざわざ括弧で囲むということは、ちゃんと代入を意図して使っていることがわかっていると意思表示したことになり、結果として警告メッセージはなくなる。 + +この警告メッセージ単体を有効にするオプションは\,\texttt{-Wparentheses}だ。 + +警告メッセージは万能ではない。ときにはまったく問題ないコードに対して警告メッセージが出たりする。これは仕方がないことだ。というのもコンパイラーはソースコード中に表現されていない、人間の脳内にある意図を読むことはできないからだ。ただし、警告メッセージにはひと通り目を通して、それが問題ない誤検知であるかどうかを確認することは重要だ。 diff --git a/TeX/007-standard-input.tex b/TeX/007-standard-input.tex new file mode 100644 index 0000000..80ced3c --- /dev/null +++ b/TeX/007-standard-input.tex @@ -0,0 +1,456 @@ +\hyperchapter{ch07}{最近体重が気になるあなたのための\\標準入力}{最近体重が気になるあなたのための標準入力} +\index{ひようじゆんにゆうりよく@標準入力} + +\hypersection{ch0701}{これまでのおさらい} + +ここまで学んできた範囲でも、かなりのプログラムが書けるようになってきた。試しにちょっとプログラムを書いてみよう。 + +最近肥満が気になる読者は、肥満度を把握するためにBMI(Body Mass Index)\index{BMI@BMI(Body Mass Index)}を計算して出力するプログラムを書くことにした。 + +BMIの計算は以下のとおり。 + +\[ +BMI = \frac{体重_{kg}}{身長^2_{m}} +\] + +本書をここまで読み進めた読者ならば、このようなプログラムは簡単に書けるだろう。計算は小数点以下の値を扱う必要があるために、変数は浮動小数点数型(\texttt{double})にする。掛け算は\texttt{operator *}\,で、割り算は\texttt{operator /}\,だ。出力には\texttt{std::cout}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 身長1.63m + double height = 1.63 ; + // 体重73kg + double mass = 73.0 ; + + // BMIの計算 + double bmi = mass / (height*height) ; + +(@\ifTombow\pagebreak\fi@) + // BMIの出力 + std::cout << "BMI="s << bmi << "\n"s ; +} +\end{lstlisting} + +結果は\,\texttt{"27.4756"}\,となった。これだけでは太っているのか痩せているのかよくわからない。調べてみると、BMIの数値と肥満との関係は以下の表のとおりになるそうだ。 + +\vskip 1.0zw +\begin{small} +\begin{longtable}[]{@{\,\,}ll@{\,\,}} +\hline%\toprule +\textsf{BMI} & \textsf{状態}\tabularnewline +\hline%\midrule +\endhead +18.5未満 & 痩せすぎ(Underweight)\tabularnewline +18.5以上、25未満 & 普通(Normal)\tabularnewline +25以上、30未満 & 太り気味(Overweight)\tabularnewline +30以上 & 肥満(Obese)\tabularnewline +\hline%\bottomrule +\end{longtable} +\end{small} + +ではさっそく、この表のようにBMIから肥満状態も出力してくれるように、プログラムを書き換えよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 身長1.63m + double height = 1.63 ; + // 体重73kg + double mass = 73.0 ; + + // BMIの計算 + double bmi = mass / (height*height) ; + + // BMIの出力 + std::cout << "BMI="s << bmi << "\n"s ; + + // 状態の判定をする関数 + auto status = []( double bmi ) + { + if ( bmi < 18.5 ) + return "Underweight.\n"s ; + else if ( bmi < 25.0 ) + return "Normal.\n"s ; + else if ( bmi < 30.0 ) + return "Overweight.\n"s ; + else + return "Obese."s ; + } ; + + // 状態の出力 + std::cout << status(bmi) ; +} +\end{lstlisting} + +ここまで問題なく読むことができただろうか。ここまでのコードはすべて、本書を始めから読めば理解できる機能しか使っていない。わからない場合、この先に進む前に本書をもう一度始めから読みなすべきだろう。 + +\hypersection{ch0702}{標準入力} +\index{ひようじゆんにゆうりよく@標準入力} + +上のプログラムには実用にする上で1つ問題がある。身長と体重の値を変えたい場合、ソースコードを書き換えてコンパイルしなければならないのだ。 + +例えば読者の身長が1.8mで体重が80kgの場合、以下のように書き換えなければならない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 身長1.63m + double height = 1.80 ; + // 体重73kg + double mass = 80.0 ; + + // BMIの計算 + double bmi = mass / (height*height) ; + + // BMIの出力 + std::cout << "BMI="s << bmi << "\n"s ; +} +\end{lstlisting} + +すると今度は身長が1.48mで体重が48kgの人がやってきて私のBMIも計測しろとうるさい。しかも昨日と今日で体重が変わったからどちらも計測したいと言い出す始末。 + +こういうとき、プログラムのコンパイル時ではなく、実行時に値を入力できたならば、いちいちプログラムをコンパイルし直す必要がなくなる。 + +入力には\texttt{std::cin}\index{cin@\texttt{cin}}を使う。\texttt{std::cout}は標準出力を扱うのに対し、\texttt{std::cin}は標準入力を扱う。\texttt{std::cout}が\texttt{operator {<}{<}}を使って値を出力したのに対し、\texttt{std::cin}は\texttt{operator {>}{>}}\,\index{{>}{>}@\texttt{{>}{>}}}を使って値を変数に入れる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 入力を受け取るための変数 + std::string x{} ; + // 変数に入力を受け取る + std::cin >> x ; + // 入力された値を出力 + std::cout << x ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +実行結果、 + +\begin{lstlisting}[style=terminal] +$ make run +hello +hello +\end{lstlisting} + +標準入力はデフォルトでは、プログラムを実行したユーザーがターミナルから入力する。上の実行結果の2行目は、ユーザーの入力だ。 + +\texttt{std::cin}は入力された文字列を変数に入れる。入力は空白文字や改行で区切られる。そのため、空白で区切られた文字列を渡すと、以下のようになる。 + +\begin{lstlisting}[style=terminal] +$ make run +hello world +hello +\end{lstlisting} + +入力は複数取ることができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::string x{} ; + std::string y{} ; + std::cin >> x >> y ; + std::cout << x << y ; +} +\end{lstlisting} + +実行結果、 + +\begin{lstlisting}[style=terminal] +$ make run +hello world +helloworld +\end{lstlisting} + +空白文字は文字列の区切り文字として認識されるので変数\texttt{x}, \texttt{y}には入らない。 + +\texttt{std::cin}では文字列のほかにも整数や浮動小数点数、\texttt{bool}を入力として得ることができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 整数 + int i{} ; + std::cin >> i ; + // 浮動小数点数 + double d{} ; + std::cin >> d ; +} +\end{lstlisting} + +実行結果、 + +\begin{lstlisting}[style=terminal] +$ make run +123 1.23 +\end{lstlisting} + +数値はデフォルトで10進数として扱われる。 + +\texttt{bool}の入力には注意が必要だ。普通に書くと、ゼロが\texttt{false}, 非ゼロが\texttt{true}として扱われる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + bool b{} ; + std::cin >> b ; + + std::cout << std::boolalpha << b << "\n"s ; +} +\end{lstlisting} + +実行結果、 + +\begin{lstlisting}[style=terminal] +$ make run +1 +true +$ make run +0 +false +$ make run +123 +true +$ make run +-1 +true +\end{lstlisting} + +\texttt{"true"}, \texttt{"false"}\,という文字列で\texttt{true}, \texttt{false}の入力をしたい場合、\texttt{std::cin}に\texttt{std::boolalpha}\index{boolalpha@\texttt{boolalpha}}を「入力」させる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // bool型 + bool b{} ; + std::cin >> std::boolalpha >> b ; + + std::cout << std::boolalpha << b ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +実行結果 + +\begin{lstlisting}[style=terminal] +$ make run +true +true +$ make run +false +false +\end{lstlisting} + +\texttt{std::boolalpha}を入出力するというのは、実際には何も入出力しないので奇妙に見えるが、そういう設計になっているので仕方がない。 + +では標準入力を学んだので、さっそくBMIを計算するプログラムを標準入力に対応させよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 身長の入力 + double height{} ; + std::cout << "height(m)>" ; + std::cin >> height ; + + // 体重の入力 + double mass{} ; + std::cout << "mass(kg)>" ; + std::cin >> mass ; + + double bmi = mass / (height*height) ; + + std::cout << "BMI=" << bmi << "\n"s ; +} +\end{lstlisting} + +上出来だ。 + +\hypersection{ch0703}{リダイレクト} +\index{りだいれくと@リダイレクト} + +標準入出力が扱えるようになれば、もう自分の好きなプログラムを書くことができる。プログラムというのはけっきょく、入力を得て、処理して、出力するだけのものだからだ。入力はテキストだったりグラフィックだったり何らかの特殊なデバイスだったりするが、基本は変わらない。 + +たとえば読者はまだC++でファイルを読み書きする方法を知らないが、標準入出力さえ使えれば、ファイルの読み書きはリダイレクトを使うだけでできるのだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << "hello" ; +} +\end{lstlisting} + +これは\,\texttt{"hello"}\,と標準出力するだけの簡単なプログラムだ。このプログラムをコンパイルしたプログラム名を\texttt{program}としよう。標準出力の出力先はデフォルトで、ユーザーのターミナルになる。 + +\begin{lstlisting}[style=terminal] +$ ./program +hello +\end{lstlisting} + +リダイレクトを使えば、この出力先をファイルにできる。リダイレクトを使うには\,\texttt{"プログラム > ファイル名"}\,とする。 +\index{>@\texttt{>}}\index{りだいれくと@リダイレクト!>@\texttt{>}} + +\begin{lstlisting}[style=terminal] +$ ./program > hello.txt +$ cat hello.txt +hello +\end{lstlisting} + +ファイルへの簡単な書き込みは、リダイレクトを使うことであとから簡単に実現可能だ。 + +リダイレクトはファイルの読み込みにも使える。例えば先ほどのBMIを計算するプログラムを用意しよう。 + +\begin{lstlisting}[language={C++}] +// bmi +int main() +{ + double height{ } ; + double mass { } ; + + std::cin >> height >> mass ; + + std::cout << mass / (height*height) ; +} +\end{lstlisting} + +このプログラム名を\texttt{bmi}として、通常どおり実行すると以下のようになる。 + +\begin{lstlisting}[style=terminal] +$ ./bmi +1.63 +73 +27.4756 +\end{lstlisting} + +このうち、\texttt{1.63}と\texttt{73}はユーザーによる入力だ。これを毎回手で入力するのではなく、ファイルから入力することができる。つまり以下のようなファイルを用意して、 + +\begin{lstlisting}[style=terminal] +1.63 +73 +\end{lstlisting} + +このファイルを例えば、\texttt{"bodymass.txt"}\,とする。手で入力する代わりに、このファイルを入力として使いたい。これにはリダイレクトとして\,\texttt{"プログラム名 < ファイル名"}\,とする。 +\index{<@\texttt{<}}\index{りだいれくと@リダイレクト!<@\texttt{<}} + +\begin{lstlisting}[style=terminal] +$ ./bmi < bodymass.txt +27.4756 +\end{lstlisting} + +リダイレクトの入出力を組み合わせることも可能だ。 + +\begin{lstlisting}[style=terminal] +$ cat bodymass.txt +1.63 +73 +$ ./bmi < bodymass.txt > index.txt +$ cat index.txt +27.4756 +\end{lstlisting} + +もちろん、このようなファイルの読み書きは簡易的なものだが、かなりの処理がこの程度のファイル操作でも行えるのだ。 + +\hypersection{ch0704}{パイプ} +\index{ぱいぷ@パイプ} + +プログラムが出力した結果をさらに入力にすることだってできる。 + +例えば、先ほどのプログラム\texttt{bmi}に入力するファイル\texttt{bodymass.txt}の身長の単位がメートルではなくセンチメートルだったとしよう。 + +\begin{lstlisting}[style=terminal] +163 +73 +\end{lstlisting} + +この場合、プログラム\texttt{bmi}を書き換えて対処することもできるが、プログラムに入力させる前にファイルを読み込み、書き換えて出力し、その出力を入力とすることもできる。 + +まず、身長の単位をセンチメートルからメートルに直すプログラムを書く。 + +\begin{lstlisting}[language={C++}] +// convert +int main() +{ + double height{} ; + double mass{} ; + + std::cin >> height >> mass ; + + // 身長をセンチメートルからメートルに直す + // 体重はそのままでよい + std::cout << height/100.0 << "\n"s << mass ; +} +\end{lstlisting} + +このプログラムを\texttt{convert}と名付け、さっそく使ってみよう。 + +\begin{lstlisting}[style=terminal] +$ ./convert +163 +73 +1.63 +73 +\end{lstlisting} + +身長の単位がセンチメートルからメートルに正しく直されている。 + +これをリダイレクトで使うとこうなる。 + +\begin{lstlisting}[style=terminal] +$ ./convert < bodymass.txt > fixed_bodymass.txt +$ ./bmi < fixed_bodymass.txt +27.4756 +\end{lstlisting} + +しかしこれではファイルが増えて面倒だ。この場合、パイプを使うとスッキリと書ける。 + +パイプはプログラムの標準出力をプログラムの標準入力とするの使い方は、\texttt{"プログラム名 | プログラム名"}\,だ。 +\index{\protect{|}@\texttt{|}}\index{ぱいぷ@パイプ!\protect{|}@\texttt{|}} + +\begin{lstlisting}[style=terminal] +$ ./convert < bodymass.txt | ./bmi +27.4756 +\end{lstlisting} + +ところで、すでに何度か説明なしで使っているが、POSIX規格を満たすOSには\texttt{cat}というプログラムが標準で入っている。\texttt{cat ファイル名}は指定したファイル名の内容を標準出力する。標準出力はパイプで標準入力にできる。 + +\begin{lstlisting}[style=terminal] +$ cat bodymass.txt | ./convert | ./bmi +27.4756 +\end{lstlisting} + +\hypersection{ch0705}{プログラムの組み合わせ} + +現代のプログラミングというのは、すでに存在するプログラムを組み合わせて作るものだ。もし、自分の必要とする処理がすでに実装されているのであれば、自分で書く必要はない。 + +例えば、読者はまだカレントディレクトリー下のファイルの一覧を列挙する方法を知らない。しかしPOSIX規格を満たすOSには\texttt{ls}というカレントディレクトリー下のファイルの一覧を列挙するプログラムが存在する。これを先ほどまでBMIの計算などの作業をしていたディレクトリー下で実行してみよう。 + +\begin{lstlisting}[style=terminal] +$ ls +all.h all.h.gch bmi bodymass.txt convert data main.cpp Makefile program +\end{lstlisting} + +ファイルの一覧が列挙される。そしてこれはプログラム\texttt{ls}による標準出力だ。標準出力ということは、リダイレクトしてファイルに書き込んだり、パイプで別のプログラムに渡したりできるということだ。 + +\begin{lstlisting}[style=terminal] +$ ls > files.txt +$ ls | ./program +\end{lstlisting} + +標準入出力が扱えれば、ネットワークごしにWebサイトをダウンロードすることもできる。これにはほとんどのGNU/LinuxベースのOSに入っている\texttt{curl}\index{curl@\texttt{curl}}というプログラムを使う。 + +\begin{lstlisting}[style=terminal] +$ curl https://example.com +\end{lstlisting} + +プログラム\texttt{curl}は指定されたURLからデータをダウンロードして、標準出力する。標準出力するということは、パイプによって標準入力にできるということだ。 + +\begin{lstlisting}[style=terminal] +$ curl https://example.com | ./program +\end{lstlisting} + +読者はC++でネットワークアクセスする方法を知らないが、すでにネットワークアクセスは可能になった。 + +ほかにも便利なプログラムはたくさんある。プログラミングの学び始めはできることが少なくて退屈になりがちだが、読者はもうファイルの読み書きやネットワークアクセスまでできるようになったのだから、退屈はしないはずだ。 diff --git a/TeX/008-loop.tex b/TeX/008-loop.tex new file mode 100644 index 0000000..2a1f054 --- /dev/null +++ b/TeX/008-loop.tex @@ -0,0 +1,1860 @@ +\hyperchapter{ch08}{ループ}{ループ} +\index{るぷ@ループ} + +さて、ここまでで変数や関数、標準入出力といったプログラミングの基礎的な概念を教えてきた。あと1つでプログラミングに必要な基礎的な概念はすべて説明し終わる。ループだ。 + +\hypersection{ch0801}{これまでのおさらい} + +C++では、プログラムは書いた順番に実行される。これを\texttt{逐次実行}\index{ちくじじつこう@逐次実行}という。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << 1 ; + std::cout << 2 ; + std::cout << 3 ; +} +\end{lstlisting} + +実行結果、 + +\begin{lstlisting}[style=terminal] +123 +\end{lstlisting} + +この実行結果が\,\texttt{"123"}\,以外の結果になることはない。C++ではプログラムは書かれた順番に実行されるからだ。 + +条件分岐\index{じようけんぶんき@条件分岐}は、プログラムの実行を条件付きで行うことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << 1 ; + + if ( false ) + std::cout << 2 ; + + std::cout << 3 ; + + if ( true ) + std::cout << 4 ; + else + std::cout << 5 ; +} +\end{lstlisting} + +実行結果、 + +\begin{lstlisting}[style=terminal] +134 +\end{lstlisting} + +条件分岐によって、プログラムの一部を実行しないということが可能になる。 + +\hypersection{ch0802}{goto文} +\index{goto@\texttt{goto}文}\index{るぷ@ループ!goto@\texttt{goto}文} + +ここでは繰り返し(ループ)の基礎的な仕組みを理解するために、最も原始的で最も使いづらい繰り返しの機能である\texttt{goto文}を学ぶ。\texttt{goto文}で実用的な繰り返し処理をするのは面倒だが、恐れることはない。より簡単な方法もすぐに説明するからだ。なぜ本書で\texttt{goto文}を先に教えるかというと、あらゆる繰り返しは、けっきょくのところ\texttt{if文}と\texttt{goto文}へのシンタックスシュガーにすぎないからだ。\texttt{goto文}を学ぶことにより、繰り返しを恐れることなく使う本物のプログラマーになれる。 + +\hypersubsection{ch080201}{無限ループ} +\index{むげんるぷ@無限ループ}\index{るぷ@ループ!むげん@無限〜} + +\texttt{"hello{\textbackslash}n"}\,と3回出力するプログラムはどうやって書くのだろうか。\texttt{"hello{\textbackslash}n"}\,を1回出力するプログラムの書き方はすでにわかっているので、同じ文を3回書けばよい。 + +\begin{lstlisting}[language={C++}] +// 1回"hello\n"を出力する関数 +void hello() +{ + std::cout << "hello\n"s ; +} + +int main() +{ + hello() ; + hello() ; + hello() ; +} +\end{lstlisting} + +10回出力する場合はどうするのだろう。10回書けばよい。コードは省略する。 + +では100回出力する場合はどうするのだろう。100回書くのだろうか。100回も同じコードを書くのはとても面倒だ。読者がVimのような優秀なテキストエディターを使っていない限り100回も同じコードを間違えずに書くことは不可能だろう。Vimならば1回書いたあとにノーマルモードで\,\texttt{"100."}\,するだけで100回書ける。 + +実際のところ、100回だろうが、1000回だろうが、あらかじめ回数がコンパイル時に決まっているのであれば、その回数だけ同じ処理を書くことで実現可能だ。 + +しかし、プログラムを外部から強制的に停止させるまで、無限に出力し続けるプログラムはどう書けばいいのだろうか。そういった停止しないプログラムを外部から強制的に停止させるには\texttt{Ctrl-C}を使う。 + +以下はそのようなプログラムの実行例だ。 + +\begin{lstlisting}[style=terminal] +$ make run +hello +hello +hello +hello +... +[Ctrl-Cを押す] +\end{lstlisting} + +\texttt{goto文}は指定したラベルに実行を移す機能だ。 + +\begin{lstlisting}[style=grammar] +ラベル名 : 文 + +goto ラベル名 ; +\end{lstlisting} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << 1 ; + + // ラベルskipまで飛ぶ + goto skip ; + + std::cout << 2 ; + +// ラベルskip +skip : + std::cout << 3 ; +} +\end{lstlisting} + +これを実行すると以下のようになる。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[style=terminal] +13 +\end{lstlisting} + +\texttt{2}を出力すべき文の実行が飛ばされていることがわかる。 + +これだけだと\,\texttt{"if (false)"}\,と同じように見えるが、\texttt{goto文}はソースコードの上に飛ぶこともできるのだ。 + +\begin{lstlisting}[language={C++}] +void hello() +{ + std::cout << "hello\n"s ; +} +int main() +{ +loop : + hello() ; + goto loop ; +} +\end{lstlisting} + +これは\,\texttt{"hello{\textbackslash}n"}\,を無限に出力するプログラムだ。 + +このプログラムを実行すると、 +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 関数\texttt{hello}が呼ばれる +\item + \texttt{goto}文でラベル\texttt{loop}まで飛ぶ +\item + 1.に戻る +\end{enumerate} +という処理を行う。 + +\hypersubsection{ch080202}{終了条件付きループ} +\index{しゆうりようじようけんつきるぷ@終了条件付きループ}\index{るぷ@ループ!しゆうりようじようけんつき@終了条件付き〜} + +ひたすら同じ文字列を出力し続けるだけのプログラムというのも味気ない。もっと面白くてためになるプログラムを作ろう。例えば、ユーザーから入力された数値を合計し続けるプログラムはどうだろう。 + +いまから作るプログラムを実行すると以下のようになる。 + +\begin{lstlisting}[style=terminal] +$ make run +> 10 +10 +> 5 +15 +> 999 +1014 +> -234 +780 +\end{lstlisting} + +このプログラムは、 +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + \texttt{">"}\,と表示してユーザーから整数値を入力 +\item + これまでの入力との合計値を出力 +\item + 1.に戻る +\end{enumerate} +という動作を繰り返す。先ほど学んだ無限ループと同じだ。 + +さっそく作っていこう。 + +\begin{lstlisting}[language={C++}] +int input() +{ + std::cout << ">"s ; + int x {} ; + std::cin >> x ; + return x ; +} + +int main() +{ + int sum = 0 ; +loop : + sum = sum + input() ; + std::cout << sum << "\n"s ; + goto loop ; +} +\end{lstlisting} + +関数\texttt{input}は\,\texttt{">"}\,を表示してユーザーからの入力を得て戻り値として返すだけの関数だ。 + +\texttt{"sum = sum + input()"}\,は、変数\texttt{sum}に新しい値を代入するもので、その代入する値というのは、代入する前の変数\texttt{sum}の値と関数\texttt{input}の戻り値を足した値だ。 + +このような変数\texttt{x}に何らかの値\texttt{n}を足した結果を元の変数\texttt{x}に代入するという処理はとても多く使われるので、C++では\,\texttt{"x = x + n"}\,を意味する省略記法\,\texttt{"x += n"}\,がある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 1 ; + int n = 5 ; + + x = x + n ; // 6 + x += n ; // 11 +} +\end{lstlisting} + +さて、本題に戻ろう。上のプログラムは動く。しかし、プログラムを停止するには\texttt{Ctrl-C}を押すしかない。できればプログラム自ら終了してもらいたいものだ。 + +そこで、ユーザーが\texttt{0}を入力したときはプログラムを終了するようにしよう。 + +\begin{lstlisting}[language={C++}] +int input() +{ + std::cout << ">"s ; + int x {} ; + std::cin >> x ; + return x ; +} + +int main() +{ + int sum = 0 ; +loop : + // 一度入力を変数に代入 + int x = input() ; + // 変数xが0でない場合 + if ( x != 0 ) + {// 実行 + sum = sum + x ; + std::cout << sum << "\n"s ; + goto loop ; + } + // x == 0の場合、 ここに実行が移る + // main関数の最後なのでプログラムが終了 +} +\end{lstlisting} + +うまくいった。このループは、ユーザーが\texttt{0}を入力した場合に繰り返しを終了する、条件付きのループだ。 + +\hypersubsection{ch080203}{インデックスループ} +\index{いんでつくするぷ@インデックスループ}\index{るぷ@ループ!いんでつくす@インデックス〜} + +最後に紹介するループは、インデックスループだ。\(n\)回\,\texttt{"hello{\textbackslash}n"s}\,を出力するプログラムを書こう。問題は、この\(n\)はコンパイル時には与えられず、実行時にユーザーからの入力で与えられる。 + +\begin{lstlisting}[language={C++}] +// n回出力する関数の宣言 +void hello_n( int n ) ; + +int main() +{ + // ユーザーからの入力 + int n {} ; + std::cin >> n ; + // n回出力 + hello_n( n ) ; +} +\end{lstlisting} + +このコードをコンパイルしようとするとエラーになる。これは実はコンパイルエラーではなくてリンクエラーという種類のエラーだ。その理由は、関数\texttt{hello\_n}に対する関数の定義が存在しないからだ。 + +関数というのは宣言と定義に分かれている。\index{かんすう@関数}\index{かんすう@関数!せんげん@宣言}\index{かんすう@関数!ていぎ@定義} + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=grammar] +// 関数の宣言 +void f( ) ; + +// 宣言 +void f( ) +// 定義 +{ } +\end{lstlisting} + +関数の宣言というのは何度書いても大丈夫だ。 + +\begin{lstlisting}[style=grammar] +// 宣言 +int f( int x ) ; + +// 再宣言 +int f( int x ) ; + +// 再宣言 +int f( int x ) ; +\end{lstlisting} + +関数の宣言というのは戻り値の型や関数名や引数リストだけで、\texttt{";"}\,で終わる。 + +関数の定義とは、関数の宣言のあとの\,\texttt{"\{\}"}\,だ。この場合、宣言のあとに\,\texttt{";"}\,は書かない。 + +\begin{lstlisting}[style=grammar] +int f( int x ) { return x ; } +\end{lstlisting} + +関数の定義は一度しか書けない。 + +\begin{lstlisting}[style=grammar] +// 定義 +void f() {} +// エラー、 再定義 +void f() {} +\end{lstlisting} + +なぜ関数は宣言と定義とに分かれているかというと、C++では名前は宣言しないと使えないためだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // エラー + // 名前fは宣言されていない + f() ; +} + +// 定義 +void f() { } +\end{lstlisting} + +なので、必ず名前は使う前に宣言しなければならない。 + +\begin{lstlisting}[language={C++}] +// 名前fの宣言 +void f() ; + +int main() +{ + // OK、名前fは関数 + f() ; +} + +// 名前fの定義 +void f() { } +\end{lstlisting} + +さて、話を元に戻そう。これから学ぶのは\(n\)回\,\texttt{"hello{\textbackslash}n"s}\,と出力するプログラムの書き方だ。ただし\(n\)はユーザーが入力するので実行時にしかわからない。すでに我々はユーザーから\(n\)の入力を受け取る部分のプログラムは書いた。 + +\begin{lstlisting}[language={C++}] +// n回出力する関数の宣言 +void hello_n( int n ) ; + +int main() +{ + // ユーザーからの入力 + int n {} ; + std::cin >> n ; + // n回出力 + hello_n( n ) ; +} +\end{lstlisting} + +あとは関数\texttt{hello\_n(n)}が\(n\)回\,\texttt{"hello{\textbackslash}n"s}と出力するようなループを実行すればいいのだ。 + +すでに我々は無限回\,\texttt{"hello{\textbackslash}n"s}と出力する方法を知っている。まずは無限回ループを書こう。 + +\begin{lstlisting}[language={C++}] +void hello_n( int n ) +{ +loop : + std::cout << "hello\n"s ; + goto loop ; +} +\end{lstlisting} + +終了条件付きループで学んだように、このループを\(n\)回繰り返した場合に終了させるには、\texttt{if文}を使って、終了条件に達したかどうかで実行を分岐させればよい。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +void hello_n( int n ) +{ +loop : + // まだn回繰り返していない場合 + if ( ??? ) + { // 以下を実行 + std::cout << "hello\n"s ; + goto loop ; + } +} +\end{lstlisting} + +このコードを完成させるにはどうすればいいのか。まず、現在何回繰り返しを行ったのか記録する必要がある。このために変数を作る。 + +\begin{lstlisting}[language={C++}] +int i = 0 ; +\end{lstlisting} + +変数\texttt{i}の初期値は0だ。まだ繰り返し実行を1回も行っていないということは、つまり0回繰り返し実行をしたということだ。 + +1回繰り返し実行をするたびに、変数\texttt{i}の値を1増やす。 + +\begin{lstlisting}[language={C++}] +i = i + 1 ; +\end{lstlisting} + +これはすでに学んだように、もっと簡単に書ける。 + +\begin{lstlisting}[language={C++}] +i += 1 ; +\end{lstlisting} + +実は、さらに簡単に書くこともできる。変数の代入前の値に1を足した値を代入する、つまり変数の値を1増やすというのはとてもよく書くコードなので、とても簡単な演算子が用意されている。\texttt{operator ++}だ。 +\index{\protect{++}@\texttt{\protect{++}}} + +\begin{lstlisting}[language={C++}] +int main() +{ + int i = 0 ; + ++i ; // 1 + ++i ; // 2 + ++i ; // 3 +} +\end{lstlisting} + +これで変数\texttt{i}の値は1増える。これをインクリメント(increment)\index{いんくりめんと@インクリメント}という。 + +インクリメントと対になるのがデクリメント(decrement)\index{でくりめんと@デクリメント}だ。これは変数の値を1減らす。演算子は\texttt{operator {-}{-}}\,だ。 +\index{{-}{-}@\texttt{{-}{-}}} + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + int i = 0 ; + --i ; // -1 + --i ; // -2 + --i ; // -3 +} +\end{lstlisting} + +さて、必要な知識は学び終えたので本題に戻ろう。\(n\)回の繰り返しをしたあとにループを終了するには、まずいま何回繰り返し実行しているのかを記録する必要がある。その方法を学ぶために、0, 1, 2, 3, 4\ldots と無限に出力されるプログラムを書いてみよう。 + +このプログラムを実行すると以下のように表示される。 + +\begin{lstlisting}[style=terminal] +$ make run +1, 2, 3, 4, 5, 6, [Ctrl-C] +\end{lstlisting} + +\texttt{Ctrl-C}を押すまでプログラムは無限に実行される。 + +ではどうやって書くのか。以下のようにする。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 変数\texttt{i}を作り、値を\texttt{0}にする +\item + 変数\texttt{i}と\,\texttt{", "s}を出力する +\item + 変数\texttt{i}をインクリメントする +\item + \texttt{goto} 2. +\end{enumerate} + +この処理を素直に書くと以下のコードになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 1. 変数iを作り、値を0にする + int i = 0 ; +loop : + // 2. 変数iと", "sを出力する + std::cout << i << ", "s ; + // 3. 変数iをインクリメントする + ++i ; + // 4. goto 2 + goto loop ; +} +\end{lstlisting} + +どうやら、いま何回繰り返し実行しているか記録することはできるようになったようだ。 + +ここまでくればしめたもの。あとは\texttt{goto文}\index{goto@\texttt{goto}文}を実行するかどうかを\texttt{if文}\index{if@\texttt{if}文}で条件分岐すればよい。しかし、\texttt{if文}の中にどんな条件を書けばいいのだろうか。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +void hello_n( int n ) +{ + int i = 0 ; +loop : + // まだn回繰り返し実行をしていなければ実行 + if ( ??? ) + { + std::cout << "hello\n"s ; + ++i ; + goto loop ; + } +} +\end{lstlisting} + +具体的に考えてみよう。\texttt{n == 3}のとき、つまり3回繰り返すときを考えよう。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 1回目の\texttt{if}文実行のとき、\texttt{i == 0} +\item + 2回目の\texttt{if}文実行のとき、\texttt{i == 1} +\item + 3回目の\texttt{if}文実行のとき、\texttt{i == 2} +\item + 4回目の\texttt{if}文実行のとき、\texttt{i == 3} +\end{enumerate} + +ここでは\texttt{n == 3}なので、3回まで実行してほしい。つまり3回目までは\texttt{true}になり、4回目の\texttt{if}文実行のときには\texttt{false}になるような式を書く。そのような式とは、ズバリ\,\texttt{"i != n"}\,だ。 + +\begin{lstlisting}[language={C++}] +void hello_n( int n ) +{ + int i = 0 ; +loop : + if ( i != n ) + { + std::cout << "hello\n"s ; + ++i ; + goto loop ; + } +} +\end{lstlisting} + +さっそく実行してみよう。 + +\begin{lstlisting}[style=terminal] +$ make run +3 +hello +hello +hello +(@\ifTombow\pagebreak\fi@) +$ make run +2 +hello +hello +\end{lstlisting} + +なるほど、動くようだ。しかしこのプログラムにはバグがある。\texttt{-1}を入力すると、なぜか大量の\texttt{hello}が出力されてしまうのだ。 + +\begin{lstlisting}[style=terminal] +$ make run +-1 +hello +hello +hello +hello +[Ctrl-C] +\end{lstlisting} + +この原因はまだ現時点の読者には難しい。この謎はいずれ明らかにするとして、いまは\texttt{n}が負数の場合にプログラムを0回の繰り返し分の実行で終了するように書き換えよう。 + +\begin{lstlisting}[language={C++}] +void hello_n( int n ) +{ + // nが負数ならば + if ( n < 0 ) + // 関数の実行を終了 + return ; + + int i = 0 ; +loop : + if ( i != n ) + { + std::cout << "hello\n"s ; + ++i ; + goto loop ; + } +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +\hypersection{ch0803}{while文} +\index{while@\texttt{while}文}\index{るぷ@ループ!while@\texttt{while}文} + +\texttt{goto文}は極めて原始的で使いづらい機能だ。現実のC++プログラムでは\texttt{goto文}はめったに使われない。もっと簡単な機能を使う。ではなぜ\texttt{goto文}が存在するかというと、\texttt{goto文}は最も原始的で基礎的で、ほかの繰り返し機能は\texttt{if文}と\texttt{goto文}に変換することで実現できるからだ。 + +\texttt{goto文}より簡単な繰り返し文に、\texttt{while文}がある。ここでは\texttt{goto文}と\texttt{while文}を比較することで、\texttt{while文}を学んでいこう。 + +\hypersubsection{ch080301}{無限ループ} +\index{むげんるぷ@無限ループ}\index{るぷ@ループ!むげん@無限〜} + +無限ループを\texttt{goto文}で書く方法を思い出してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto hello = []() + { std::cout << "hello\n"s ; } ; + +loop : + // 繰り返し実行される文 + hello() ; + goto loop ; +} +\end{lstlisting} + +このコードで本当に重要なのは関数\texttt{hello}を呼び出している部分だ。ここが繰り返し実行される文で、\texttt{ラベル文}と\texttt{goto文}は、繰り返し実行を実現するために必要な記述でしかない。 + +そこで\texttt{while(true)}\index{while(true)@\texttt{while(true)}}だ。\texttt{while(true)}は\texttt{goto文}と\texttt{ラベル文}よりも簡単に無限ループを実現できる。 + +\begin{lstlisting}[style=grammar] +while (true) 文 +\end{lstlisting} + +\texttt{while文}は文を無限に繰り返して実行してくれる。試してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto hello = []() + { std::cout << "hello\n"s ; } ; + + while (true) + hello() ; +} +\end{lstlisting} + +このコードの重要な部分は以下の2行。 + +\begin{lstlisting}[language={C++}] +while (true) + hello() ; +\end{lstlisting} + +これを\texttt{goto文}と\texttt{ラベル文}を使った無限ループと比べてみよう。 + +\begin{lstlisting}[language={C++}] +loop: + hello() ; + goto loop ; +\end{lstlisting} + +どちらも同じ意味のコードだが、\texttt{while文}の方が明らかに書きやすくなっているのがわかる。 + +\texttt{goto文}で学んだ、ユーザーからの整数値の入力の合計の計算を繰り返すプログラムを\texttt{while(true)}で書いてみよう。 + +\begin{lstlisting}[language={C++}] +int input() +{ + std::cout << ">"s ; + int x {} ; + std::cin >> x ; + return x ; +} + +int main() +{ + int sum = 0 ; + + while( true ) + { + sum += input() ; + std::cout << sum << "\n"s ; + } +} +\end{lstlisting} + +重要なのは以下の5行だ。 + +\begin{lstlisting}[language={C++}] +while( true ) +{ + sum += input() ; + std::cout << sum << "\n"s ; +} +\end{lstlisting} + +これを\texttt{goto文}で書いた場合と比べてみよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +loop : + sum += input() ; + std::cout << sum << "\n"s ; + goto loop ; +\end{lstlisting} + +本当に重要で本質的な、繰り返し実行をする部分の2行のコードはまったく変わっていない。それでいて\texttt{while(true)}の方が圧倒的に簡単に書ける。 + +\hypersubsection{ch080302}{終了条件付きループ} +\index{しゆうりようじようけんつきるぷ@終了条件付きループ}\index{るぷ@ループ!しゆうりようじようけんつき@終了条件付き〜} + +なるほど、無限ループを書くのに、\texttt{goto文}を使うより\texttt{while(true)}を使った方がいいことがわかった。ではほかのループの場合でも、\texttt{while文}の方が使いやすいだろうか。 + +本書を先頭から読んでいる優秀な読者は\texttt{while(true)}の\texttt{true}は\texttt{bool}型の値であることに気が付いているだろう。実は\texttt{while(E)}の括弧の中\texttt{E}は、\texttt{if(E)}と書くのとまったく同じ\texttt{条件}なのだ。\texttt{条件}が\texttt{true}であれば繰り返し実行される。\texttt{false}なら繰り返し実行されない。 + +\begin{lstlisting}[style=grammar] +while ( 条件 ) 文 +\end{lstlisting} + +\begin{lstlisting}[language={C++}] +int main() +{ + // 実行されない + while ( false ) + std::cout << "No"s ; + + // 実行されない + while ( 1 > 2 ) + std::cout << "No"s ; + + // 実行される + // 無限ループ + while ( 1 < 2 ) + std::cout << "Yes"s ; +} +\end{lstlisting} + +\texttt{while文}を使って、\texttt{0}が入力されたら終了する合計値計算プログラムを書いてみよう。 + +\begin{lstlisting}[language={C++}] +int input() +{ + std::cout << ">"s ; + int x {} ; + std::cin >> x ; + return x ; +} + +int main() +{ + int sum = 0 ; + int x {} ; + + while( ( x = input() ) != 0 ) + { + sum += x ; + std::cout << sum << "\n"s ; + } +} +\end{lstlisting} + +重要なのはこの5行。 + +\begin{lstlisting}[language={C++}] +while( ( x = input() ) != 0 ) +{ + sum += x ; + std::cout << sum << "\n"s ; +} +\end{lstlisting} + +ここではちょっと難しいコードが出てくる。\texttt{while}の中の\texttt{条件}が、\texttt{"( x = input() ) != 0"}\,になっている。これはどういうことか。 + +実は\texttt{条件}は\texttt{bool型}に変換さえできればどんな式でも書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x { } ; + + if ( (x = 1) == 1 ) + std::cout << "(x = 1) is 1.\n"s ; +} +\end{lstlisting} + +このコードでは、``\texttt{(x=1)}''と``\texttt{1}''が等しい``\texttt{==}''かどうかを判断している。``\texttt{(x=1)}''という式は変数\texttt{x}に\texttt{1}を代入する式だ。\texttt{代入式}の値は、代入された変数の値になる。この場合変数\texttt{x}の値だ。変数\texttt{x}には\texttt{1}が代入されているので、その値は1、つまり``\texttt{(x=1) == 1}''は``\texttt{1 == 1}''と書くのと同じ意味になる。この結果は\texttt{true}だ。 + +さて、このことを踏まえて、``\texttt{( x = input() ) != 0}''を考えてみよう。 + +``\texttt{( x = input() )}''は変数\texttt{x}に関数\texttt{input}を呼び出した結果を代入している。関数\texttt{input}はユーザーから入力を得て、その入力をそのまま返す。つまり変数\texttt{x}にはユーザーの入力した値が代入される。その結果が\texttt{0}と等しくない``\texttt{!=}''かどうかを判断している。つまり、ユーザーが\texttt{0}を入力した場合は\texttt{false}、非ゼロを入力した場合は\texttt{true}となる。 + +\texttt{while(条件)}は\texttt{条件}が\texttt{true}となる場合に繰り返し実行をする。結果として、ユーザーが\texttt{0}を入力するまで繰り返し実行をするコードになる。 + +\texttt{goto文}を使った終了条件付きループと比較してみよう。 + +\begin{lstlisting}[language={C++}] +loop: + if ( (x = input() ) != 0 ) + { + sum += x ; + std::cout << sum << "\n"s ; + goto loop ; + } +\end{lstlisting} + +\texttt{while文}の方が圧倒的に書きやすいことがわかる。 + +\hypersubsection{ch080303}{インデックスループ} +\index{いんでつくするぷ@インデックスループ}\index{るぷ@ループ!いんでつくす@インデックス〜} + +\(n\)回\,\texttt{"hello{\textbackslash}n"s}と出力するプログラムを\texttt{while文}で書いてみよう。ただし\(n\)はユーザーが入力するものとする。 + +まずは\texttt{goto文}でも使ったループ以外の処理をするコードから。 + +\begin{lstlisting}[language={C++}] +void hello_n( int n ) ; + +int main() +{ + int n {} ; + std::cin >> n ; + hello_n( n ) ; +} +\end{lstlisting} + +あとは関数\texttt{hello\_n(n)}がインデックスループを実装するだけだ。ただし\texttt{n}が負数ならば何も実行しないようにしよう。 + +\texttt{goto文}でインデックスループを書くときに学んだように、 +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + \texttt{n < 0}ならば関数を終了 +\item + 変数\texttt{i}を作り値を0にする +\item + \texttt{i != n}ならば繰り返し実行 +\item + 出力 +\item + \texttt{++i} +\item + \texttt{goto} 3. +\end{enumerate} +を\texttt{while文}で書くだけだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +void hello_n( int n ) +{ + // 1. n < 0ならば関数を終了 + if ( n < 0 ) + return ; + + // 2. 変数iを作り値を0にする + int i = 0 ; + + // 3. i != nならば繰り返し実行 + while( i != n ) + { // 4. 出力 + std::cout << "hello\n"s ; + // 5. ++i + ++i ; + } // 6. goto 3 +} +\end{lstlisting} + +重要な部分だけ抜き出すと以下のとおり。 + +\begin{lstlisting}[language={C++}] +while( i != n ) +{ + std::cout << "hello\n"s ; + ++i ; +} +\end{lstlisting} + +\texttt{goto文}を使ったインデックスループと比較してみよう。 + +\begin{lstlisting}[language={C++}] +loop : + if ( i != n ) + { + std::cout << "hello\n"s ; + ++i ; + goto loop ; + } +\end{lstlisting} + +読者の中にはあまり変わらないのではないかと思う人もいるかもしれない。しかし、次の問題を解くプログラムを書くと、\texttt{while文}がいかに楽に書けるかを実感するだろう。 + +\ifTombow\pagebreak\fi +\textsf{問題}:以下のような九九の表を出力するプログラムを書きなさい。 + +\begin{lstlisting}[style=terminal] +1 2 3 4 5 6 7 8 9 +2 4 6 8 10 12 14 16 18 +3 6 9 12 15 18 21 24 27 +4 8 12 16 20 24 28 32 36 +5 10 15 20 25 30 35 40 45 +6 12 18 24 30 36 42 48 54 +7 14 21 28 35 42 49 56 63 +8 16 24 32 40 48 56 64 72 +9 18 27 36 45 54 63 72 81 +\end{lstlisting} + +もちろん、このような文字列を愚直に出力しろという問題ではない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 違う! + std::cout << "1 2 3 4 5..."s ; +} +\end{lstlisting} + +逐次実行、条件分岐、ループまでを習得した誇りある本物のプログラマーである我々は、もちろん九九の表はループを書いて出力する。 + +まず出力すべき表を見ると、数値が左揃えになっていることに気が付くだろう。 + +\begin{lstlisting}[style=terminal] +4 8 12 +5 10 15 +\end{lstlisting} + +\texttt{8}は1文字、\texttt{10}は2文字にもかかわらず、\texttt{12}と\texttt{15}は同じ列目から始まっている。これは出力するスペース文字を調整することでも実現できるが、ここでは単にタブ文字を使っている。 + +タブ文字は\texttt{Makefile}を書くのにも使った文字で、C++の文字列中に直接書くこともできるが、エスケープ文字\,\texttt{{\textbackslash}t}を使ってもよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << "4\t8\t12\n5\t10\t15"s ; +} +\end{lstlisting} + +エスケープ文字\,\texttt{{\textbackslash}n}が改行文字に置き換わるように、エスケープ文字\,\texttt{{\textbackslash}t}\index{{\textbackslash}t@\texttt{{\textbackslash}t}}はタブ文字\index{たぶもじ@タブ文字}に置き換わる。 + +九九の表はどうやって出力すればよいだろうか。計算自体はC++では\,\texttt{"a*b"}\,でできる。上の表がどのように計算されているかを考えてみよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=terminal] +1*1 1*2 1*3 1*4 1*5 1*6 1*7 1*8 1*9 +2*1 2*2 2*3 2*4 2*5 2*6 2*7 2*8 2*9 +3*1 3*2 3*3 3*4 3*5 3*6 3*7 3*8 3*9 +4*1 4*2 4*3 4*4 4*5 4*6 4*7 4*8 4*9 +5*1 5*2 5*3 5*4 5*5 5*6 5*7 5*8 5*9 +6*1 6*2 6*3 6*4 6*5 6*6 6*7 6*8 6*9 +7*1 7*2 7*3 7*4 7*5 7*6 7*7 7*8 7*9 +8*1 8*2 8*3 8*4 8*5 8*6 8*7 8*8 8*9 +9*1 9*2 9*3 9*4 9*5 9*6 9*7 9*8 9*9 +\end{lstlisting} + +これを見ると、\texttt{"a*b"}\,のうちの\texttt{a}を\texttt{1}から\texttt{9}までインクリメントし、それに対して\texttt{b}を\texttt{1}から\texttt{9}までインクリメントさせればよい。つまり、9回のインデックスループの中で9回のインデックスループを実行することになる。ループの中のループだ。 + +\begin{lstlisting}[style=grammar] +while ( 条件 ) + while ( 条件 ) + 文 +\end{lstlisting} + +さっそくそのようなコードを書いてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 1から9まで + int a = 1 ; + while ( a <= 9 ) + { + // 1から9まで + int b = 1 ; + while ( b <= 9 ) + { + // 計算結果を出力 + std::cout << a * b << "\t"s ; + ++b ; + } + // 段の終わりに改行 + std::cout << "\n"s ; + ++a ; + } +} +\end{lstlisting} + +うまくいった。 + +ところで、このコードを\texttt{goto文}で書くとどうなるだろうか。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; +loop_outer : + if ( a <= 9 ) + { + int b = 1 ; +loop_inner : + if ( b <= 9 ) + { + std::cout << a * b << "\t"s ; + ++b ; + goto loop_inner ; + } + std::cout << "\n"s ; + ++a ; + goto loop_outer ; + } +} +\end{lstlisting} + +とてつもなく読みにくい。 + +\hypersection{ch0804}{for文} +\index{for@\texttt{for}文}\index{るぷ@ループ!for@\texttt{for}文} + +ところでいままで\texttt{while文}で書いてきたインデックスループには特徴がある。 + +試しに1から100までの整数を出力するコードを見てみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int i = 1 ; + while ( i <= 100 ) + { + std::cout << i << " "s ; + ++i ; + } +} +\end{lstlisting} + +このコードを読むと、以下のようなパターンがあることがわかる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // ループ実行前の変数の宣言と初期化 + int i = 1 ; +(@\ifTombow\pagebreak\fi@) + // ループ中の終了条件の確認 + while ( i <= 100 ) + { + // 実際に繰り返したい文 + std::cout << i << " "s ; + // 各ループの最後に必ず行う処理 + ++i ; + } +} +\end{lstlisting} + +ここで真に必要なのは、「実際に繰り返したい文」だ。その他の処理は、ループを実現するために必要なコードだ。ループの実現に必要な処理が飛び飛びの場所にあるのは、はなはだわかりにくい。 + +\texttt{for文}はそのような問題を解決するための機能だ。 + +\begin{lstlisting}[style=grammar] +for ( 変数の宣言 ; 終了条件の確認 ; 各ループの最後に必ず行う処理 ) 文 +\end{lstlisting} + +\texttt{for文}を使うと、上のコードは以下のように書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + for ( int i = 1 ; i <= 100 ; ++i ) + { + std::cout << i << " "s ; + } +} +\end{lstlisting} + +ループの実現に必要な部分だけ抜き出すと以下のようになる。 + +\begin{lstlisting}[language={C++}] +// for文の開始 +for ( +// 変数の宣言と初期化 +int i = 1 ; +// 終了条件の確認 +i <= 100 ; +// 各ループの最後に必ず行う処理 +++i ) +\end{lstlisting} + +\texttt{for文}はインデックスループによくあるパターンをわかりやすく書くための機能だ。例えば\texttt{while文}のときに書いた九九の表を出力するプログラムは、\texttt{for文}ならばこんなに簡潔に書ける。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + for ( int a = 1 ; a <= 9 ; ++a ) + { + for ( int b = 1 ; b <= 9 ; ++b ) + { std::cout << a*b << "\t"s ; } + + std::cout << "\n"s ; + } +} +\end{lstlisting} + +\texttt{while文}を使ったコードと比べてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; + while ( a <= 9 ) + { + int b = 1 ; + while ( b <= 9 ) + { + std::cout << a * b << "\t"s ; + ++b ; + } + std::cout << "\n"s ; + ++a ; + } +} +\end{lstlisting} + +格段に読みやすくなっていることがわかる。 + +C++ではカンマ\,\texttt{','}\,\index{,@\texttt{,}}を使うことで、複数の\texttt{式}を1つの\texttt{文}に書くことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 0, b = 0 ; + ++a, ++b ; +} +\end{lstlisting} + +\texttt{for文}でもカンマが使える。九九の表を出力するプログラムは、以下のように書くこともできる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + for ( int a = 1 ; a <= 9 ; ++a, std::cout << "\n"s ) + for ( int b = 1 ; b <= 9 ; ++b ) + std::cout << a*b << "\t"s ; +} +\end{lstlisting} + +変数もカンマで複数宣言できると知った読者は、以下のように書きたくなるだろう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + for ( int a = 1, b = 1 ; + a <= 9 ; + ++a, ++b, + std::cout << "\n"s + ) + std::cout << a*b << "\t"s ; +} +\end{lstlisting} + +これは動かない。なぜならば、\texttt{for文}を2つネストさせたループは、\(a \times b\)回のループで、変数\texttt{a}が\texttt{1}から\texttt{9}まで変化するそれぞれに対して、変数\texttt{b}が\texttt{1}から\texttt{9}まで変化する。しかし、上の\texttt{for文}1つのコードは、変数\texttt{a}, \texttt{b}ともに同時に\texttt{1}から\texttt{9}まで変化する。したがって、これは単に\texttt{a}回のループでしかない。\texttt{a}回のループの中で\texttt{b}回のループをすることで\(a \times b\)回のループを実現できる。 + +\texttt{for文}では使わない部分を省略することができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + bool b = true ; + // for文による変数宣言は使わない + for ( ; b ; b = false ) + std::cout << "hello"s ; +} +\end{lstlisting} + +\texttt{for文}で終了条件を省略した場合、\texttt{true}と同じになる。 +\index{for@\texttt{for}文!しようりやく@省略} + +\begin{lstlisting}[language={C++}] +int main() +{ + for (;;) + std::cout << "hello\n"s ; +} +\end{lstlisting} + +このプログラムは\,\texttt{"hello{\textbackslash}n"s}と無限に出力し続けるプログラムだ。\texttt{"for(;;)"}\,は\,\texttt{"for(;true;)"}\,と同じ意味であり、\texttt{"while(true)"}\,とも同じ意味だ。 + +\hypersection{ch0805}{do文} +\index{do@\texttt{do}文}\index{るぷ@ループ!do@\texttt{do}文} + +\texttt{do文}は\texttt{while文}に似ている。 + +\begin{lstlisting}[style=grammar] +do 文 while ( 条件 ) ; +\end{lstlisting} + +比較のために\texttt{while文}の文法も書いてみると以下のようになる。 + +\begin{lstlisting}[style=grammar] +while ( 条件 ) 文 +\end{lstlisting} + +\texttt{while文}はまず\texttt{条件}を確認し\texttt{true}の場合\texttt{文}を実行する。これを繰り返す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + while ( false ) + { + std::cout << "hello\n"s ; + } +} +\end{lstlisting} + +\texttt{do文}はまず\texttt{文}を実行する。しかる後に\texttt{条件}を確認し\texttt{true}の場合繰り返しを行う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + do { + std::cout << "hello\n"s ; + } while ( false ) ; +} +\end{lstlisting} + +違いがわかっただろうか。\texttt{do文}は繰り返し実行する\texttt{文}を、\texttt{条件}がなんであれ、最初に一度実行する。 + +\texttt{do文}を使うと条件にかかわらず文を1回は実行するコードが、文の重複なく書けるようになる。 + +\ifTombow\pagebreak\fi +\hypersection{ch0806}{break文} +\index{break@\texttt{break}文}\index{るぷ@ループ!break@\texttt{break}文} + +ループの実行の途中で、ループの中から外に脱出\index{るぷ@ループ!だつしゆつ@脱出}したくなった場合、どうすればいいのだろうか。例えばループを実行中に何らかのエラーを検出したので処理を中止したい場合などだ。 + +\begin{lstlisting}[language={C++}] +while ( true ) +{ + // 処理 + + if ( is_error() ) + // エラーのため脱出したくなった + + // 処理 +} +\end{lstlisting} + +\texttt{break文}はループの途中から脱出するための文だ。 + +\begin{lstlisting}[style=grammar] +break ; +\end{lstlisting} + +\texttt{break文}は\texttt{for文}、\texttt{while文}、\texttt{do文}の中でしか使えない。 + +\texttt{break文}は\texttt{for文}、\texttt{while文}、\texttt{do文}の外側に脱出する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + while ( true ) + { + // 処理 + + break ; + + // 処理 + } +} +\end{lstlisting} + +これは以下のようなコードと同じだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + while ( true ) + { + // 処理 + + goto break_while ; + + // 処理 + } +break_while : ; +} +\end{lstlisting} + +\texttt{break文}は最も内側の\texttt{繰り返し文}から脱出する + +\begin{lstlisting}[language={C++}] +int main() +{ + while ( true ) // 外側 + { + while ( true ) // 内側 + { + break ; + } + // ここに脱出 + } +} +\end{lstlisting} + +\hypersection{ch0807}{continue文} +\index{continue@\texttt{continue}文}\index{るぷ@ループ!continue@\texttt{continue}文} + +ループの途中で、いまのループを打ち切って\index{るぷ@ループ!うちきり@打ち切り}次のループに進みたい場合はどうすればいいのだろう。例えば、ループの途中でエラーを検出したので、そのループについては処理を打ち切りたい場合だ。 + +\begin{lstlisting}[language={C++}] +while ( true ) +{ + // 処理 + + if ( is_error() ) + // このループは打ち切りたい + + // 処理 +} +\end{lstlisting} + +\texttt{continue文}はループを打ち切って次のループに行くための文だ。 + +\begin{lstlisting}[style=grammar] +continue ; +\end{lstlisting} + +\texttt{continue文}は\texttt{for文}、\texttt{while文}、\texttt{do}文の中でしか使えない。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + while ( true ) + { + // 処理 + + continue ; + + // 処理 + } +} +\end{lstlisting} + +これは以下のようなコードと同じだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + while ( true ) + { + // 処理 + + goto continue_while ; + + // 処理 + +continue_while : ; + } +} +\end{lstlisting} + +\texttt{continue}文はループの最後に処理を移す。その結果、次のループを実行するかどうかの\texttt{条件}を評価することになる。 + +\texttt{continue文}は最も内側のループに対応する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + while ( true ) // 外側 + { + while ( true ) // 内側 + { + continue ; + // continueはここに実行を移す + } + } +} +\end{lstlisting} + +\hypersection{ch0808}{再帰関数} +\index{さいきかんすう@再帰関数}\index{るぷ@ループ!さいきかんすう@再帰関数} + +最後に関数でループを実装する方法を示してこの章を終わりにしよう。 + +関数は関数を呼び出すことができる。 + +\begin{lstlisting}[language={C++}] +void f() { } + +void g() +{ + f() ; // 関数fの呼び出し +} + +int main() +{ + g() ; // 関数gの呼び出し +} +\end{lstlisting} + +ではもし、関数が自分自身を呼び出したらどうなるだろうか。 + +\begin{lstlisting}[language={C++}] +void hello() +{ + std::cout << "hello\n" ; + hello() ; +} + +int main() +{ + hello() ; +} +\end{lstlisting} + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 関数\texttt{main}は関数\texttt{hello}を呼び出す +\item + 関数\texttt{hello}は\,\texttt{"hello{\textbackslash}n"}\,と出力して関数\texttt{hello}を呼び出す +\end{enumerate} + +関数\texttt{hello}は必ず関数\texttt{hello}を呼び出すので、この実行は無限ループする。 + +関数が自分自身を呼び出すことを、\texttt{再帰}(recursion)\index{さいき@再帰}という。 + +なるほど、再帰によって無限ループを実現できることはわかった。では終了条件付きループは書けるだろうか。 + +関数は\texttt{return文}\index{return@\texttt{return}文}によって呼び出し元に戻る。単に\,\texttt{'return ;'}\,と書けば再帰はしない。そして、\texttt{if文}によって実行は分岐できる。これを使えば再帰で終了条件付きループが実現できる。 + +試しに、ユーザーが\texttt{0}を入力するまでループし続けるプログラムを書いてみよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// ユーザーからの入力を返す +int input () +{ + int x { } ; + std::cin >> x ; + return x ; +} + +// 0の入力を終了条件としたループ +void loop_until_zero() +{ + if ( input() == 0 ) + return ; + else + loop_until_zero() ; +} + +int main() +{ + loop_until_zero() ; +} +\end{lstlisting} + +書けた。 + +ではインデックスループはどうだろうか。\texttt{1}から\texttt{10}までの整数を出力してみよう。 + +インデックスループを実現するには、書き換えられる変数が必要だ。関数は引数で値を渡すことができる。 + +\begin{lstlisting}[language={C++}] +void g( int x ) { } +void f( int x ) { g( x+1 ) ; } + +int main() { f( 0 ) ; } +\end{lstlisting} + +これを見ると、関数\texttt{main}は関数\texttt{f}に引数\texttt{0}を渡し、関数\texttt{f}は関数\texttt{g}に引数\texttt{1}を渡している。これをもっと再帰的に考えよう。 + +\begin{lstlisting}[language={C++}] +void until_ten( int x ) +{ + if ( x > 10 ) + return ; + else + { + std::cout << x << "\n" ; + return until_ten( x + 1 ) ; + } +} + +int main() +{ + until_ten(1) ; +} +\end{lstlisting} + +関数\texttt{main}は関数\texttt{until\_ten}に引数1を渡す。 + +関数\texttt{until\_ten}は引数が\texttt{10}より大きければ何もせず処理を戻し、そうでなければ引数を出力して再帰する。そのとき引数は\(+1\)される。 + +これによりインデックスループが実現できる。 + +関数は戻り値を返すことができる。再帰で戻り値を使うことにより面白い問題も解くことができる。 + +例えば、\texttt{1}と\texttt{0}だけを使った10進数\index{10しんすう@10進数}の整数を2進数\index{2しんすう@2進数}に変換するプログラムを書いてみよう。 + +\begin{lstlisting}[style=terminal] +$ make run +> 0 +0 +> 1 +1 +> 10 +2 +> 11 +3 +> 1010 +10 +> 1111 +15 +\end{lstlisting} + +まず10進数と2進数を確認しよう。数学的に言うと「10を底にする」とか「2を底にする」という言い方をする。 + +具体的な例を出すと10進数では1, 2, 3, 4, 5, 6, 7, 8, 9, 0の文字を使う。\texttt{1234}は以下のようになる。 +\[ +1234 = 1 \times 10^3 + 2 \times 10^2 + 3 \times 10^1 + 4 \times 10^0 = 1 \times 1000 + 2 \times 100 + 3 \times 10 + 4 \times 1 +\] + +10進数で\texttt{1010}は以下のようになる。 +\[ +1010 = 1 \times 10^3 + 0 \times 10^2 + 1 \times 10^1 + 0 \times 10^0 = 1 \times 1000 + 0 \times 100 + 1 \times 10 + 0 \times 1 +\] + +2進数では1, 0の文字を使う。\texttt{1010}は以下のようになる。 +\[ +1010 = 1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 1 \times 8 + 0 \times 4 + 1 \times 2 + 0 \times 1 +\] + +2進数の\texttt{1010}は10進数では\texttt{10}になる。 + +では問題を解いていこう。 + +問題を難しく考えるとかえって解けなくなる。ここではすでに10進数から2進数への変換は解決したものとして考えよう。関数\texttt{convert}によってその問題は解決した。 + +\begin{lstlisting}[language={C++}] +// 2進数への変換 +int convert( int n ) ; +\end{lstlisting} + +まだ我々は関数\texttt{convert}の中身を書いていないが、すでに書き終わったと仮定しよう。するとプログラムの残りの部分は以下のように書ける。 + +\begin{lstlisting}[language={C++}] +int convert( int n ) ; + +// 入力 +int input() +{ + std::cout << "> " ; + int x{} ; + std::cin >> x ; + return x ; +} + +// 出力 +void output( int binary ) +{ + std::cout << binary << "\n"s ; +} + +int main() +{ + // 入力、 変換、 出力のループ + while( true ) + { + auto decimal = input() ; + auto binary = convert( decimal ) ; + output( binary ) ; + } +} +\end{lstlisting} + +あとは関数\texttt{convert}を実装すればよいだけだ。 + +関数\texttt{convert}に引数を渡したときの結果を考えてみよう。\texttt{convert(1010)}は\texttt{10}を返し、\texttt{convert(1111)}は\texttt{15}を返す。 + +では\texttt{convert(-1010)}の結果はどうなるだろうか。これは\,\texttt{-10}になる。 + +負数と正数の違いを考えるのは面倒だ。ここでは正数を引数として与えると10進数から2進数へ変換した答えを返してくる魔法のような関数\texttt{solve}をすでに書き終えたと仮定しよう。我々はまだ関数\texttt{solve}を書いていないが、その問題は未来の自分に押し付けよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// 1,0のみを使った10進数から +// 2進数へ変換する関数 +int solve( int n ) ; +\end{lstlisting} + +すると、関数\texttt{convert}がやるのは負数と正数の処理だけでよい。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 引数が正数の場合はそのまま関数\texttt{solve}に渡して\texttt{return} +\item + 引数が負数の場合は絶対値を関数\texttt{solve}に渡して負数にして\texttt{return} +\end{enumerate} + +\begin{lstlisting}[language={C++}] +int convert( int n ) +{ + // 引数が正数の場合 + if ( n > 0 ) + // そのまま関数solveに渡してreturn + return solve( n ) ; + else // 引数が負数の場合 + // 絶対値を関数solveに渡して負数にしてreturn + return - solve( -n ) ; +} +\end{lstlisting} + +\texttt{n}が負数の場合の絶対値は\,\texttt{-n}で得られる。その場合、関数\texttt{solve}の答えは正数なので負数にする。 + +あとは関数\texttt{solve}を実装するだけだ。 + +今回、引数の整数を10進数で表現した場合に2, 3, 4, 5, 6, 7, 8, 9が使われている場合は考えないものとする。 + +\begin{lstlisting}[language={C++}] +// OK +solve(10111101) ; +// あり得ない +solve(2) ; +\end{lstlisting} + +再帰で問題を解くには再帰的な考え方が必要だ。再帰的な考え方では、問題の一部のみを解き、残りは自分自身に丸投げする。 + +まずとても簡単な1桁の変換を考えよう。 + +\begin{lstlisting}[language={C++}] +solve(0) ; // 0 +solve(1) ; // 1 +\end{lstlisting} + +引数が\texttt{0}か\texttt{1}の場合、単にその値を返すだけだ。関数\texttt{solve}には正数しか渡されないので、負数は考えなくてよい。すると、以下のように書ける。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int solve( int n ) +{ + if ( n <= 1 ) + return n ; + else + // その他の場合 +} +\end{lstlisting} + +その他の場合とは、桁数が多い場合だ。 + +\begin{lstlisting}[language={C++}] +solve(10) ; // 2 +solve(11) ; // 3 +solve(110) ; // 4 +solve(111) ; // 5 +\end{lstlisting} + +関数\texttt{solve}が解決するのは最下位桁だ。\texttt{110}の場合は\texttt{0}で、\texttt{111}の場合は\texttt{1}となる。最も右側の桁のみを扱う。数値から10進数で表記したときの最下位桁を取り出すには、10で割った余りが使える。覚えているだろうか。剰余演算子の\texttt{operator \%}を。 + +\begin{lstlisting}[language={C++}] +int solve( int n ) +{ + if ( n <= 1 ) + return n ; + else // 未完成 + return n%10 ; +} +\end{lstlisting} + +結果は以下のようになる。 + +\begin{lstlisting}[language={C++}] +solve(10) ; // 0 +solve(11) ; // 1 +solve(110) ; // 0 +solve(111) ; // 1 +\end{lstlisting} + +これで関数\texttt{solve}は最下位桁に完全に対応した。しかしそれ以外の桁はどうすればいいのだろう。 + +ここで再帰的な考え方が必要だ。関数\texttt{solve}はすでに最下位桁に完全に対応している。ならば次の桁を最下位桁とした数値で関数\texttt{solve}を再帰的に呼び出せばいいのではないか。 + +以下は\texttt{solve(n)}が再帰的に呼び出す関数だ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +solve(10) ; // solve(1) +solve(11) ; // solve(1) +solve(100) ; // solve(10)→solve(1) +solve(110) ; // solve(11)→solve(1) +solve(111) ; // solve(11)→solve(1) +\end{lstlisting} + +10進数表記された数値から最下位桁を取り除いた数値にするというのは、11を1に, 111を11にする処理だ。これは数値を10で割ればよい。 + +\begin{lstlisting}[language={C++}] +10 / 10 ; // 1 +11 / 10 ; // 1 +100 / 10 ; // 10 +110 / 10 ; // 11 +111 / 10 ; // 11 +\end{lstlisting} + +10進数表記は桁が1つ上がると10倍される。だから10で割れば最下位桁が消える。ところで、我々は計算しようとしているのは2進数だ。2進数では桁が1つ上がると2倍される。なので、再帰的に関数\texttt{solve}を呼び出して得られた結果は2倍しなければならない。そして足し合わせる。 + +\begin{lstlisting}[language={C++}] +int solve( int n ) +{ + // 1桁の場合 + if ( n <= 1 ) + return n ; // 単に返す + else // それ以外 + return + // 最下位桁の計算 + n%10 + // 残りの桁を丸投げする + // 次の桁なので2倍する + + 2 * solve( n/10 ) ; +} +\end{lstlisting} + +冗長なコメントを除いて短くすると以下のとおり。 + +\begin{lstlisting}[language={C++}] +int solve( int n ) +{ + if ( n <= 1 ) + return n ; + else + return n%10 + 2 * solve( n/10 ) ; +} +\end{lstlisting} + +再帰ではないループで関数\texttt{solve}を実装するとどうなるのだろうか。 + +引数の数値が何桁あっても対応できるよう、ループで1桁ずつ処理していくのは変わらない。 + +もう一度2進数の計算を見てみよう。 + +\[ +1010 = 1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 0 \times 2^0 = 1 \times 8 + 0 \times 4 + 1 \times 2 + 0 \times 1 +\] + +1桁目は0で、この値は\(0 \times 2^0\)、2桁目は1で、この値は\(1 \times 2^1\)になる。 + +一般に、\(i\)桁目の値は\(i桁目の数字 \times 2^{i-1}\)になる。 + +すると解き方としては、各桁の値を計算した和を返せばよい + +\begin{lstlisting}[language={C++}] +int solve( int n ) +{ + // 和 + int result = 0 ; + // i桁目の数字に乗ずる値 + int i = 1 ; + + // 桁がなくなれば終了 + while ( n != 0 ) + { + // 現在の桁を計算して足す + result += n%10 * i ; + // 次の桁に乗ずる値 + i *= 2 ; + // 桁を1つ減らす + n /= 10 ; + } + + return result ; +} +\end{lstlisting} + +再帰を使うコードは、再帰を理解できれば短く簡潔でわかりやすい。ただし、再帰を理解するためにはまず再帰を理解しなければならない。 + +再帰は万能ではない。そもそも関数とは、別の関数から呼ばれるものだ。関数\texttt{main}だけは特別で、関数\texttt{main}を呼び出すことはできない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + main() ; // エラー +} +\end{lstlisting} + +関数の実行が終了した場合、呼び出し元に処理が戻る。そのために関数は呼び出し元を覚えていなければならない。これには通常\texttt{スタック}\index{すたつく@スタック}と呼ばれるメモリーを消費する。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +void f() { } // gに戻る +void g() { f() ; } // mainに戻る +int main() { g() ; } +\end{lstlisting} + +関数の中の変数も通常\texttt{スタック}に確保される。これもメモリーを消費する。 + +\begin{lstlisting}[language={C++}] +void f() { } + +void g() +{ + int x {} ; + std::cin >> x ; + f() ; // 関数を呼び出す + // 関数を呼び出したあとに変数を使う + std::cout << x ; +} +\end{lstlisting} + +このコードでは、関数\texttt{g}が変数\texttt{x}を用意し、関数\texttt{f}を呼び出し、処理が戻ったら変数\texttt{x}を使っている。このコードが動くためには、変数\texttt{x}は関数\texttt{f}が実行されている間もスタックメモリーを消費し続けなければならない。 + +スタックメモリーは有限であるので、以下のような再帰による無限ループは、いつかスタックメモリーを消費し尽して実行が止まるはずだ。 + +\begin{lstlisting}[language={C++}] +void hello() +{ + std::cout << "hello\n" ; + hello() ; +} + +int main() { hello() ; } +\end{lstlisting} + +しかし、大半の読者の環境ではプログラムの実行が止まらないはずだ。これはコンパイラーの末尾再帰の最適化によるものだ。 + +末尾再帰\index{さいきまつび@末尾再帰}とは、関数のすべての条件分岐の末尾が再帰で終わっている再帰のことだ。 + +例えば以下は階乗を計算する再帰で書かれたループだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int factorial( int n ) +{ + if ( n < 1 ) + return 0 ; + else if ( n == 1 ) + return 1 ; + else + return n * factorial(n-1) ; +} +\end{lstlisting} + +\texttt{factorial(n)}は\(1 \times 2 \times 3 \times ... \times n\)を計算する。 + +この関数は、引数\texttt{n}が\texttt{1}未満であれば引数が間違っているので\texttt{0}を返す。そうでない場合で\texttt{n}が\texttt{1}であれば\texttt{1}を返す。それ以外の場合、\texttt{n * factorial(n-1)}を返す。 + +このコードは末尾再帰になっている。末尾再帰は非再帰のループに機械的に変換できる特徴を持っている。例えば以下のように、 +\begin{lstlisting}[language={C++}] +int factorial( int n ) +{ + int temp = n ; + +loop : + if ( n < 1 ) + return 0 ; + else if ( n == 1 ) + return temp * 1 ; + else + { + n = n-1 ; + temp *= n ; + goto loop ; + } +} +\end{lstlisting} +関数のすべての条件分岐の末尾が再帰になっているため、機械的に関数呼び出しを\texttt{goto}文で置き換えることができる。 + +ただし、プログラミング言語C++の標準規格は、C++の実装に末尾再帰の最適化を義務付けてはいない。そのため、末尾再帰が最適化されるかどうかはC++コンパイラー次第だ。 + +再帰は強力なループの実現方法で、再帰的な問題を解くのに最適だが、落とし穴もある。 diff --git a/TeX/009-vector.tex b/TeX/009-vector.tex new file mode 100644 index 0000000..8eaa20e --- /dev/null +++ b/TeX/009-vector.tex @@ -0,0 +1,583 @@ +\addtocontents{toc}{\protect\newpage} +\hyperchapter{ch09}{メモリーを無限に確保する}{メモリーを無限に確保する} + +\hypersection{ch0901}{これまでのまとめ} + +ここまで読み進めてきた読者は、逐次実行、条件分岐、ループに加えて、変数と関数を理解した。これだけの要素を習得したならば、本質的にはプログラミングはほぼできるようになったと言ってよい。ただし、まだできないことがある。動的なメモリー確保\index{めもりかくほ@メモリー確保}だ。 + +標準入力から\texttt{0}が入力されるまで任意個の整数値を受け取り、小さい値から順に出力するプログラムを実装しよう。以下はそのようなプログラムの実行例だ。 + +\begin{lstlisting}[style=terminal] +$ make run +100 +-100 +1 +6 +3 +999 +-5000 +0 +-5000 +-100 +1 +3 +6 +100 +999 +\end{lstlisting} + +\texttt{0}が入力されるまで、1番目に、2番目に小さい値はわからない。そのため、この問題の解決には、入力をすべて保持しておく必要がある。 + +ここで必要なのは、値をいくらでも保持しておく方法と、値に順番があり、\(i\)番目の値を間接的に指定して読み書きできる方法だ。その方法としてC++には標準ライブラリ\texttt{std::vector}がある。 + +\hypersection{ch0902}{vector} +\index{vector@\texttt{vector}} + +\texttt{std::vector}\,\index{vector@\texttt{vector}}は\texttt{T}型の値をいくらでも保持できる。\texttt{T}\index{T@\texttt{T}}\index{vector@\texttt{vector}!T@\texttt{T}}には保持する値の型を指定する。例えば\texttt{int}とか\texttt{double}とか\texttt{std::string}だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 整数型intの値を保持するvector + std::vector vi ; + + // 浮動小数点数型doubleの値を保持するvector + std::vector vd ; + + // 文字列型std::stringの値を保持するvector + std::vector vs ; +} +\end{lstlisting} + +\texttt{std::vector}\,というのはそれ自体が型になっている。そして\texttt{T}には型を指定する。ということは、\texttt{vector}型の値を保持する\texttt{vector}も書けるということだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 整数型intを保持するvectorを保持するvector + std::vector< std::vector< int > > vvi ; +} +\end{lstlisting} + +もちろん、上の\texttt{vector}を保持する\texttt{vector}も書ける。その場合、\texttt{std::vector{>}{>}}\,になる。この\texttt{vector}を保持する\texttt{vector}も当然書けるが省略する。 + +\texttt{std::vector}型の変数にはメンバー関数\texttt{push\_back}\index{push\_back@\texttt{push\_back}}\index{vector@\texttt{vector}!push\_back@\texttt{push\_back}}を使うことで値を保持できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + + v.push_back(1) ; + v.push_back(2) ; + v.push_back(3) ; +} +\end{lstlisting} + +\texttt{メンバー関数}(member function)\index{めんばかんすう@メンバー関数}というのは特別な関数で、詳細はまだ説明しない。ここで覚えておくべきこととしては、メンバー関数は一部の変数に使うことができること、メンバー関数\texttt{f}を変数\texttt{x}に使うには\,\texttt{'x.f(...)'}\,のように書くこと、を覚えておこう。 + +\texttt{std::vector}はメモリーの続く限りいくらでも値を保持できる。試しに1000個の整数を保持させてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + + for ( int i = 0 ; i != 1000 ; ++i ) + { + v.push_back( i ) ; + } +} +\end{lstlisting} + +このプログラムは\texttt{0}から\texttt{999}までの1000個の整数を\texttt{std::vector}に保持させている。 + +\texttt{std::vector}では保持する値のことを要素という。要素は順番を持っている。メンバー関数\texttt{push\_back}は最後の要素の次に要素を追加する。最初に要素はない。もしくは0個ある空の状態だと言ってもよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + + // vは空 + + // 要素数1、 中身は{1} + v.push_back(1) ; + // 要素数2、 中身は{1,2} + v.push_back(2) ; + // 要素数3、 中身は{1,2,3} + v.push_back(3) ; +} +\end{lstlisting} + +\texttt{std::vector}はメンバー関数\texttt{size()}\index{size@\texttt{size}}\index{vector@\texttt{vector}!size@\texttt{size}}で現在の要素数を取得できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + + // 0 + std::cout << v.size() ; + v.push_back(1) ; + // 1 + std::cout << v.size() ; + v.push_back(2) ; + // 2 + std::cout << v.size() ; +} +\end{lstlisting} + +せっかく値を入れたのだから取り出したいものだ。\texttt{std::vector}ではメンバー関数\texttt{at(i)}\index{at@\texttt{at}}\index{vector@\texttt{vector}!at@\texttt{at}}を使うことで、\texttt{i}番目の要素を取り出すことができる。この\texttt{i}のことを添字、インデックスと呼ぶ。ここで注意してほしいのは、最初の要素は0番目で、次の要素は1番目だということだ。最後の要素は\texttt{size()-1}番目になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + + std::vector v ; + + for ( int i = 0 ; i != 10 ; ++i ) + { + v.push_back(i) ; + } + + // vの中身は{0,1,2,3,4,5,6,7,8,9} + + // 0, 0番目の最初の要素 + std::cout << v.at(0) ; + // 4, 4番目の要素 + std::cout << v.at(4) ; + // 9, 9番目の最後の要素 + std::cout << v.at(9) ; +} +\end{lstlisting} + +この例ではループを使っている。読者はすでにループについては理解しているはずだ。上のコードが理解できないのであれば、もう一度ループの章に戻って学び直すべきだ。 + +もし\texttt{at(i)}に要素数を超える\texttt{i}を渡してしまった場合どうなるのだろうか。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v { } ; + v.push_back(0) ; + // vには0番目の要素しかない + // 1番目は誤り + std::cout << v.at(1) ; +} +\end{lstlisting} + +実行して確かめてみよう。 + +\begin{lstlisting}[style=terminal] +$ ./program +terminate called after throwing an instance of 'std::out_of_range' + what(): vector::_M_range_check: __n (which is 1) >= this->size() (which is 1) +Aborted (core dumped) +\end{lstlisting} + +なにやら恐ろしげなメッセージが表示されるではないか。しかし心配することはない。このメッセージはむしろうれしいメッセージだ。変数\texttt{v}に1番目の要素がないことを発見してくれたという実行時のエラーメッセージだ。すでに学んだように、エラーメッセージは恐れるものではない。エラーメッセージはうれしいものだ。エラーメッセージが出たらありがとう。エラーメッセージがあるおかげでバグの存在がわかる。 + +このメッセージの本当の意味はいずれ例外やデバッガーを解説する章で説明するとして、\texttt{vector}の要素数を超える指定をしてはいけないことを肝に銘じておこう。もちろん、\texttt{-1}もダメだ。 + +メンバー関数\texttt{at(i)}に与える引数\texttt{i}の型は整数型ではあるのだが\texttt{int}型ではない。\texttt{std::size\_t}型\index{size\_t@\texttt{size\_t}型}という特殊な型になる。メンバー関数\texttt{size}も同様に\texttt{std::size\_t}型を返す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + + // std::size_t型 + std::size_t size = v.size() ; + + v.push_back(0) ; + + // std::size_t型 + std::size_t index = 0 ; + v.at( index ) ; +} +\end{lstlisting} + +なぜ\texttt{int}型ではダメなのか。その謎は整数の章で明らかになる。ここでは\texttt{std::size\_t}型は負数が使えない整数型だということだけ覚えておこう。\texttt{std::size\_t}型に\,\texttt{-1}はない。\texttt{vector}の要素指定では負数は使えないので、負数が使えない変数を使うのは理にかなっている。 + +さて、これまでに学んだ知識だけを使って、\texttt{std::vector}のすべての要素を順番どおりに出力するコードが書けるはずだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + + std::vector v ; + + for ( int iota = 0 ; iota != 10 ; ++iota ) + { + v.push_back(iota) ; + } + + for ( std::size_t index = 0 ; index != v.size() ; ++index ) + { + std::cout << v.at(index) << " "s ; + } +} +\end{lstlisting} + +このコードが書けるということは、もう標準入力から\texttt{0}が入力されるまで任意個の値を受け取り、入力された順番で出力するプログラムも書けるということだ。 + +\begin{lstlisting}[language={C++}] +int input() +{ + int x{} ; + std::cin >> x ; + return x ; +} +int main() +{ + std::vector v ; + int x { } ; + + // 入力 + while ( ( x = input() ) != 0 ) + { + v.push_back( x ) ; + } + + // 出力 + for ( std::size_t index = 0 ; index != v.size() ; ++index ) + { + std::cout << v.at(index) << " "s ; + } +} +\end{lstlisting} + +入力された順番に出力できるということは、その逆順にも出力できるということだ。 + +\begin{lstlisting}[language={C++}] +for ( std::size_t index = v.size()-1 ; index != 0 ; --index ) +{ + std::cout << v.at(index) << " "s ; +} + +std::cout << v.at(0) ; +\end{lstlisting} + +最後に\,\texttt{'v.at(0)'}\,を出力しているのは、ループが\,\texttt{'i == 0'}\,のときに終了してしまうからだ。つまり最後に出力すべき\texttt{vector}最初の要素である\,\texttt{'v.at(0)'}\,が出力されない。 + +\texttt{std::size\_t}型は\,\texttt{-1}が使えないため、このようなコードになってしまう。\texttt{int}型を使えば負数は使えるのだが、\texttt{int}型と\texttt{std::size\_t}型の比較はさまざまな理由で問題がある。その理由は整数の章で深く学ぶことになるだろう。 + +ところで、問題は入力された整数を小さい順に出力することだった。この問題を考えるために、まず\texttt{vector}の中に入っている要素から最も小さい整数の場所を探すプログラムを考えよう。 + +問題を考えるにあたって、いちいち標準入力から入力を取るのも面倒なので、あらかじめ\texttt{vector}に要素\index{vector@\texttt{vector}!ようそ@要素}を入れておく方法を学ぶ。実は、\texttt{vector}の要素は以下のように書けば指定することができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 要素{1, 2, 3} + std::vector v = { 1,2,3 } ; + + // 1 + auto x = v.at(0) ; + // 2 + auto y = v.at(1) ; + // 3 + auto z = v.at(2) ; +} +\end{lstlisting} + +この例では、\texttt{1}, \texttt{2}, \texttt{3}の整数が書かれた順番であらかじめ\texttt{vector}の要素として入った状態になる。 + +さて、以下のような要素の\texttt{vector}から最も小さい整数を探すプログラムを考えよう。 + +\begin{lstlisting}[language={C++}] +std::vector v = { 8, 3, 7, 4, 2, 9, 3 } ; +\end{lstlisting} + +これを見ると、最も小さい整数は4番目(最初の要素は0番目なので4番目)にある\texttt{2}だ。ではどうやって探すのだろうか。 + +解決方法としては先頭から末尾まで要素を1つずつ比較して、最も小さい要素を見つけ出す。まず0番目の\texttt{8}が最も小さいと仮定する。現在わかっている中で最も小さい要素のインデックスを記録するために変数\texttt{min}を作っておこう。 + +\begin{lstlisting}[style=terminal] +min = 0 +8 3 7 4 2 9 3 +^ +\end{lstlisting} + +次に1番目の\texttt{3}と\texttt{min}番目を比較する。1番目の方が小さいので変数\texttt{min}に\texttt{1}を代入する。 + +\begin{lstlisting}[style=terminal] +min = 1 +8 3 7 4 2 9 3 + ^ +\end{lstlisting} + +2番目の\texttt{7}と\texttt{min}番目を比較するとまだ1番目の方が小さい。3番目の4と比較してもまだ\texttt{min}番目の方が小さい。 + +4番目の\texttt{2}と\texttt{min}番目を比較すると、4番目の方が小さい。変数\texttt{min}に\texttt{4}を代入しよう。 + +\begin{lstlisting}[style=terminal] +min = 4 +8 3 7 4 2 9 3 + ^ +\end{lstlisting} + +5番目と6番目も\texttt{min}番目より大きいので、これで変数\texttt{min}に代入された4番目の要素が最も小さいことがわかる。 + +\texttt{vector}の変数を\texttt{v}、要素数を\texttt{size}とする。変数\texttt{min}には現在わかっている中で最も小さい要素へのインデックスが代入される。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 変数\texttt{min}に0を代入する +\item + \texttt{size}回のループを実行する +\item + 変数\texttt{index}に\texttt{0}から\texttt{size-1}までの整数を代入する +\item + \texttt{'v.at(index) < v.at(min)'}\,ならば\texttt{min = index} +\end{enumerate} + +さっそく書いてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // vectorの変数をv + std::vector v = { 8, 3, 7, 4, 2, 9, 3 } ; + // 要素数をsizeとする + std::size_t size = v.size() ; + + // 変数minに0を代入する + std::size_t min = 0 ; + + // size回のループを実行する + // 変数iに0からsize-1までの整数を代入する + for ( std::size_t index = 1 ; index != size ; ++index ) + { + // 'v.at(index) < v.at(min)'ならばmin = index + if ( v.at(index) < v.at(min) ) + min = index ; + } + + // 一番小さい値を出力 + std::cout << v.at(min) ; +} +\end{lstlisting} + +うまくいった。 + +ところで、最終的に解きたい問題とは、\texttt{vector}のすべての要素を小さい順に出力するということだ。すると、もっと小さい要素を出力した次に、2番目に小さい要素、3番目に小さい要素{$\,\cdots\,$}と出力していく必要がある。 + +2番目に小さい要素を見つけるためには、1番目に小さい要素を探さなければよい。そこで、発見した最も小さい要素と先頭の要素を交換してしまい、先頭は無視して最も小さい要素を探すことを繰り返すと実現できる。 + +例えば以下のような要素があるとして、 +\begin{lstlisting}[style=terminal] +8 3 7 4 2 9 3 + ^ +\end{lstlisting} +最も小さい要素である4番目の\texttt{2}と0番目の\texttt{8}を交換する。 + +\begin{lstlisting}[style=terminal] +2 3 7 4 8 9 3 +^ ^ ++-------+ +\end{lstlisting} + +そして、0番目は無視して最も小さい要素を探す。 + +\begin{lstlisting}[style=terminal] +3 7 4 8 9 3 +^ +\end{lstlisting} + +この場合、最も小さいのは0番目と5番目の\texttt{3}だ。どちらも同じだが今回は0番目を選ぶ。もともと0番目にあるので0番目と0番目を交換した結果は変わらない。 + +そして、新しい0番目は無視して最も小さい要素を探す。 + +\begin{lstlisting}[style=terminal] +7 4 8 9 3 + ^ +\end{lstlisting} + +今度は4番目の\texttt{3}だ。これも先頭と交換する + +\begin{lstlisting}[style=terminal] +3 4 8 9 7 +^ ^ ++-------+ +\end{lstlisting} + +これを繰り返していけば、小さい順に要素を探していくことができる。 + +この処理を行うコードを考えるために、先ほどと似たようなコードを見てみよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = { 8, 3, 7, 4, 2, 9, 3 } ; + std::size_t size = v.size() ; + + // この部分を繰り返す? + { // これ全体が1つのブロック文 + std::size_t min = 0 ; + + for ( std::size_t index = 1 ; index != size ; ++index ) + { + if ( v.at(index) < v.at(min) ) + min = index ; + } + + // 出力 + std::cout << v.at(min) << " "s ; + + // 先頭と交換 + } +} +\end{lstlisting} + +このコードはそのまま使えない。今回考えた方法では、先頭が1つずつずれていく。そのために、最も小さい要素を探すループを、さらにループさせる。 + +\begin{lstlisting}[language={C++}] +// 現在の先頭 +for ( std::size_t head = 0 ; head != size ; ++head ) +{ + // 現在の先頭であるmin番目を仮の最小の要素とみなすのでhead + std::size_t min = head ; + // 現在の先頭の次の要素から探すのでhead + 1 + for ( std::size_t index = head + 1 ; index != size ; ++index ) + { + if ( v.at(index) < v.at(min) ) + min = index ; + } + + std::cout << v.at(min) << " "s ; + + // 先頭と交換 +} +\end{lstlisting} + +次に先頭(0番目)と現在見つけた最小の要素(\texttt{min}番目)を交換する方法を考えよう。 + +\texttt{vector}の\texttt{n}番目の要素の値を\texttt{x}に変更するには、単に\texttt{v.at(n) = x}と書けばよい。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3} ; + + v.at(0) = 4 ; + // vは{4,2,3} +} +\end{lstlisting} + +すると、\texttt{vector}の\texttt{i}番目の要素に\texttt{j}番目の要素値を入れるには\,\texttt{'v.at(i) = v.at(j)'}\,と書く。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3} ; + v.at(0) = v.at(2) ; + // vは{3,2,3} +} +\end{lstlisting} + +変数とまったく同じだ。 + +しかし、変数\texttt{a}に変数\texttt{b}の値を代入すると、変数\texttt{a}の元の値は消えてしまう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; + int b = 2 ; + + // aの元の値は上書きされる + a = b ; + // a == 2 + b = a ; + // b == 2 +} +\end{lstlisting} + +変数\texttt{a}, \texttt{b}の値を交換するためには、変数への代入の前に、別の変数に値を一時退避しておく必要がある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; + int b = 2 ; + + // 退避 + auto temp = a ; + + a = b ; + b = temp ; + + // a == 2 + // b == 1 +} +\end{lstlisting} + +さて、これで問題を解く準備はすべて整った。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = { 8, 3, 7, 4, 2, 9, 3 } ; + std::size_t size = v.size() ; + + // 先頭をずらすループ + for ( std::size_t head = 0 ; head != size ; ++head ) + { + std::size_t min = head ; + // 現在の要素の範囲から最小値を見つけるループ + for ( std::size_t index = head+1 ; index != size ; ++index ) + { + if ( v.at(index) < v.at(min) ) + min = index ; + } + // 出力 + std::cout << v.at(min) << " "s ; + + // 最小値を先頭と交換 + auto temp = v.at(head) ; + v.at(head) = v.at(min) ; + v.at(min) = temp ; + } + + // 実行したあと +} +\end{lstlisting} + +ところで、このプログラムの「実行したあと」地点での\texttt{vector}の中身はどうなっているだろうか。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = { 8,3,7,4,2,9,3 } ; + + // 上と同じコードなので省略 + + // 実行したあと + std::cout << "\n"s ; + +(@\ifTombow\pagebreak\fi@) + for ( std::size_t index = 0, size = v.size() ; index != size ; ++index ) + { + std::cout << v.at(index) << " "s ; + } +} +\end{lstlisting} + +これを実行すると以下のようになる。 + +\begin{lstlisting}[style=terminal] +$ make run +2 3 3 4 7 8 9 +2 3 3 4 7 8 9 +\end{lstlisting} + +なんと\texttt{vector}の要素も小さい順に並んでいる。この状態のことを、ソートされているという。ループの中で最も小さい値を出力していく代わりに、まずソートして先頭から値を出力してもよいということだ。 + +ソート\index{そと@ソート}にはさまざまな方法があるが、今回使ったのは選択ソート(selection sort)\index{せんたくそと@選択ソート}というアルゴリズムだ。 + +\texttt{vector}を使う方法には、イテレーターというもっと便利な方法があるが、それはイテレーターの章で説明する。 diff --git a/TeX/010-debug-printf.tex b/TeX/010-debug-printf.tex new file mode 100644 index 0000000..7699def --- /dev/null +++ b/TeX/010-debug-printf.tex @@ -0,0 +1,394 @@ +\hyperchapter{ch10}{デバッグ:\\printfデバッグ}{デバッグ:printfデバッグ} + +ループと多数の要素の集合を扱えるようになったので、読者はもう相当複雑な処理をするプログラムでも書けるようになった。処理が複雑になってくると増えるのがバグだ。 + +この章では、伝統ある\texttt{printfデバッグ}\index{printfでばつぐ@\texttt{printf}デバッグ}\index{でばつぐ@デバッグ!printf@\texttt{printf}}を紹介する。 + +\texttt{printfデバッグ}とは、プログラムの実行中に知りたい情報を出力することだ。\texttt{printf}とはC言語の伝統ある出力用のライブラリに由来する名前だが、本書では\texttt{iostream}\index{iostream@\texttt{iostream}}を使う。 + +\hypersection{ch1001}{実践例} + +例えば前章で実装したように\texttt{vector}の要素を選択ソートでソートしたいとする。 + +選択ソートとは、要素の集合の中から0番目に来るべき要素の場所を探し、0番目の要素と交換し、1番目に来るべき要素の場所を探し、1番目の要素と交換し{$\,\cdots\,$}を要素の数だけ繰り返すことによって要素全体をソートする方法だ。 + +以下のように書いたとする。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = { 3,8,2,5,6,9,4,1,7 } ; + auto size = v.size() ; + + for ( std::size_t head = 0 ; head != size ; ++head ) + { + auto min = head ; + for ( std::size_t index = head+1 ; index != size ; ++index ) + { + if ( v.at(index) < v.at(min) ) + min = index ; + } + + auto temp = v.at(head) ; + v.at(head) = v.at(min) ; + v.at(min) = v.at(head) ; + } + + for ( std::size_t i = 0 ; i != size ; ++i ) + { + std::cout << v.at(i) << " "s ; + } +} +\end{lstlisting} + +さっそく実行してみよう。 + +\begin{lstlisting}[style=terminal] +$ make run +1 1 1 1 1 1 1 1 7 +\end{lstlisting} + +コンパイルはできるが、なぜかうまく動かない。コードのどこかが間違っているのはわかる。しかしどこが間違っているのかはわからない。さっそく\texttt{printfデバッグ}により問題のある箇所を特定してみよう。 + +\texttt{printfデバッグ}を行うには、まずコード中の間違っていそうな箇所にアタリをつける必要がある。 + +問題がどこにあるかわからないが、ループのどこかで間違っていそうだ。一番外側のループにアタリをつけよう。ループが実行されるごとに変数\texttt{v}の中身を表示してみる。 + +\begin{lstlisting}[language={C++}] +for ( std::size_t head = 0 ; head != size ; ++head ) +{ + // printfデバッグ + std::cout << "debug: head = "s << head << ", v = { "s; + for ( std::size_t i = 0 ; i != v.size() ; ++i ) + { + std::cout << v.at(i) << " "s ; + } + std::cout << "}\n"s ; + // printfデバッグ +\end{lstlisting} + +そして実行した結果が以下だ。 + +\begin{lstlisting}[style=terminal] +$ make run +debug: v = { 3, 8, 2, 5, 6, 9, 4, 1, 7, } +debug: v = { 1, 8, 2, 5, 6, 9, 4, 1, 7, } +debug: v = { 1, 1, 2, 5, 6, 9, 4, 1, 7, } +debug: v = { 1, 1, 1, 5, 6, 9, 4, 1, 7, } +debug: v = { 1, 1, 1, 1, 6, 9, 4, 1, 7, } +debug: v = { 1, 1, 1, 1, 1, 9, 4, 1, 7, } +debug: v = { 1, 1, 1, 1, 1, 1, 4, 1, 7, } +debug: v = { 1, 1, 1, 1, 1, 1, 1, 1, 7, } +debug: v = { 1, 1, 1, 1, 1, 1, 1, 1, 7, } +1 1 1 1 1 1 1 1 7 +\end{lstlisting} + +なぜか\texttt{1}が増えている。明らかにおかしい。しかしまだ問題の特定にまでは至らない。 + +内側のループにも\texttt{printfデバッグ}を追加してみよう。 + +\begin{lstlisting}[language={C++}] +auto min = head ; +for ( std::size_t index = head+1 ; index != size ; ++index ) +{ + // printfデバッグ + std::cout << v.at(index) << ", "s ; + // printfデバッグ + + if ( v.at(index) < v.at(min) ) + min = index ; +} +// printfデバッグ + std::cout << "\n"s ; +// printfデバッグ +\end{lstlisting} + +そして実行する。 + +\begin{lstlisting}[style=terminal] +debug: v = { 3, 8, 2, 5, 6, 9, 4, 1, 7, } +8, 2, 5, 6, 9, 4, 1, 7, +debug: v = { 1, 8, 2, 5, 6, 9, 4, 1, 7, } +2, 5, 6, 9, 4, 1, 7, +debug: v = { 1, 1, 2, 5, 6, 9, 4, 1, 7, } +5, 6, 9, 4, 1, 7, +debug: v = { 1, 1, 1, 5, 6, 9, 4, 1, 7, } +6, 9, 4, 1, 7, +debug: v = { 1, 1, 1, 1, 6, 9, 4, 1, 7, } +9, 4, 1, 7, +debug: v = { 1, 1, 1, 1, 1, 9, 4, 1, 7, } +4, 1, 7, +debug: v = { 1, 1, 1, 1, 1, 1, 4, 1, 7, } +1, 7, +debug: v = { 1, 1, 1, 1, 1, 1, 1, 1, 7, } +7, +debug: v = { 1, 1, 1, 1, 1, 1, 1, 1, 7, } + +1 1 1 1 1 1 1 1 7 +\end{lstlisting} + +あまりいい情報は得られないようだ。問題はここではないらしい。 + +ひょっとしたら大小比較が間違っているのかもしれない。確かめてみよう。 + +\begin{lstlisting}[language={C++}] +for ( std::size_t index = head+1 ; index != size ; ++index ) +{ + + if ( v.at(index) < v.at(min) ) + { + std::cout << v.at(index) << " < "s << v.at(min) << "\n"s ; + min = index ; + } + else + { + std::cout << v.at(index) << " >= "s << v.at(min) << "\n"s ; + } +} +\end{lstlisting} + +実行結果は長いので一部だけ記載しておく。 + +\begin{lstlisting}[style=terminal] +debug: v = { 3, 8, 2, 5, 6, 9, 4, 1, 7, } +8 >= 3 +2 < 3 +5 >= 2 +6 >= 2 +9 >= 2 +4 >= 2 +1 < 2 +7 >= 1 + +debug: v = { 1, 8, 2, 5, 6, 9, 4, 1, 7, } +2 < 8 +5 >= 2 +6 >= 2 +9 >= 2 +4 >= 2 +1 < 2 +7 >= 1 +\end{lstlisting} + +大小比較も問題ないようだ。では最終的に見つけた最も小さい値は、本当に最も小さい値だろうか。 + +\begin{lstlisting}[language={C++}] +// 最小値を探すループ +for ( std::size_t index = head+1 ; index != size ; ++index ) +{ + // より小さい値があればそれを現在の最小値とする + if ( v.at(index) < v.at(min) ) + min = index ; +} + +// printfデバッグ + std::cout << v.at(min) << "\n"s ; +// printfデバッグ +\end{lstlisting} + +\begin{lstlisting}[style=terminal] +debug: v = { 3, 8, 2, 5, 6, 9, 4, 1, 7, } +1 +debug: v = { 1, 8, 2, 5, 6, 9, 4, 1, 7, } +1 +debug: v = { 1, 1, 2, 5, 6, 9, 4, 1, 7, } +1 +debug: v = { 1, 1, 1, 5, 6, 9, 4, 1, 7, } +1 +debug: v = { 1, 1, 1, 1, 6, 9, 4, 1, 7, } +1 +debug: v = { 1, 1, 1, 1, 1, 9, 4, 1, 7, } +1 +debug: v = { 1, 1, 1, 1, 1, 1, 4, 1, 7, } +1 +debug: v = { 1, 1, 1, 1, 1, 1, 1, 1, 7, } +1 +debug: v = { 1, 1, 1, 1, 1, 1, 1, 1, 7, } +7 +1 1 1 1 1 1 1 1 7 +\end{lstlisting} + +見つけた値は最も小さいようだ。しかし毎回\texttt{1}になる。\texttt{1}が残っているのだから当然だが、なぜ残っているのだろう。 + +ひょっとしたら要素の交換が間違っているのかもしれない。\texttt{printfデバッグ}してみよう。 + +\begin{lstlisting}[language={C++}] +// printfデバッグ + std::cout << "debug before: "s << v.at(head) << ", " << v.at(min) << "\n"s ; +// printfデバッグ + +v.at(head) = v.at(min) ; +v.at(min) = v.at(head) ; + +// printfデバッグ + std::cout << "debug after : "s << v.at(head) << ", " << v.at(min) << "\n"s ; +// printfデバッグ +\end{lstlisting} + +\texttt{"debug before:"}\,は交換前、\texttt{"debug after:"}\,は交換後の2つの要素の値だ。 + +以下は実行結果の一部だ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=terminal] +debug: v = { 3, 8, 2, 5, 6, 9, 4, 1, 7, } +debug before: 3, 1 +debug after : 1, 1 +debug: v = { 1, 8, 2, 5, 6, 9, 4, 1, 7, } +debug before: 8, 1 +debug after : 1, 1 +debug: v = { 1, 1, 2, 5, 6, 9, 4, 1, 7, } +debug before: 2, 1 +debug after : 1, 1 +debug: v = { 1, 1, 1, 5, 6, 9, 4, 1, 7, } +\end{lstlisting} + +これを見ると、要素の値の交換が正しく行われていないことがわかる。 + +問題の場所がわかったので、さっそくコードを見てみよう。 + +\begin{lstlisting}[language={C++}] +v.at(head) = v.at(min) ; +v.at(min) = v.at(head) ; +\end{lstlisting} + +これは要するに以下のコードと同じだ。 + +\begin{lstlisting}[language={C++}] +int a = 0 ; +int b = 1 ; + +a = b ; // a = 1 +b = a ; // b = 1 +\end{lstlisting} + +変数\texttt{a}, \texttt{b}の値を交換したい場合、変数\texttt{a}に変数\texttt{b}を代入したあとに、変数\texttt{b}に変数\texttt{a}を代入する処理は誤りだ。なぜならば、変数\texttt{b}の代入のときには、変数\texttt{a}の値は変数\texttt{b}の値になってしまっているからだ。 + +前章で学んだように、こういう場合、別の変数に値を代入して退避させておく。 + +\begin{lstlisting}[language={C++}] +int a = 0 ; +int b = 1 ; + +int temp = a ; +a = b ; +b = temp ; +\end{lstlisting} + +こうして\texttt{printfデバッグ}によって問題が解決した。 + +\clearpage +\hypersection{ch1002}{std::cerr} +\index{cerr@\texttt{cerr}} + +\texttt{printfデバッグ}として標準出力である\texttt{std::cout}に出力すると、プログラムの通常の標準出力と混ざって見づらくなる。例えば以下のプログラムを見てみよう。 + +\begin{lstlisting}[language={C++}] +// 1 * 2 * 3 * ... * nを計算するプログラム +int main() +{ + int n{} ; + std::cin >> n ; + if ( n < 1 ) + return -1 ; + + int sum = 1 ; + for ( int i = 2 ; i <= n ; ++i ) + { + sum *= i ; + + // printfデバッグ + std::cout << "debug: "s << i << ", " << sum << "\n"s ; + // printfデバッグ + } + + std::cout << sum ; +} +\end{lstlisting} + +この場合、標準エラー出力を使うとプログラムの通常の出力と\texttt{printfデバッグ}用の出力を分けることができる。 + +標準エラー出力を使うには、\texttt{std::cout}の代わりに\texttt{std::cerr}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 標準出力 + std::cout << "standard output\n"s ; + + // 標準エラー出力 + std::cerr << "standard error output\n"s ; +} +\end{lstlisting} + +このプログラムを実行すると一見すべて同じように出力されているように見える。 + +\begin{lstlisting}[style=terminal] +$ make run +standard output +standard error output +\end{lstlisting} + +違いはリダイレクトやパイプを使うとわかる。 + +\begin{lstlisting}[style=terminal] +$ ./program > /dev/null +standard error output +$ ./program | grep error +standard error output +\end{lstlisting} + +標準出力には\,\texttt{"standard output{\textbackslash}n"}\,しか出力されていない。通常のリダイレクトやパイプで扱われるのも標準出力だけだ。そのため、\texttt{/dev/null}にリダイレクトすると標準エラー出力しか見えないし、\texttt{grep}にパイプしても標準出力しか受け取らない。 + +標準出力と標準エラー出力を別々にリダイレクトする方法もある。 + +\begin{lstlisting}[style=terminal] +$ ./program > cout.txt 2> cerr.txt +\end{lstlisting} + +これを実行すると、ファイル\texttt{cout.txt}には\,\texttt{"standard output{\textbackslash}n"}\,が、ファイル\texttt{cerr.txt}には\,\texttt{"standard error output{\textbackslash}n"}\,が出力されている。 + +これを使って先ほどのプログラムを書き直すと以下のようになる。 + +\begin{lstlisting}[language={C++}] +// 1 * 2 * 3 * ... * nを計算するプログラム +int main() +{ + int n{} ; + std::cin >> n ; + if ( n < 1 ) + return -1 ; + + int sum = 1 ; + for ( int i = 2 ; i <= n ; ++i ) + { + sum *= i ; + + // printfデバッグ + // 標準エラー出力 + std::cerr << "debug: "s << i << ", " << sum << "\n"s ; + // printfデバッグ + } + // 標準出力 + std::cout << sum ; +} +\end{lstlisting} + +\clearpage +\hypersection{ch1003}{まとめ} + +\texttt{printfデバッグ}はコード中のどこに問題があるかを絞り込むための方法だ。プログラムに問題が存在し、問題の発生の有無はプログラムの状態を調べることで判断できるが、コード中のどこに問題が存在するかわからないとき、\texttt{printfデバッグ}で問題の箇所を絞り込むことができる。 + +\texttt{printfデバッグ}のやり方は以下のとおり。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + コード中の間違っていそうな箇所にアタリをつける +\item + プログラムの状態を出力する +\item + 出力結果が期待どおりかどうかを調べる +\end{enumerate} + +\texttt{printfデバッグ}は原始的だが効果的なデバッグ方法だ。あとの章ではデバッガーというより高級でプログラマーらしいデバッグ方法も紹介するが、そのような高級なデバッグ方法が使えない環境でも、\texttt{printfデバッグ}ならば使えることは多い。 diff --git a/TeX/011-integer.tex b/TeX/011-integer.tex new file mode 100644 index 0000000..85ba06e --- /dev/null +++ b/TeX/011-integer.tex @@ -0,0 +1,634 @@ +\hyperchapter{ch11}{整数}{整数} +\index{せいすう@整数} + +始めに書いておくがこの章はユーモア欠落症患者によって書かれており極めて退屈だ。しかし、整数の詳細はすべてのプログラマーが理解すべきものだ。心して読むとよい。 + +\hypersection{ch1101}{整数リテラル} +\index{せいすうりてらる@整数リテラル} + +整数リテラルとは整数の値を直接ソースファイルに記述する機能だ。本書ではここまで何の説明もなくリテラルを使っていた。例えば以下のように。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 123 ; + int b = 0 ; + int c = -123 ; +} +\end{lstlisting} + +ここでは、\texttt{'123'}, \texttt{'0'}\,がリテラルだ。\texttt{'-123'}\,というのは演算子\texttt{operator -}\,に整数リテラル\texttt{123}を適用したものだ。リテラルは\texttt{123}だけだ。ただしこれは細かい詳細なのでいまはそれほど気にしなくてもよい。 + +\hypersubsection{ch110101}{10進数リテラル} +\index{10しんすうりてらる@10進数リテラル}\index{せいすうりてらる@整数リテラル!10しんすう@10進数〜} + +10進数リテラルは最も簡単で我々が日常的に使っている数の表記方法と同じものだ。接頭語は何も使わず数字には\texttt{0}, \texttt{1}, \texttt{2}, \texttt{3}, \texttt{4}, \texttt{5}, \texttt{6}, \texttt{7}, \texttt{8}, \texttt{9}が使える。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// 10進数で123 +int decimal = 123 ; +\end{lstlisting} + +ただし、10進数リテラルの先頭を\texttt{0}にしてはならない。これは8進数リテラルになってしまう。 + +\begin{lstlisting}[language={C++}] +// 10進数で83 +int octal = 0123 ; +\end{lstlisting} + +\hypersubsection{ch110102}{2進数リテラル} +\index{2しんすうりてらる@2進数リテラル}\index{せいすうりてらる@整数リテラル!2しんすう@2進数〜} + +2進数リテラルは接頭語\texttt{'0b'}, \texttt{'0B'}\,から始まる。数字には\texttt{0}, \texttt{1}を使うことができる。 + +\begin{lstlisting}[language={C++}] +// 10進数で5 +int binary = 0b1010 ; + +// 0bと0Bは同じ +int a = 0B1010 ; +\end{lstlisting} + +\hypersubsection{ch110103}{8進数リテラル} +\index{8しんすうりてらる@8進数リテラル}\index{せいすうりてらる@整数リテラル!8しんすう@8進数〜} + +8進数リテラルは接頭語\,\texttt{'0'}\,から始まる。数字には\texttt{0}, \texttt{1}, \texttt{2}, \texttt{3}, \texttt{4}, \texttt{5}, \texttt{6}, \texttt{7}を使うことができる。 + +\begin{lstlisting}[language={C++}] +// 10進数で83 +int octal = 0123 ; + +// 10進数で342391 +int a = 01234567 ; +\end{lstlisting} + +\hypersubsection{ch110104}{16進数リテラル} +\index{16しんすうりてらる@16進数リテラル}\index{せいすうりてらる@整数リテラル!16しんすう@16進数〜} + +16進数リテラルは接頭語\texttt{'0x'}, \texttt{'0X'}\,から始まる。数字には\texttt{0}, \texttt{1}, \texttt{2}, \texttt{3}, \texttt{4}, \texttt{5}, \texttt{6}, \texttt{7}, \texttt{8}, \texttt{9}, \texttt{a}, \texttt{b}, \texttt{c}, \texttt{d}, \texttt{e}, \texttt{f}, \texttt{A}, \texttt{B}, \texttt{C}, \texttt{D}, \texttt{E}, \texttt{F}が使える。ローマ字の大文字と小文字は意味が同じだ。\texttt{a}, \texttt{b}, \texttt{c}, \texttt{d}, \texttt{e}, \texttt{f}がそれぞれ\texttt{10}, \texttt{11}, \texttt{12}, \texttt{13}, \texttt{14}, \texttt{15}を意味する。 + +\begin{lstlisting}[language={C++}] +// 10進数で291 +int hexadecimal = 0x123 ; + +// 0xと0Xは同じ +int a = 0X123 ; + +// 10進数で10 +int b = 0xa ; + +// 10進数で15 +int c = 0xf ; +\end{lstlisting} + +\hypersubsection{ch110105}{数値区切り} +\index{すうちくぎり@数値区切り}\index{せいすうりてらる@整数リテラル!すうちくぎり@数値区切り} + +長い整数リテラルは読みにくい。例えば\texttt{10000000}と\texttt{100000000}はどちらが大きくて具体的にどのくらいの値なのかがわからない。C++には整数リテラルを読みやすいように区切ることのできる数値区切りという機能がある。整数リテラルはシングルクオート文字(\,\texttt{'}\,)\index{\protect{'}@\texttt{\protect{'}}}で区切ることができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = (@\textcolor{black}{\texttt{1000'0000}}@) ; + int b = (@\textcolor{black}{\texttt{1'0000'0000}}@) ; +} +\end{lstlisting} + +区切り幅は何文字でもよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = (@\textcolor{black}{\texttt{1'22'333'4444'55555}}@) ; +} +\end{lstlisting} + +10進数整数リテラル以外でも使える。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto a = (@\textcolor{black}{\texttt{0b10101010'11110000'00001111}}@) ; + auto b = (@\textcolor{black}{\texttt{07'7'5}}@) ; + auto c = (@\textcolor{black}{\texttt{0xde'ad'be'ef}}@) ; +} +\end{lstlisting} + +\hypersection{ch1102}{整数の仕組み} +\index{せいすう@整数!しくみ@仕組み} + +\hypersubsection{ch110201}{情報の単位} +\index{じようほうのたんい@情報の単位} + +0から100までの整数を表現するには101種類の状態を表現できる必要がある。コンピューターはどうやって整数を表現しているのかをここで学ぶ。 + +情報の最小単位はビット(bit)\index{びつと@ビット}だ。ビットは2種類の状態を表現できる。たとえば\texttt{bool}型は\texttt{true}/\texttt{false}という2種類の状態を表現できる。 + +しかし、2種類の状態しか表現できない整数は使いづらい。0もしくは1しか表現できない整数とか、100もしく1000しか表現できない整数は使い物にならない。 + +また、ビットという単位も扱いづらい。コンピューターは膨大な情報を扱うので、ビットをいくつかまとめたバイト(byte)\index{ばいと@バイト}を単位として情報を扱っている。1バイトが何ビットであるかは環境により異なる。本書では最も普及している1バイトは8ビットを前提にする。 + +1ビットは2種類の状態を表現できるので、1バイトの中の8ビットは\(2^8 = 256\)種類の状態を表現できる。2バイトならば16ビットとなり、\(2^{16} = 65536\)種類の状態を表現できる。 + +\hypersubsection{ch110202}{1バイトで表現された整数} + +整数の表現方法について理解するために、1バイトで表現された整数を考えよう。 + +1バイトは8ビットであり256種類の状態を表現できる。整数を0から正の方向の数だけ表現したいとすると、0から255までの値を表現できることになる。 + +その場合、1バイトの整数の中の8ビットはちょうど2進数8桁で表現できる。 + +\begin{lstlisting}[language={C++}] +// 0 +auto zero = 0b00000000 ; +// 255 +auto max = 0b11111111 ; +\end{lstlisting} + +一番左側の桁が最上位桁で、一番右側の桁が最下位桁だ。これを最上位ビット、最下位ビットともいう。 + +正数だけを表現するならば話は簡単だ。1バイトの整数は0から255までの値を表現できる。これを符号なし整数(unsigned integer)\index{ふごうなしせいすう@符号なし整数}\index{せいすう@整数!ふごうなし@符号なし〜}という。 + +では負数を表現するにはどうしたらいいだろう。正数と負数を両方扱える整数表現のことを、符号付き整数(signed integer)\index{ふごうつきせいすう@符号付き整数}\index{せいすう@整数!ふごうつき@符号付き〜}という。1バイトは256種類の状態しか表現できないので、もし\(-1\)を表現したい場合、\(-1\)から254までの値を扱えることになる。 + +\(-1\)しか扱えないのでは実用的ではないので、負数と正数を同じ種類ぐらい表現したい。256の半分は128だが、1バイトで表現された整数は\(-128\)から128までを表現することはできない。0があるからだ。0を含めると、1バイトの整数は最大で\(-128\)から127までか、\(-127\)から128までを表現できる。どちらかに偏ってしまう。 + +では実際に1バイトで負数も表現できる整数表現を考えてみよう。 + +\hypersubsubsection{ch11020201}{符号ビット} +\index{ふごうびつと@符号ビット}\index{せいすう@整数!ふごうびつと@符号ビット} + +誰でも思いつきそうな表現方法に、符号ビットがある。これは最上位ビットを符号の有無を管理するフラグとして用いることにより、下位7ビットの値の符号を指定する方法だ。 + +符号ビット表現では\(-1\)と1は以下のように表現できる。 + +\begin{lstlisting}[language={C++}] +// 1 +(@\textcolor{black}{\texttt{0b0'0000001}}@) +// -1 +(@\textcolor{black}{\texttt{0b1'0000001}}@) +\end{lstlisting} + +最上位ビットが0であれば正数、1であれば負数だ。 + +この一見わかりやすい表現方法には問題がある。まず表現できる値の範囲は\(-127\)から\(+127\)だ。先ほど、1バイトで正負になるべく均等に値を割り振る場合、\(-128\)から\(+127\)、もしくは\(-127\)から\(+128\)までを扱えると書いた。しかし符号ビット表現では\(-127\)から\(+127\)しか扱えない。残りの1はどこにいったのか。 + +答えはゼロにある。符号ビット表現ではゼロに2通りの表現がある。\(+0\)と\(-0\)だ。 + +\begin{lstlisting}[language={C++}] +// +0 +(@\textcolor{black}{\texttt{0b0'0000000}}@) +// -0 +(@\textcolor{black}{\texttt{0b1'0000000}}@) +\end{lstlisting} + +\(+0\)も\(-0\)もゼロには違いない。しかし符号ビットが独立して存在しているために、ゼロが2種類ある。 + +符号ビットは電子回路で実装するには複雑という問題もある。 + +\hypersubsubsection{ch11020202}{1の補数} +\index{1のほすう@1の補数}\index{せいすう@整数!1のほすう@1の補数} + +1の補数は負数を絶対値を2進数で表したときの各ビットを反転させた値で表現する。たとえば\(-1\)は1(\texttt{0b00000001})の1の補数の\texttt{0b11111110}で表現される。 + +\begin{lstlisting}[language={C++}] +// -1 +0b11111110 + +// -2 +0b11111101 +\end{lstlisting} + +\(-1\)と\(-2\)を足すと結果は\(-3\)だ。この計算を1の補数で行うとどうなるか。 + +まず1の補数表現による\(-1\)と\(-2\)を足す。 + +\begin{lstlisting}[style=terminal] + 11111110 ++) 11111101 +----------- + 1'11111011 +\end{lstlisting} + +この結果は9ビットになる。この整数は8ビットなので、9ビット目を表現することはできない。ただし1の補数表現の計算では、もし9ビット目が繰り上がった場合は、演算結果に1を足す取り決めがある。 + +\begin{lstlisting}[style=terminal] + 11111011 ++) 1 +----------- + 11111100 +\end{lstlisting} + +1の補数による\(-3\)は3の各ビットを反転したものだ。3は\texttt{0b00000011}で、そのビットを反転させたものは\texttt{0b11111100}だ。上の計算結果は\(-3\)の1の補数表現になった。 + +もう1つ例を見てみよう。5と\(-2\)を足すと3になる。 + +\begin{lstlisting}[style=terminal] + 00000101 ++) 11111101 +----------- + 1'00000010 +\end{lstlisting} + +繰り上がりが発生したので1を足すと +\begin{lstlisting}[style=terminal] + 00000010 ++) 1 +----------- + 00000011 +\end{lstlisting} +3になった。 + +1の補数は引き算も足し算で表現できるので電子回路での実装が符号ビットよりもやや簡単になる。 + +ただし、1の補数にも問題がある。0の表現だ。0というのは\texttt{0b00000000}だが1の補数では\(-x\)は\(x\)の各ビット反転ということを適用すると、\(-0\)は\texttt{0b11111111}になる。すると、符号ビット表現と同じく、\(+0\)と\(-0\)が存在することになる。したがって、1の補数8ビットで表現できる範囲は\(-127\)から\(+127\)になる。 + +\hypersubsubsection{ch11020203}{2の補数} +\index{2のほすう@2の補数}\index{せいすう@整数!2のほすう@2の補数} + +符号ビットと1の補数による負数表現にある問題は、2の補数表現で解決できる。 + +2の補数表現による負数は1の補数表現の負数に、繰り上がり時に足すべき1を加えた値になる。 + +\(-1\)は1の補数表現では、1(\texttt{0b00000001})の各ビットを反転させた値になる(\texttt{0b11111110})。2の補数表現では、1の補数表現に1を加えた値になるので、\texttt{0b11111111}になる。 + +同様に、\(-2\)は\texttt{0b11111110}に、\(-3\)は\texttt{0b11111101}になる。 + +2の補数表現の\(-1\)と\(-2\)を足すと以下のようになる。 + +\begin{lstlisting}[style=terminal] + 11111111 ++) 11111110 +----------- + 1'11111101 +\end{lstlisting} + +9ビット目の繰り上がりを無視すると、計算結果は\texttt{0b11111101}になる。これは2の補数表現による\(-3\)と同じだ。 + +5と\(-2\)の計算も見てみよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=terminal] + 00000101 ++) 11111110 +----------- + 1'00000011 +\end{lstlisting} + +結果は3(\texttt{0b00000011})だ。 + +2の補数表現は引き算も足し算で実装できる上に、ゼロの表現方法は1つで、\(+0\)と\(-0\)が存在しない。8ビットの2の補数表現された整数の範囲は\(-128\)から\(+127\)になる。とても便利な負数の表現方法なのでほとんどのコンピューターで採用されている。 + +\hypersection{ch1103}{整数型} +\index{せいすうがた@整数型} + +C++にはさまざまな整数型が存在する。C++はCから引き継いだ歴史的な経緯により、整数型の文法がわかりにくくなっている。 + +基本的には、符号付き整数型と符号なし整数型に分かれている。 + +符号付き整数型\index{ふごうつきせいすうがた@符号付き整数型}としては、\texttt{signed char}, \texttt{short int}, \texttt{int}, \texttt{long int}, \texttt{long long int}が存在する。符号付き整数型は負数を表現できる。 + +符号なし整数型\index{ふごうなしせいすうがた@符号なし整数型}としては、\texttt{unsigned char}, \texttt{unsigned short int}, \texttt{unsigned int}, \texttt{unsigned long int}, \texttt{unsigned long long int}が存在する。符号なし整数型は負数を表現できない。 + +\hypersubsection{ch110301}{int型} + +\texttt{int型}\index{int@\texttt{int}型}は最も基本となる整数型だ。C++で数値を扱う場合、多くは\texttt{int}型になる。 + +\begin{lstlisting}[language={C++}] +int x = 123 ; +\end{lstlisting} + +整数リテラル\index{せいすうりてらる@整数リテラル}の型は通常は\texttt{int}型になる。 + +\begin{lstlisting}[language={C++}] +// int +auto x = 123 ; +\end{lstlisting} + +\texttt{unsigned int型}\index{unsigned int@\texttt{unsigned int}型}は符号のない\texttt{int}型だ。 + +\begin{lstlisting}[language={C++}] +unsigned int x = 123 ; +\end{lstlisting} + +整数リテラルの末尾に\texttt{u}/\texttt{U}\index{uU@\texttt{u}/\texttt{U}}と書いた場合、\texttt{unsigned int}型になる。 + +\begin{lstlisting}[language={C++}] +// int +auto x = 123 ; +// unsigned int +auto y = 123u ; +\end{lstlisting} + +特殊なルールとして、単に\texttt{signed}と書いた場合、それは\texttt{int}になる。\texttt{unsigned}と書いた場合は、\texttt{unsigned int}になる。 + +\begin{lstlisting}[language={C++}] +// int +signed a = 1 ; +// unsigned int +unsigned b = 1 ; +\end{lstlisting} + +\texttt{signed int}と書いた場合、\texttt{int型}になる。\texttt{signed int}は\texttt{int}の冗長な書き方だ。 + +\hypersubsection{ch110302}{long int型} + +\texttt{long int型}\index{long int@\texttt{long int}型}は\texttt{int型}以上の範囲の整数を扱える型だ。具体的な整数型の値の範囲は実装依存だが、\texttt{long int型}は\texttt{int型}の表現できる整数の範囲はすべて表現でき、かつ\texttt{int型}以上の範囲の整数型を表現できるかもしれない型だ。 + +\texttt{unsigned long int型}\index{unsigned long int@\texttt{unsigned long int}型}は符号なしの\texttt{long int}だ。 + +\begin{lstlisting}[language={C++}] +long int a = 123 ; +unsigned long int b = 123 ; +\end{lstlisting} + +特殊なルールとして、単に\texttt{long}と書いた場合、それは\texttt{long int}になる。\texttt{unsigned long}と書いた場合、\texttt{unsigned long int}になる。 + +\begin{lstlisting}[language={C++}] +// long int +long a = 1 ; +// unsigned long int +unsigned long b = 1 ; +\end{lstlisting} + +通常、\texttt{int}を省略して単に\texttt{long}と書くことが多い。 + +整数リテラルの値が\texttt{int型}で表現できない場合、\texttt{long型}になる。例えば、\texttt{int型}で100億を表現できないが、\texttt{long型}では表現できる実装の場合、以下の変数\texttt{a}は\texttt{long型}になる。 + +\begin{lstlisting}[language={C++}] +// 100億 +auto a = (@\textcolor{black}{\texttt{100'0000'0000}}@) ; +\end{lstlisting} + +整数リテラルの値が\texttt{long}では表現できないが\texttt{unsigned long}では表現できる場合、\texttt{unsigned long型}になる。 + +整数リテラルの末尾に\texttt{l}/\texttt{L}\index{lL@\texttt{l}/\texttt{L}}と書いた場合、値にかかわらず\texttt{long型}になる。 + +\begin{lstlisting}[language={C++}] +// int +auto a = 123 ; +// long +auto b = 123l ; +// long +auto c = 123L ; +\end{lstlisting} + +符号なし整数型を意味する\texttt{u}/\texttt{U}と組み合わせることもできる。 + +\begin{lstlisting}[language={C++}] +// unsigned long +auto a = 123ul ; +auto b = 123lu ; +\end{lstlisting} + +順番と大文字小文字の組み合わせは自由だ。 + +\hypersubsection{ch110303}{long long int型} + +\texttt{long long int型}\index{long long int@\texttt{long long int}型}は\texttt{long int型}以上の範囲の整数を扱える型だ。\texttt{long}と同じく\texttt{long long}は\texttt{long long int}と同じで、\texttt{unsigned long long int}\index{unsigned long long int@\texttt{unsigned long long int}型}もある。 + +\begin{lstlisting}[language={C++}] +// long long int +long long a = 1 ; +// unsigned long long int +unsigned long long b = 1 ; +\end{lstlisting} + +整数リテラルの値が\texttt{long型}でも表現できないときは、\texttt{long long}が使われる。\texttt{long long}でも表現できない場合は\texttt{unsigned long long}が使われる。 + +整数リテラルの末尾に\texttt{ll}/\texttt{LL}\index{llLL@\texttt{ll}/\texttt{LL}}と書くと\texttt{long long int型}になる。 + +\begin{lstlisting}[language={C++}] +// long long int +auto a = 123ll ; +// long long int +auto b = 123LL ; +// unsigned long long int +auto c = 123ull ; +\end{lstlisting} + +\hypersubsection{ch110304}{short int型} + +\texttt{short int型}\index{short int@\texttt{short int}型}は\texttt{int型}より小さい範囲の値を扱う整数型だ。\texttt{long}, \texttt{long long}と同様に、\texttt{unsigned short int}型\index{unsigned short int@\texttt{unsigned short int}型}もある。単に\texttt{short}と書くと、\texttt{short int}と同じ意味になる。 + +整数リテラルで\texttt{short int}型を表現する方法はない。 + +\hypersubsection{ch110305}{char型} + +\texttt{char型}はやや特殊で、\texttt{char}\index{char@\texttt{char}型}, \texttt{signed char}\index{signed char@\texttt{signed char}型}, \texttt{unsigned char}\index{unsigned char@\texttt{unsigned char}型}の3種類の型がある。\texttt{signed char}と\texttt{char}は別物だ。\texttt{char型}は整数型であり、あとで説明するように文字型でもある。\texttt{char型}の符号の有無は実装ごとに異なる。 + +\hypersection{ch1104}{整数型のサイズ} + +整数型を含む変数のサイズ\index{せいすうがた@整数型!さいず@サイズ}\index{へんすう@変数!さいず@サイズ}は、\texttt{sizeof演算子}\index{sizeof@\texttt{sizeof}演算子}で確認することができる。\texttt{sizeof(T)}は\texttt{T}に型名や変数名を入れることで、サイズを取得することができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << sizeof(int) << "\n"s ; + + int x{} ; + std::cout << sizeof(x) ; +} +\end{lstlisting} + +\texttt{sizeof演算子}は\texttt{std::size\_t型}\index{size\_t@\texttt{size\_t}型}を返す。\texttt{vector}の章でも出てきたこの型は実装依存の符号なし型であると定義されている。単位はバイトだ。 + +以下が各種整数型のサイズを出力するプログラムだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto print = []( std::size_t s ) + { std::cout << s << "\n"s ; } ; + + print( sizeof(char) ) ; + print( sizeof(short) ) ; + print( sizeof(int) ) ; + print( sizeof(long) ) ; + print( sizeof(long long ) ) ; +} +\end{lstlisting} + +このプログラムを筆者の環境で実行した結果が以下になる。 + +\begin{lstlisting}[style=terminal] +1 +2 +4 +8 +8 +\end{lstlisting} + +どうやら筆者の環境では、\texttt{char}が1バイト、\texttt{short}が2バイト、\texttt{int}が4バイト、\texttt{long}と\texttt{long long}が8バイトのようだ。この結果は環境ごとに異なるので読者も自分で\texttt{sizeof}演算子をさまざまな型に適用して試してほしい。 + +\hypersection{ch1105}{整数型の表現できる値の範囲} + +整数型の表現できる値の最小値\index{せいすうがた@整数型!さいしようち@最小値}と最大値\index{せいすうがた@整数型!さいだいち@最大値}は\texttt{std::numeric\_limits}\,\index{numeric\_limits@\texttt{numeric\_limits}}で取得できる。最小値は\texttt{::min()}\index{min@\texttt{min}}を、最大値は\texttt{::max()}\index{max@\texttt{max}}で得られる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout + << std::numeric_limits::min() << "\n"s + << std::numeric_limits::max() ; +} +\end{lstlisting} + +実行結果 + +\begin{lstlisting}[style=terminal] +-2147483648 +2147483647 +\end{lstlisting} + +どうやら筆者の環境では\texttt{int}型は\(−21億4748万3648\)から21億4748万3647までの範囲の値を表現できるようだ。 + +\texttt{unsigned int}はどうだろうか。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout + << std::numeric_limits::min() << "\n"s + << std::numeric_limits::max() ; +} +\end{lstlisting} + +実行結果 + +\begin{lstlisting}[style=terminal] +0 +4294967295 +\end{lstlisting} + +どうやら筆者の環境では\texttt{unsigned int}型は0から42億9496万7295までの範囲の値を表現できるようだ。\texttt{sizeof(int)}が4バイトであり、1バイトが8ビットの筆者の環境では自然な値だ。符号なしの4バイト整数型は0から\(2^{32}-1\)までの範囲の値を表現できる。符号付き4バイト整数型は\(-2^{31}\)から\(2^{31}-1\)までの範囲の値を表現できる。 + +整数の最小値を\(-1\)したり、最大値を\(+1\)した場合、何が起こるのだろうか。 + +符号なし整数型の場合は簡単だ。最小値\(-1\)は最大値になる。最大値\(+1\)は最小値になる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + unsigned int min = std::numeric_limits::min() ; + unsigned int max = std::numeric_limits::max() ; + + unsigned int min_minus_one = min - 1u ; + unsigned int max_plus_one = max + 1u ; + + std::cout << min << "\n"s << max << "\n"s + << min_minus_one << "\n"s << max_plus_one ; +} +\end{lstlisting} + +8ビットの符号なし整数型があるとして、最小値は\texttt{0b00000000}(0)になるが、この値を\(-1\)すると\texttt{0b11111111}(255)となり、これは最大値になる。逆に、最大値である\texttt{0b11111111}(255)に\(+1\)すると\texttt{0b00000000}(0)となり、これは最小値になる。 + +これを数学的に厳密に書くと、「符号なし整数は算術モジュロ\(2^n\)の法に従う。ただし\(n\)は整数を表現する値のビット数である」となる。 + +符号付き整数型の場合、挙動は定められていない。ただし、一般に普及している2の補数表現の場合は、以下のような挙動になることが多い。 + +符号付き整数型の最小値を\(-1\)すると最大値になり、最大値を\(+1\)すると最小値になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int min = std::numeric_limits::min() ; + int max = std::numeric_limits::max() ; + + int min_minus_one = min - 1 ; + int max_plus_one = max + 1 ; + + std::cout << min << "\n"s << max << "\n"s + << min_minus_one << "\n"s << max_plus_one ; +} +\end{lstlisting} + +これはなぜか。2の補数表現の8ビットの符号付き整数の最小値は\texttt{0b10000000}(\(-128\))だが、これを\(-1\)すると\texttt{0b01111111}(127)となり、これは最大値となる。逆に最大値\texttt{0b01111111}(127)を\(+1\)すると\texttt{0b10000000}(\(-128\))となり、これは最小値となる。 + +\clearpage +\hypersection{ch1106}{整数型の変換} +\index{せいすうがた@整数型!へんかん@変換} + +整数型にはここで紹介しただけでも、さまざまな型がある。同じ型同士を使った方がよい。 + +以下は型が一致している例だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 123 ; + long b = 123l ; + long long c = 123ll ; + + unsigned int d = 123u ; +} +\end{lstlisting} + +以下は型が一致していない例だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // intからshort + short a = 123 ; + // longからint + int b = 123l ; + + // intからunsigned int + unsigned int c = 123 ; + // unsigned intからint + int d = 123u ; +} +\end{lstlisting} + +代入や演算で整数型が一致しない場合、整数型の変換が行われる。 + +整数型の変換で注意すべきこととしては、変換元の値を変換先の型で表現できない場合の挙動だ。 + +たとえば\texttt{short}型と\texttt{int}型の表現できる最大値を調べるプログラムを書いてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << "short: "s << std::numeric_limits::max() << "\n"s + << "int: "s << std::numeric_limits::max() ; +} +\end{lstlisting} + +これを実行すると筆者の環境では以下のようになる。 + +\begin{lstlisting}[style=terminal] +short: 32767 +int: 2147483647 +\end{lstlisting} + +どうやら筆者の環境では\texttt{short}型は約3万、\texttt{int}型は約21億ぐらいの値を表現できるようだ。 + +では約3万までしか表現できない\texttt{short}型に4万を代入しようとするとどうなるのか。これは1つ前の整数型の表現できる値の範囲で説明したものと同じことが起こる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + short x = 40000 ; + std::cout << x ; +} +\end{lstlisting} + +このプログラムを実行した結果は実装ごとに異なる。例えば筆者の環境では以下のようになる。 + +\begin{lstlisting}[style=terminal] +-25536 +\end{lstlisting} + +整数型の変換は暗黙的に行われるが、明示的に行うこともできる。明示的な変換には\texttt{static\_cast(e)}\index{static\_cast@\texttt{static\_cast}}を使う。\texttt{static\_cast}は値\texttt{e}を型\texttt{T}の値に変換する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 123 ; + short y = static_cast(x) ; +} +\end{lstlisting} + diff --git a/TeX/012-floating-point.tex b/TeX/012-floating-point.tex new file mode 100644 index 0000000..db8f0f9 --- /dev/null +++ b/TeX/012-floating-point.tex @@ -0,0 +1,344 @@ +\hyperchapter{ch12}{浮動小数点数}{浮動小数点数} +\index{ふどうしようすうてんすう@浮動小数点数} + +浮動小数点数の型\index{ふどうしようすうてんすうかた@浮動小数点数型}には\texttt{float}\index{float@\texttt{float}}, \texttt{double}\index{double@\texttt{double}}, \texttt{long double}\index{long double@\texttt{long double}}がある。\texttt{float}が最も精度が低く、\texttt{double}は\texttt{float}と同等以上の精度を持ち、\texttt{long double}は\texttt{double}と同等以上の精度を持つ。 + +\begin{lstlisting}[language={C++}] +float f = 1.0 ; +double d = 1.0 ; +long double ld = 1.0 ; +\end{lstlisting} + +以下は浮動小数点数型の変数のサイズを調べるコードだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto print = [](std::size_t s ) + { std::cout << s << "\n"s ; } ; + + print( sizeof(float) ) ; + print( sizeof(double) ) ; + print( sizeof(long double) ) ; +} +\end{lstlisting} + +筆者の環境では以下のように出力される。 + +\begin{lstlisting}[style=terminal] +4 +8 +16 +\end{lstlisting} + +浮動小数点数は一見整数と同じように扱える上、小数点以下の値も扱える。 + +\begin{lstlisting}[language={C++}] +double a = 1.23 ; +double b = 0.00001 ; +\end{lstlisting} + +浮動小数点数が表現できる最大値は実装依存だが、通常はかなり大きな値を表現できる。 + +しかし、浮動小数点数は値を正確に表現しているわけではない。例えば以下のコードを実行してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 1万 + float a = 10000.0 ; + // 1万分の1 + float b = 0.0001 ; + + // 1万足す1万分の1 + float c = a + b ; + + std::cout << a << "\n"s << b << "\n"s << c ; +} +\end{lstlisting} + +変数\texttt{a}の値は1万、変数\texttt{b}の値は1万分の1だ。変数\texttt{c}の値は\texttt{a+b}で\texttt{10000.0001}となるはずだが結果はどうだろう。 + +\begin{lstlisting}[style=terminal] +10000 +0.0001 +10000 +\end{lstlisting} + +変数\texttt{c}の値は\texttt{10000.0001}ではない。この謎は浮動小数点数を学べば明らかになる。 + +\clearpage +\hypersection{ch1201}{浮動小数点数リテラル} +\index{ふどうしようすうてんすうりてらる@浮動小数点数リテラル} + +\hypersubsection{ch120101}{10進浮動小数点数リテラル} +\index{10しんふどうしようすうてんすうりてらる@10進浮動小数点数リテラル}\index{ふどうしようすうてんすうりてらる@浮動小数点数リテラル!10しんふどうしようすうてんすう@10進浮動小数点数〜} + +浮動小数点数リテラルの最も簡単な書き方は10進数で整数部を書き、小数点\,\texttt{'.'}\,を書き、続けて小数部を書く。末尾が\texttt{f}/\texttt{F}\index{fF@\texttt{f}/\texttt{F}}なら\texttt{float}型、末尾がなければ\texttt{double}型、末尾が\texttt{l}/\texttt{L}\index{lL@\texttt{l}/\texttt{L}}なら\texttt{long double}型だ。 + +\begin{lstlisting}[language={C++}] +// float +auto a = 123.456f ; +auto b = 123.456F ; + +// double +auto c = 123.456 ; + +// long double +auto d = 123.456l ; +auto e = 123.456L ; +\end{lstlisting} + +\hypersubsection{ch120102}{10進数の仮数と指数による表記} + +\texttt{123.456}という値について考えてみよう。この値は以下のように表現することができる。 +\[1.23456 \times 10^{2}\] + +あるいは以下のように表現することもできる。 +\[123456 \times 10^{-3}\] + +あるいは以下のようにも表現できる。 +\[123.456 \times 10^{0}\] + +一般に、値は以下のように表現できるということだ。 +\[a \times 10^{b}\] + +浮動小数点数リテラルのもう1つの文法として、この\texttt{a}と\texttt{b}を指定するものがある。 + +\begin{lstlisting}[language={C++}] +// 値はすべて123.456 +auto a = 1.23456e2 ; +auto b = 123456e-3 ; +auto c = 123.456e0 ; +auto d = 123.456E0 ; +\end{lstlisting} + +この文法は、\texttt{a}と\texttt{b}に\texttt{e}/\texttt{E}\index{eE@\texttt{e}/\texttt{E}}を挟むことによって浮動小数点数の値を指定する。 + +この\texttt{a}を仮数部(fractional part)\index{かすう@仮数}、\texttt{b}を指数部(exponent part)\index{しすう@指数}という。仮数のことはほかにも、coefficient, significand, mantissaなどと呼ばれたりもする。 + +そして、指数は底が10になる。 + +浮動小数点数は、値を正確に表現しているのではなく、仮数と指数の組み合わせで表現している。浮動小数点数が浮動と呼ばれる理由は、指数の存在によって小数点数が浮いているかのように動くからだ。 + +例えば、仮数と指数がともに符号付き1バイトの整数で表現された2バイトの浮動小数点数があるとする。指数、仮数ともに、\(-128\)から127までの範囲の整数を表現できる。この浮動小数点数は10000(1万)も100000000(1億)も1000000000000(1兆)も表現できる。それぞれ、\texttt{1e4}, \texttt{1e8}, \texttt{1e12}だ。 + +しかし、この浮動小数点数では1000100010000(1兆1億1万)を表現できない。なぜならば、この値を正確に表現するには、\texttt{100010001e4}を表現できる必要があるが、仮数は100010001を表現できないからだ。 + +浮動小数点数は値を必ずしも正確に表現できない。その代わり、とても大きな値や、とても小さな値を表現できる。 + +浮動小数点数の型を表す末尾の\texttt{f}/\texttt{F}/\texttt{l}/\texttt{L}\index{fF@\texttt{f}/\texttt{F}}\index{lL@\texttt{l}/\texttt{L}}は同じように使える。 + +\begin{lstlisting}[language={C++}] +// float +auto a = 1.0e0f ; +// double +auto b = 1.0e0 ; +// long double +auto c = 1.0e0l ; +\end{lstlisting} + +\hypersubsection{ch120103}{16進数の仮数と指数による表記} + +浮動小数点数の仮数部と指数部によるリテラルは、16進数で記述することもできる。 + +文法は、\texttt{0x}から始め、16進数の仮数部\index{かすう@仮数}を書き、\texttt{e}/\texttt{E}の代わりに\texttt{p}/\texttt{P}\index{pP@\texttt{p}/\texttt{P}}を使い、指数部\index{しすう@指数}を10進数で指定する。このときの指数部の底は2になる。 + +値は +\[仮数 \times 2^{指数}\] +になる。 + +\begin{lstlisting}[language={C++}] +// 5496 +double a = 0xabc.0p0 ; +// 7134 +double b = 0xde.fp5 ; +\end{lstlisting} + +\hypersection{ch1202}{浮動小数点数の表現と特性} + +浮動小数点数\index{ふどうしようすうてんすう@浮動小数点数}は指数と仮数で表現される。浮動小数点数の表現はさまざまだが、多くのアーキテクチャーでは国際標準規格のISO/IEC/IEEE 60559:2011が使われている。これは米国電気電子学会の規格IEEE 754--2008と同じ内容になっている。その大本はIntelが立案した規格、IEEE 754--1985だ。一般にはIEEE 754(アイトリプルイー 754)\index{IEEE 754}という名称で知られている。 + +IEEE 754では、浮動小数点数は符号ビット、仮数部、指数部からなる。本書ではIEEE 754を前提として、浮動小数点数で気を付けるべき特性を説明する。 + +\hypersubsection{ch120201}{\texttt{+}0.0と\,\texttt{-}0.0} +\index{+0.0}\index{-0.0@\(-0.0\)} + +IEEE 754では符号ビットがあるので、ゼロには2種類ある。正のゼロと負のゼロだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << 0.0 << "\n"s << -0.0 ; +} +\end{lstlisting} + +\(+0.0\)と\(-0.0\)の違いを浮動小数点数で表現することはできるが、値を比較すると同じものだとみなされる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // a, bは異なるビットパターンを持つ + double a = +0.0 ; + double b = -0.0 ; + + // true + bool c = a == b ; +} +\end{lstlisting} + +\hypersubsection{ch120202}{\texttt{+}{\(\infty\)}と\,\texttt{-}{\(\infty\)}\,(無限大)} +\index{+{\(\infty\)}}\index{-\(\infty\)@\(-\infty\)} + +IEEE 754の浮動小数点数は正の無限と負の無限を表現できる。 + +浮動小数点数の値としての無限は、計算の結果として現れるほか、\texttt{numeric\_limits::infinity()}\index{numeric\_limits@\texttt{numeric\_limits}}\index{infinity@\texttt{infinity}}を使って取得できる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + double a = std::numeric_limits::infinity() ; + double b = -std::numeric_limits::infinity() ; + + std::cout << a << "\n"s << b ; +} +\end{lstlisting} + +\hypersubsection{ch120203}{NaN\,(Not a Number)} + +NaN(Not a Number)\index{NaN(Not a Number)}は計算結果が未定義の場合を表現する浮動小数点数の特別な値だ。 + +計算結果が未定義な場合とは、例えばゼロで除算する場合だ。 + +値としてのNaNは\texttt{numeric\_limits::quiet\_NaN()}\index{numeric\_limits@\texttt{numeric\_limits}}\index{quiet\_NaN@\texttt{quiet\_NaN}}で取得できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + double NaN = std::numeric_limits::quiet_NaN() ; + std::cout << NaN ; +} +\end{lstlisting} + +NaNとの比較結果はすべて\texttt{false}となる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + double NaN = std::numeric_limits::quiet_NaN() ; + + // すべてfalse + bool a = NaN == 0.0 ; + bool b = NaN != 0.0 ; + bool c = NaN == NaN ; + bool d = NaN != NaN ; + bool e = NaN < 0.0 ; +} +\end{lstlisting} + +整数であれば、\texttt{'a == b'}\,が\texttt{false}であるならば、\texttt{'a != b'}\,なのだと仮定してもよいが、こと浮動小数点数の場合、NaNの存在があるために必ずしもそうとは限らない。上の例でわかるように、NaNとの比較はすべて\texttt{false}になる。 + +\hypersubsection{ch120204}{有効桁数} +\index{ゆうこうけたすう@有効桁数}\index{ふどうしようすうてんすう@浮動小数点数!ゆうこうけたすう@有効桁数} + +浮動小数点数は正確な値のすべての桁数を表現できない。表現できるのは仮数部が何桁を正確に表現できるかに依存している。この有効桁数は、\texttt{numeric\_limits::digits10}\index{numeric\_limits@\texttt{numeric\_limits}}\index{digits10@\texttt{digits10}}で取得できる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout + << "float: "s << std::numeric_limits::digits10 << "\n"s + << "double: "s << std::numeric_limits::digits10 << "\n"s + << "long double: "s << std::numeric_limits::digits10 << "\n"s ; +} +\end{lstlisting} + +浮動小数点数型\texttt{T}の\texttt{numeric\_limits}\,にはもう1つ、\texttt{max\_digits10}\index{numeric\_limits@\texttt{numeric\_limits}}\index{max\_digits10@\texttt{max\_digits10}}がある。これは浮動小数点数を10進数表記にして、その10進数表記を浮動小数点数に戻したときに、浮動小数点数としての値を精度が落ちることなく再現できる桁数のことだ。 + +もう1つ興味深い値としては、\texttt{numeric\_limits::epsilon()}\index{numeric\_limits@\texttt{numeric\_limits}}\index{epsilon@\texttt{epsilon}}がある。これは浮動小数点数の1と比較可能な最小の値との差だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout + << "float: "s << std::numeric_limits::epsilon() << "\n"s + << "double: "s << std::numeric_limits::epsilon() << "\n"s + << "long double: "s << std::numeric_limits::epsilon() << "\n"s ; +} +\end{lstlisting} + +\hypersection{ch1203}{浮動小数点数同士の変換} +\index{ふどうしようすうてんすう@浮動小数点数!へんかん@変換} + +浮動小数点数型は相互に変換できる。変換先の浮動小数点数型が変換元の値を完全に表現できるならばその値に、できないのであれば近い値に変換される。 + +\begin{lstlisting}[language={C++}] +int main() +{ + double a = 1.23456789 ; + + // 変換 + float b = a ; + // 変換 + long double c = a ; +} +\end{lstlisting} + +異なる浮動小数点数同士を演算すると、\texttt{float}\,\textless{}\,\texttt{double}\,\textless{}\,\texttt{long double}の順で大きい浮動小数点数型に合わせて変換される。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + // float + auto a = 1.0f + 1.0f ; + // double + auto b = 1.0f + 1.0 ; + // long double + auto c = 1.0f + 1.0l ; +} +\end{lstlisting} + +\hypersection{ch1204}{浮動小数点数と整数の変換} + +浮動小数点数型を整数型に変換すると、小数部が切り捨てられる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + double a = 1.9999 ; + // 1 + int x = a ; +} +\end{lstlisting} + +変換元の浮動小数点数から小数部を切り捨てた値が変換先の整数型で表現できなかった場合は、何が起こるかわからない。 + +整数型を浮動小数点数型に変換すると、変換元の整数の値が変換先の浮動小数点数型で正確に表現できる場合はその値に、そうでない場合は表現できる最も近い値になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; + // 1.0 + double b = a ; +} +\end{lstlisting} + +浮動小数点数と整数を演算した場合、浮動小数点数型になる。 + +\ifTombow\enlargethispage{5mm}\fi +\begin{lstlisting}[language={C++}] +int main() +{ + // double + auto a = 1.0 + 1 ; + auto b = 1 + 1.0 ; + + // float + auto c = 1.0f + 1 ; +} +\end{lstlisting} + diff --git a/TeX/013-names.tex b/TeX/013-names.tex new file mode 100644 index 0000000..ae041b2 --- /dev/null +++ b/TeX/013-names.tex @@ -0,0 +1,813 @@ +\hyperchapter{ch13}{名前}{名前} + +プログラミング言語C++にはさまざまな名前\index{なまえ@名前}が出てくる。変数、関数、型など、さまざまなものに名前が付いている。この章では名前について学ぶ。 + +\hypersection{ch1301}{キーワード} + +一部の名前はキーワード\index{きわど@キーワード}として予約され、特別な意味を持つ。キーワードは名前として使うことができない。 + +キーワードの一覧は以下のとおり。 + +\begin{lstlisting}[style=grammar] +alignas alignof asm auto bool break +case catch char char16_t char32_t class +concept const constexpr const_cast continue decltype +default delete do double dynamic_cast else +enum explicit export extern false float +for friend goto if inline int +long mutable namespace new noexcept nullptr +operator private protected public register reinterpret_cast +requires return short signed sizeof static +static_assert static_cast struct switch template this +thread_local throw true try typedef typeid +typename union unsigned using virtual void +volatile wchar_t while +\end{lstlisting} + +\hypersection{ch1302}{名前に使える文字} + +名前というのは根本的には識別子\index{しきべつし@識別子}と呼ばれる文字列で成り立っている。 + +C++では識別子にラテンアルファベット小文字、大文字、アラビア数字、アンダースコア、を使うことができる。以下がその文字の一覧だ。 + +\begin{lstlisting}[style=grammar] +a b c d e f g h i j k l m +n o p q r s t u v w x y z +A B C D E F G H I J K L M +N O P Q R S T U V W X Y Z +0 1 2 3 4 5 6 7 8 9 +_ +\end{lstlisting} + +小文字と大文字は区別される。名前\texttt{a}と名前\texttt{A}は別の名前だ。 + +ただし、名前はアラビア数字で始まってはならない。 + +\begin{lstlisting}[language={C++}] +int 123abc = 0 ; // エラー +\end{lstlisting} + +名前にダブルアンダースコア(\,\texttt{\_\_}\,)が含まれているものは予約されているので使ってはならない。ダブルアンダースコアとはアンダースコア文字が2つ連続したものをいう。 + +\begin{lstlisting}[language={C++}] +// 使ってはならない +// すべてダブルアンダースコアを含む +int __ = 0 ; +int a__ = 0 ; +int __a = 0 ; +\end{lstlisting} + +アンダースコアに大文字から始まる名前も予約されているので使ってはならない。 + +\begin{lstlisting}[language={C++}] +// 使ってはならない +// アンダースコアに大文字から始まる +int _A = 0 ; +\end{lstlisting} + +アンダースコアに小文字から始まる名前もグローバル名前空間で予約されているので使ってはならない。グローバル名前空間についてはこのあと説明する。 + +\begin{lstlisting}[language={C++}] +// 使ってはならない +// アンダースコアに小文字から始まる +int _a = 0 ; +\end{lstlisting} + +予約されているというのは、C++コンパイラーがその名前をC++の実装のために使うかもしれないということだ。例えばC++コンパイラーは\,\texttt{\_A}という名前を特別な意味を持つものとして使うかもしれないし、その名前の変数や関数をプログラムに追加するかもしれない。 + +\hypersection{ch1303}{宣言と定義} + +C++では、名前は使う前に宣言\index{せんげん@宣言}\index{なまえ@名前!せんげん@宣言}しなければならない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; // 宣言 + x = 1 ; // 使用 +} +\end{lstlisting} + +宣言する前に使うことはできない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // エラー、 名前xは宣言されていない。 + x = 1 ; + + int x = 0 ; +} +\end{lstlisting} + +C++では多くの名前は宣言と定義\index{ていぎ@定義}\index{なまえ@名前!ていぎ@定義}に分かれている。宣言と定義の分離は関数が一番わかりやすい。 + +\begin{lstlisting}[language={C++}] +// 関数の宣言 +int plus_one( int x ) ; + +// 関数の定義 +int plus_one( int x ) // 宣言部分 +// 定義部分 +// 関数の本体 +{ + return x + 1 ; +} +\end{lstlisting} + +関数の定義は宣言を兼ねる。 + +宣言は何度でも書くことができる。 + +\begin{lstlisting}[language={C++}] +int plus_one( int x ) ; // 初出 +int plus_one( int x ) ; // OK +int plus_one( int x ) ; // OK +\end{lstlisting} + +定義はプログラム中に一度しか書くことができない。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// 定義 +int odr() { } + +// エラー、 定義の重複 +int odr() { } +\end{lstlisting} + +名前を使うのに事前に必要なのは宣言だ。定義は名前を使ったあとに書いてもよい。 + +\begin{lstlisting}[language={C++}] +// 宣言 +int plus_one( int x ) ; + +int main() +{ + plus_one( 1 ) ; +} + +// 定義 +int plus_one( int x ) +{ + return x + 1 ; +} +\end{lstlisting} + +ほとんどの変数は宣言と定義が同時に行われる。変数でも宣言と定義を分割して行う方法もあるのだが、解説は分割コンパイルの章で行う。 + +\hypersection{ch1304}{名前空間} +\index{なまえくうかん@名前空間} + +本書をここまで読んだ読者は、一部の型名の記述が少し変なことに気が付いているだろう。 + +\begin{lstlisting}[language={C++}] +std::string a ; +std::vector b ; +\end{lstlisting} + +コロンやアングルブラケットは名前に使える文字ではない。信じられない読者は試してみるとよい。 + +\begin{lstlisting}[language={C++}] +// エラー +int :: = 0 ; +int = 0 ; +\end{lstlisting} + +莫大なエラーが表示されるだろうが、すでに学んだようにとてもいいことだ。コンパイラーが間違いを見つけてくれたのだから。わからないことがあったらどんどんコンパイルエラーを出すとよい。 + +実は\texttt{std}というのは名前空間(namespace)の名前だ。ダブルコロン(\texttt{::})\index{::@\texttt{::}}は名前空間を指定する文法だ。 + +\ifTombow\pagebreak\fi +名前空間の文法は以下のとおり。 +\index{namespace@\texttt{namespace}} + +\begin{lstlisting}[style=grammar] +namespace ns { +// コード +} +\end{lstlisting} + +名前空間の中の名前を参照するには\,\texttt{::}\,\index{::@\texttt{::}}を使う。 + +\begin{lstlisting}[language={C++}] +ns::name ; +\end{lstlisting} + +名前空間の中には変数も書ける。この変数は関数の内部に限定されたローカル変数とは違い、どの関数からでも参照できる。 + +\begin{lstlisting}[language={C++}] +namespace ns { + int name{} ; +} + +int f() +{ + return ns::name ; +} + +int main() +{ + ns::name = 1 ; +} +\end{lstlisting} + +名前空間の中で宣言された名前は、名前空間を指定しなければ使えなくなる。 + +\begin{lstlisting}[language={C++}] +namespace ns { + int f() { return 0 ; } +} + +int main() +{ + ns::f() ; + + f() ; // エラー +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +異なる名前空間名の下の名前は別の名前になる。 + +\begin{lstlisting}[language={C++}] +namespace a { + int f() { return 0 ; } +} + + +namespace b { + int f() { return 1 ; } +} + +int main() +{ + a::f() ; // 0 + b::f() ; // 1 +} +\end{lstlisting} + +これだけを見ると、名前空間というのはわざわざ名前空間名を指定しなければ使えない面倒な機能に見えるだろう。名前空間の価値は複数人で同じプログラムのソースファイルを編集するときに出てくる。 + +例えば、アリスとボブがプログラムを共同で開発しているとする。あるプログラムのソースファイル\texttt{f}という名前の関数を書いたとする。ここで、同じプログラムを共同開発している他人も\texttt{f}という名前の関数を書いたらどうなるか。 + +\begin{lstlisting}[language={C++}] +// アリスの書いた関数f +int f() { return 0 ; } + +// ボブの書いた関数f +int f() { return 1 ; } +\end{lstlisting} + +すでに宣言と定義で学んだように、このコードはエラーになる。なぜならば、同じ名前に対して定義が2つあるからだ。 + +名前空間なしでこの問題を解決するためはに、アリスとボブが事前に申し合わせて、名前が衝突しないように調整する必要がある。 + +しかし名前空間があるC++では、そのような面倒な調整は必要がない。アリスとボブが別の名前空間を使えばいいのだ。 + +\begin{lstlisting}[language={C++}] +// アリスの名前空間 +namespace alice { + // アリスの書いた関数f + int f() { return 0 ; } +} + +(@\ifTombow\pagebreak\fi@) +// ボブの名前空間 +namespace bob { + // ボブの書いた関数f + int f() { return 1 ; } +} +\end{lstlisting} + +\texttt{alice::f}と\texttt{bob::f}は別の名前なので定義の衝突は起こらない。 + +\hypersubsection{ch130401}{グローバル名前空間} +\index{ぐろばるなまえくうかん@グローバル名前空間} + +名前空間に包まれていないソースファイルのトップレベルの場所は、実はグローバル名前空間(global name space)という名前のない名前空間で包まれているという扱いになっている。 + +\begin{lstlisting}[language={C++}] +// グローバル名前空間 +int f() { return 0 ; } + +namespace ns { + int f() { return 1 ; } +} + +int main() +{ + f() ; // 0 + ns::f() ; // 1 +} +\end{lstlisting} + +グローバル名前空間は名前の指定のない単なる\,\texttt{::}\,\index{::@\texttt{::}}で指定することもできる。 + +\begin{lstlisting}[language={C++}] +int x { } ; + +int main() +{ + x ; // ::xと同じ + ::x ; +} +\end{lstlisting} + +すでに名前空間の中では変数を宣言できることは学んだ。グローバル名前空間は名前空間なので同じように変数を宣言できる。 + +\texttt{main}関数はグローバル名前空間に存在しなければならない。 + +\begin{lstlisting}[language={C++}] +// グローバル名前空間 +int main() { } +\end{lstlisting} + +\hypersubsection{ch130402}{名前空間のネスト} +\index{なまえくうかん@名前空間!ねすと@ネスト} + +名前空間の中に名前空間を書くことができる。 + +\begin{lstlisting}[language={C++}] +namespace A { namespace B { namespace C { + int name {} ; +} } } + +int main() +{ + A::B::C::name = 0 ; +} +\end{lstlisting} + +名前空間のネストは省略して書くこともできる。 + +\begin{lstlisting}[language={C++}] +namespace A::B::C { + int name { } ; +} + +int main() +{ + A::B::C::name = 0 ; +} +\end{lstlisting} + +\hypersubsection{ch130403}{名前空間名の別名を宣言する名前空間エイリアス} +\index{なまえくうかん@名前空間!べつめい@別名}\index{なまえくうかんえいりあす@名前空間エイリアス} + +名前空間名には別名を付けることができる。これを名前空間エイリアスと呼ぶ。 + +たとえば名前空間名が重複することを恐れるあまり、とても長い名前空間名を付けたライブラリがあるとする。 + +\begin{lstlisting}[language={C++}] +namespace very_long_name { + int f() { return 0 ; } +} + +int main() +{ + very_long_name::f() ; +} +\end{lstlisting} + +この関数\texttt{f}を使うために毎回\texttt{very\_long\_name::f}と書くのは面倒だ。こういうときには名前空間エイリアスを使うとよい。名前空間エイリアスは名前空間名の別名を宣言できる。 + +\begin{lstlisting}[style=grammar] +namespace 別名 = 名前空間名 ; +\end{lstlisting} + +使い方。 + +\begin{lstlisting}[language={C++}] +namespace very_long_name { + int f() { return 0 ; } +} + +int main() +{ + // 名前空間エイリアス + namespace vln = very_long_name ; + // vlnはvery_long_nameのエイリアス + vln::f() ; +} +\end{lstlisting} + +名前空間エイリアスは元の名前空間名と同じように使える。意味も同じだ。 + +名前空間エイリアスはネストされた名前空間にも使える。 + +\begin{lstlisting}[language={C++}] +namespace A::B::C { + int f() { return 0 ; } +} + +int main() +{ + namespace D = A::B::C ; + // DはA::B::Cのエイリアス + D::f() ; +} +\end{lstlisting} + +名前空間エイリアスを関数の中で宣言すると、その関数の中でだけ有効になる。 + +\begin{lstlisting}[language={C++}] +namespace A { int x { } ; } + +int f() +{ + // Bの宣言 + namespace B = A ; + // OK、Bは宣言されている + return B::x ; +} + +int g() +{ + // エラー、 Bは宣言されていない + return B::x ; +} +\end{lstlisting} + +名前空間エイリアスを名前空間の中で宣言すると、宣言以降の名前空間内で有効になる。 + +\begin{lstlisting}[language={C++}] +namespace ns { + namespace A { int x { } ; } + namespace B = A ; + + // OK + int f(){ return B::x ; } + // OK + int g(){ return B::x ; } + +} // end namespace ns + +// エラー、 Bは宣言されていない +int h(){ return B::x ; } +\end{lstlisting} + +グローバル名前空間は名前空間なので、名前空間エイリアスを宣言できる。 + +\begin{lstlisting}[language={C++}] +namespace long_name_is_loooong { } +namespace cat = long_name_is_loooong ; +\end{lstlisting} + +\hypersubsection{ch130404}{名前空間名の指定を省略するusingディレクティブ} +\index{なまえくうかん@名前空間!しようりやく@省略}\index{using@\texttt{using}ディレクティブ} + +名前空間は名前の衝突を防ぐ機能だが、名前空間名をわざわざ指定するのは面倒だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // std名前空間のstring + std::string s ; + // std名前空間のvector + std::vector v ; + + // std名前空間のcout + std::cout << 123 ; +} +\end{lstlisting} + +もし自分のソースファイルが\texttt{string}, \texttt{vector}, \texttt{cout}、その他\texttt{std}名前空間で使われる名前をいっさい使っていない場合、名前の衝突は発生しないことになる。その場合でも名前空間名を指定しなければならないのは面倒だ。 + +C++では指定した名前空間を省略できる機能が存在する。\texttt{using}ディレクティブだ。 + +\begin{lstlisting}[style=grammar] +using namespace 名前空間名 ; +\end{lstlisting} + +\ifTombow\pagebreak\fi +これを使えば、先ほどのコードは以下のように書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + using namespace std ; + // std名前空間のstring + string s ; + // std名前空間のvector + vector v ; + + // std名前空間のcout + cout << 123 ; +} +\end{lstlisting} + +では名前が衝突した場合はどうなるのか。 + +\begin{lstlisting}[language={C++}] +namespace abc { + int f() { return 0 ; } +} + +int f() { return 1 ; } + +int main() +{ + using namespace abc ; + + // エラー、 名前が曖昧 + f() ; +} +\end{lstlisting} + +名前\texttt{f}に対してどの名前を使用するのか曖昧になってエラーになる。このエラーを回避するためには、名前空間を直接指定する。 + +\begin{lstlisting}[language={C++}] +namespace abc { + int f() { return 0 ; } +} + +int f() { return 1 ; } + +int main() +{ + using namespace abc ; + + // OK、名前空間abcのf + abc::f() ; + + // OK、グローバル名前空間のf + ::f() ; +} +\end{lstlisting} + +\texttt{using}ディレクティブは関数の中だけではなく、名前空間の中にも書ける。 + +\begin{lstlisting}[language={C++}] +namespace A { + int f(){ return 0 ; } +} + +namespace B { + using namespace A ; + int g() + { + // OK、A::f + f() ; + } +} +\end{lstlisting} + +名前空間の中に\texttt{using}ディレクティブを書くと、その名前空間の中では指定した名前空間を省略できる。 + +グローバル名前空間は名前空間なので\texttt{using}ディレクティブが書ける。 + +\begin{lstlisting}[language={C++}] +using namespace std ; +\end{lstlisting} + +ただし、グローバル名前空間の中に\texttt{using}ディレクティブを書くと、それ以降すべての箇所で指定した名前空間の省略ができてしまうので注意が必要だ。 + +\hypersubsection{ch130405}{名前空間を指定しなくてもよいinline名前空間} +\index{なまえくうかん@名前空間!inline@\texttt{inline}}\index{inline@\texttt{inline}名前空間} + +\texttt{inline名前空間}は\texttt{inline namespace}\index{inline namespace@\texttt{inline namespace}}で定義する。 + +\begin{lstlisting}[style=grammar] +inline namespace name { } +\end{lstlisting} + +\texttt{inline}名前空間内の名前は名前空間名を指定して使うこともできるし、名前空間を指定せずとも使うことができる。 + +\begin{lstlisting}[language={C++}] +inline namespace A { + int a { } ; +} + +namespace B { + int b { } ; +} + +int main() +{ + a = 0 ; // A::a + A::a = 0 ; // A::a + + b = 0 ; // エラー、 名前bは宣言されていない + B::b = 0 ; // B::b +} +\end{lstlisting} + +読者が\texttt{inline}名前空間を使うことはほとんどないだろうが、ライブラリのソースファイルを読むときには出てくるだろう。 + +\hypersection{ch1305}{型名} +\index{かためい@型名}\index{なまえ@名前!かためい@型名} + +型名とは型を表す名前だ。 + +型名は\texttt{int}や\texttt{double}のように言語組み込みのキーワードを使うこともあれば、独自に作った型名を使うこともある。この独自に作った型名を専門用語ではユーザー定義された型(user--defined type)\index{ゆざていぎされたかた@ユーザー定義された型}\index{かた@型!ゆざていぎされた@ユーザー定義された〜}という。ユーザー定義された型を作る方法はさまざまだ。具体的に説明するのは本書のだいぶあとの方になるだろう。例としては、\texttt{std::string}や\texttt{std::vector}\,がある。標準ライブラリによってユーザー定義された型だ。 + +\begin{lstlisting}[language={C++}] +// 組み込みの型名 +int i = 0 ; +double d = 0.0 ; + +// ユーザー定義された型名 +std::string s ; +std::vector v ; +\end{lstlisting} + +\hypersubsection{ch130501}{型名の別名を宣言するエイリアス宣言} +\index{かためい@型名!べつめい@別名}\index{えいりあすせんげん@エイリアス宣言}\index{かためい@型名!えいりあすせんげん@エイリアス宣言} + +長い名前空間名を書くのが煩わしいように、長い型名を書くのも煩わしい。名前空間名の別名を宣言できるように、型名も別名を宣言できる。 + +型名の別名を宣言するにはエイリアス宣言を使う。 + +\begin{lstlisting}[style=grammar] +using 別名 = 型名 ; +\end{lstlisting} + +\ifTombow\pagebreak\fi +使い方。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // エイリアス宣言 + using Number = int ; + + // Numberはintの別名 + Number x = 0 ; +} +\end{lstlisting} + +型名の別名は型名と同じように使える。意味も同じだ。 + +歴史的な経緯により、エイリアス宣言による型名の別名のことを、\texttt{typedef名}(typedef name)\index{かためい@型名!typedef@\texttt{typedef}名}\index{typedef@\texttt{typedef}名}という。これは\texttt{typedef}名を宣言する文法が、かつては\texttt{typedef}キーワードを使ったものだったからだ。\texttt{typedef}キーワードを使った\texttt{typedef}名の宣言方法は、昔のコードによく出てくるので現代でも覚えておく必要はある。 + +\begin{lstlisting}[style=grammar] +typedef 型名 typedef名 ; +\end{lstlisting} + +使い方。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // typedef名による型名の宣言 + typedef int Number ; + + Number x = 0 ; +} +\end{lstlisting} + +これは変数の宣言と同じ文法だ。変数の宣言が以下のような文法で、 +\begin{lstlisting}[style=grammar] +型名 変数名 ; +\end{lstlisting} +これに\texttt{typedef}キーワードを使えば\texttt{typedef}名の宣言になる。 + +しかし\texttt{typedef}キーワードによる\texttt{typedef}名の宣言はわなが多い。例えば熟練のC++プログラマーでも、以下のコードが合法だということに驚くだろう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int typedef Number ; + Number x = 0 ; +} +\end{lstlisting} + +しかし本書ではまだ教えていない複雑な型名について、このようなコードを書こうとするとコンパイルエラーになることに熟練のC++プログラマーは気が付くはずだ。その理由はとても難しい。 + +エイリアス宣言にはこのようなわなはない。 + +\hypersection{ch1306}{スコープ} + +スコープ(scope)\index{すこぷ@スコープ}というのはやや説明が難しい概念だ。名前空間や関数はスコープを持っている。とてもおおざっぱに説明するとカーリブラケット\,\texttt{\{\}}\,\index{\{\}@\texttt{\{\}}}で囲まれた範囲がスコープだ。 + +\begin{lstlisting}[language={C++}] +namespace ns +{ // 名前空間スコープの始まり +} // 名前空間スコープの終わり + +void f() +{ // 関数スコープの始まり + +} // 関数スコープの終わり +\end{lstlisting} + +これとは別にブロック文のスコープもある。ブロックとは関数の中で複数の文を束ねて1つの文として扱う機能だ。覚えているだろうか。 +\index{ぶろつくすこぷ@ブロックスコープ} + +\begin{lstlisting}[language={C++}] +void f() +{ // 関数スコープ + + { // 外側のブロックスコープ + { // 内側のブロックスコープ + } + } +} +\end{lstlisting} + +スコープは\,\texttt{\{}\,に始まり\,\texttt{\}}\,に終わる。 + +なぜスコープという概念について説明したかというと、宣言された名前が有効な範囲は、宣言された最も内側のスコープの範囲だからだ。 + +\begin{lstlisting}[language={C++}] +namespace ns +{// aの所属するスコープ + int a {} ; + + void f() + { // bの所属するスコープ + int b {} ; + + { // cの所属するスコープ + int c {} ; + }// cの範囲終わり + + + }// bの範囲終わり + +} // aの範囲終わり +\end{lstlisting} + +名前が有効な範囲は、宣言された最も内側のスコープだ。 + +外側のスコープで宣言された名前は内側のスコープで使える。 + +\begin{lstlisting}[language={C++}] +void f() +{ + int a {} ; + {// 新たなスコープ + a = 0 ; + } +} +\end{lstlisting} + +その逆はできない。 + +\begin{lstlisting}[language={C++}] +void f() +{ + { int a {} ; } + // エラー + a = 0 ; +} +\end{lstlisting} + +名前空間も同じだ。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[language={C++}] +// グローバル名前空間スコープ + +namespace ns { + int a {} ; + void f() + { + a = 0 ; // OK + } +} // 名前空間nsのスコープの終了 + +int main() +{ + // エラー + a = 0 ; + // OK + ns::a ; +} +\end{lstlisting} + +名前空間スコープと関数スコープには違う点もあるが、名前の有効な範囲としては同じスコープだ。 + +外側のスコープで宣言された名前と同じ名前を内側で宣言すると、内側の名前が外側の名前を隠す。 + +\begin{lstlisting}[language={C++}] +// グローバル名前空間のf +auto f = []() +{ std::cout << 1 ; } ; + +int main() +{ + f() ; // 1 + + // 関数mainのf + auto f = []() + { std::cout << 2 ; } ; + + f() ; // 2 + + { + f() ; // 2 + + // ブロックのf + auto f = []() + { std::cout << 3 ; } ; + f() ; // 3 + } + + f() ; // 2 +} +\end{lstlisting} + +宣言されている場所に注意が必要だ。名前\texttt{f}は3つある。最初の関数呼び出しの時点ではグローバル名前空間の\texttt{f}が呼ばれる。まだ名前\texttt{f}は関数\texttt{main}の中で宣言されていないからだ。そして関数\texttt{main}のスコープの中で名前\texttt{f}が宣言される。このときグローバル名前空間の\texttt{f}は隠される。そのため、次の関数\texttt{f}の呼び出しでは関数\texttt{main}の\texttt{f}が呼ばれる。次にブロックの中に入る。ここで関数\texttt{f}が呼ばれるが、まだこの\texttt{f}は関数\texttt{main}の\texttt{f}だ。そのあとにブロックの中で名前\texttt{f}が宣言される。すると次の関数\texttt{f}の呼び出しはブロックの\texttt{f}だ。ブロックから抜けたあとの関数\texttt{f}の呼び出しは関数\texttt{main}の\texttt{f}だ。 + +この章では名前について解説した。名前は難しい。難しいが、プログラミングにおいては名前と向き合わなければならない。 diff --git a/TeX/014-iterator.tex b/TeX/014-iterator.tex new file mode 100644 index 0000000..6739a43 --- /dev/null +++ b/TeX/014-iterator.tex @@ -0,0 +1,534 @@ +\hyperchapter{ch14}{イテレーターの基礎}{イテレーターの基礎} + +\texttt{vector}の章では\texttt{vector}の要素にアクセスする方法としてメンバー関数\texttt{at(i)}を学んだ。\texttt{at(i)}は\texttt{i}番目の要素にアクセスできる。ただし最初の要素は0番目だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + int x = v.at(2) ; // 3 + v.at(2) = 0 ; + // vは{1,2,0,4,5} +} +\end{lstlisting} + +この章では\texttt{vector}\index{vector@\texttt{vector}}の要素にアクセスする方法としてイテレーター(iterator)\index{いてれた@イテレーター}を学ぶ。 + +\hypersection{ch1401}{イテレーターの取得方法} +\index{いてれた@イテレーター!しゅとく@取得} + +イテレーターは\texttt{std::begin(v)}\index{begin@\texttt{begin}}で取得する。\texttt{v}は\texttt{vector}の変数だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + auto i = std::begin(v) ; +} +\end{lstlisting} + +\hypersection{ch1402}{イテレーターの参照する要素に対する読み書き} +\index{いてれた@イテレーター!さんしよう@参照} + +イテレーターは\texttt{vector}の先頭の要素を指し示している。イテレーターの指し示す要素を参照するには\,\texttt{*}\,\index{\protect{*}@\texttt{\protect{*}}}\index{いてれた@イテレーター!*@\texttt{*}}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + auto i = std::begin(v) ; + + int x = *i ; // 1 + + *i = 0 ; + // vは{0,2,3,4,5} +} +\end{lstlisting} + +\texttt{*i}を読み込むと指し示す要素の値を読むことができ、\texttt{*i}に代入をすると指し示す要素の値を変えることができる。 + +\hypersection{ch1403}{イテレーターの参照する要素を変更} + +現在指している要素の次の要素を指すように変更するには\,\texttt{++}\,\index{\protect{++}@\texttt{\protect{++}}}\index{いてれた@イテレーター!++@\texttt{++}}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + auto i = std::begin(v) ; + + *i ; // 1 + ++i ; + *i ; // 2 + ++i ; + *i ; // 3 +} +\end{lstlisting} + +現在指している要素の前の要素を指すように変更するには\,\texttt{{-}{-}}\,\index{{-}{-}@\texttt{{-}{-}}}\index{いてれた@イテレーター!{-}{-}@\texttt{{-}{-}}}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + +(@\ifTombow\pagebreak\fi@) + auto i = std::begin(v) ; + + *i ; // 1 + ++i ; + *i ; // 2 + --i ; + *i ; // 1 +} +\end{lstlisting} + +\texttt{vector}の全要素を先頭からイテレーターでアクセスするには、要素数だけ\,\texttt{++i}すればよいことになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + auto iter = std::begin(v) ; + + for ( std::size_t i = 0 ; i != std::size(v) ; ++i, ++iter ) + { + std::cout << *iter << "\n"s ; + } +} +\end{lstlisting} + +これは動く。ただしもっとマシな方法がある。イテレーターの比較だ。 + +\hypersection{ch1404}{イテレーターの比較} +\index{いてれた@イテレーター!ひかく@比較} + +イテレーターは比較できる。同じ順番の要素を指すイテレーターは等しく、そうではないイテレーターは等しくない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + auto x = std::begin(v) ; + auto y = x ; + + // x, yは0番目の要素を指す + + bool b1 = (x == y) ; // true + bool b2 = (x != y) ; // false + + ++x ; // xは1番目の要素を指す。 + + bool b3 = (x == y) ; // false + bool b4 = (x != y) ; // true +} +\end{lstlisting} + +\hypersection{ch1405}{最後の次の要素へのイテレーター} + +\texttt{std::begin(v)}\index{begin@\texttt{begin}}は\texttt{vector}の変数\texttt{v}の最初の要素を指し示すイテレーターを取得する。 + +\texttt{std::end(v)}\index{end@\texttt{end}}は\texttt{vector}の変数\texttt{v}の最後の次の要素を指し示すイテレーターを取得する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = { 1,2,3,4,5 }; + + // 最後の次の要素を指し示すイテレーター + auto i = std::end(v) ; +} +\end{lstlisting} + +最後の次の要素とは何か。ある\texttt{vector}\,の変数の中身が\,\texttt{\{1,2,3,4,5\}}\,のとき、最初の0番目の要素の値は\texttt{1}だ。最後の4番目の要素の値は\texttt{5}だ。最後の次の要素とは、値が\texttt{5}の最後の要素の次の要素だ。そのような要素は実際には存在しないが、\texttt{std::end}は概念として最後の次の要素を返す。 + +最後の次の要素を指し示すイテレーターに対して、\texttt{*}\,で要素にアクセスを試みるとエラーになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + auto i = std::end(v) ; + + *i ; // エラー +} +\end{lstlisting} + +最後の次の要素を\,\texttt{++}\,しようとするとエラーになる。\texttt{{-}{-}}\,することはできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + auto i = std::end(v) ; + + --i ; // 最後の要素を指す + *i ; // 5 + ++i ; // 最後の次の要素を指す + *i ; // エラー +} +\end{lstlisting} + +実際には存在しない最後の次の要素を指し示すイテレーターは何の役に立つのか。答えはイテレーターの比較だ。 + +実際には存在しない最後の次の要素を指すイテレーターに\,\texttt{'*'}\,を使って要素にアクセスするのはエラーだが、イテレーター同士の比較はできる。すでに説明したように、イテレーターの比較は同じ要素を指す場合は\texttt{true}、違う要素を指す場合は\texttt{false}になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3} ; + + // xは最初の要素を指す + auto x = std::begin(v) ; + // yは最後の次の要素を指す + auto y = std::end(v) ; + + + x == y ; // false + ++x ; // xは最初の次の要素を指す + x == y ; // false + ++x ; // xは最後の要素を指す + x == y ; // false + ++x ; // xは最後の次の要素を指す + x == y ; // true +} +\end{lstlisting} + +\texttt{std::end}で取得する最後の次の要素を指すイテレーターと比較することで、イテレーターが最後の次の要素を指し示す状態に到達したことを判定できる。 + +ということは、\texttt{vector}の要素を先頭から最後まで順番に出力するプログラムは、以下のように書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + for ( auto iter = std::begin(v), last = std::end(v) ; + iter != last ; ++iter ) + { + std::cout << *iter << "\n"s ; + } +} +\end{lstlisting} + +\hypersection{ch1406}{なんでもイテレーター} + +イテレーターというのは要素にアクセスする回りくどくて面倒な方法に見える。イテレーターという面倒なものを使わずに、\texttt{vector::at(i)}で\texttt{i}番目の要素にアクセスする方が楽ではないか。そう考える読者もいるだろう。イテレーターの利点はその汎用性\index{いてれた@イテレーター!はんようせい@汎用性}にある。イテレーターの作法に従うことで、さまざまな処理が同じコードで書けるようになるのだ。 + +たとえば、\texttt{vector}の要素を先頭から順番に出力する処理を振り返ってみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + for ( std::size_t i = 0 ; i != std::size(v) ; ++i ) + { + std::cout << v.at(i) << "\n"s ; + } +} +\end{lstlisting} + +このコードは\texttt{vector}にしか使えないコードだ。イテレーターで書き直してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + for ( auto iter = std::begin(v), last = std::end(v) ; + iter != last ; ++iter ) + { + std::cout << *iter << "\n"s ; + } +} +\end{lstlisting} + +そして、この要素を先頭から出力する処理を関数にしてみよう。 + +\begin{lstlisting}[language={C++}] +auto output_all = []( auto first, auto last ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { + std::cout << *iter << "\n"s ; + } +} ; + +(@\ifTombow\pagebreak\fi@) +int main() +{ + std::vector v = {1,2,3,4,5} ; + + output_all( std::begin(v), std::end(v) ) ; +} +\end{lstlisting} + +この\texttt{関数output\_all}は\texttt{vector}以外のイテレーターにも対応している。C++にはさまざまなイテレーターがある。例えば標準入力から値を受け取るイテレーターがある。さっそく使ってみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::istream_iterator first( std::cin ), last ; + + output_all( first, last ) ; +} +\end{lstlisting} + +このプログラムは標準入力から\texttt{int}型の値を受け取り、それをそのまま標準出力する。 + +C++にはほかにも、カレントディレクトリーにあるファイルの一覧を取得するイテレーターがある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::filesystem::directory_iterator first("./"), last ; + + output_all( first, last ) ; +} +\end{lstlisting} + +\texttt{関数output\_all}のコードは何も変えていないのに、さまざまなイテレーターに対応できる。イテレーターというお作法にのっとることで、さまざまな処理が可能になるのだ。 + +これは出力にも言えることだ。\texttt{関数output\_all}は\texttt{std::cout}に出力していた。これをイテレーターに対する書き込みに変えてみよう。 + +\begin{lstlisting}[language={C++}] +auto output_all = []( auto first, auto last, auto output_iter ) +{ + for ( auto iter = first ; iter != last ; ++iter, ++output_iter ) + { + *output_iter = *iter ; + } +} ; +\end{lstlisting} + +書き換えた\texttt{関数output\_all}は新しく\texttt{output\_iter}という引数を取る。これはイテレーターだ。\texttt{std::cout}に出力する代わりに、このイテレーターに書き込むように変更している。 + +こうすることによって、出力にもさまざまなイテレーターが使える。 + +標準出力に出力するイテレーターがある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + output_all( std::begin(v), std::end(v), + std::ostream_iterator(std::cout) ) ; +} +\end{lstlisting} + +\texttt{vector}も出力先にできる。つまり\texttt{vector}のコピーだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector source = {1,2,3,4,5} ; + std::vector destination(5) ; + + output_all( std::begin(source), std::end(source), std::begin( destination ) ) ; +} +\end{lstlisting} + +\texttt{destination(5)}というのは、\texttt{vector}にあらかじめ5個の要素を入れておくという意味だ。あらかじめ入っている要素の値は\texttt{int}の場合ゼロになる。 + +このほかにもイテレーターはさまざまある。自分でイテレーターを作ることもできる。そして、\texttt{関数output\_all}はイテレーターにさえ対応していればさまざまな処理にコードを1行たりとも変えずに使えるのだ。 + +\hypersection{ch1407}{イテレーターと添字の範囲} +\index{いてれた@イテレーター!はんい@範囲} + +イテレーターは順序のある値の集合を表現するために、最初の要素への参照と、最後の次の要素への参照のペアを用いる。 + +たとえば、\texttt{\{1,2,3,4,5\}}\,という順序の値の集合があった場合、イテレーターは最初の要素である\texttt{1}と最後の1つ次の要素である5の次の架空の要素を指し示す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + auto i = std::begin(v) ; + auto j = std::end(v) ; +} +\end{lstlisting} + +このようにして範囲を表現することを、\texttt{半閉鎖(half-closed)}\index{はんへいさ@半閉鎖}とか、\texttt{[i,j)}などと表現する。 + +この状態から\,\texttt{\{2,3,4,5\}}\,のような値の集合を表現したい場合、イテレーター\texttt{i}をインクリメントすればよい。 + +\begin{lstlisting}[language={C++}] +++i ; +\end{lstlisting} + +これで\texttt{[i,j)}は\,\texttt{\{2,3,4,5\}}\,になった。 + +このような範囲の表現方法に疑問を感じる読者もいるだろう。なぜ最後の次の要素という本来存在しない架空の要素をあたかも参照しているかのようなイテレーターが必要なのか。最後の要素を参照するのではだめなのか。 + +そのような範囲の表現方法は、\texttt{閉鎖(closed)}\index{へいさ@閉鎖}とか\texttt{[i,j]}などと表現する。 + +実はこの方法は\texttt{vector}の要素の順番を指定する方法と同じなのだ。 + +\texttt{\{1,2,3,4,5\}}\,と5個の順序ある要素からなる\texttt{vector}では、最初の要素は0番目で、最後の要素は4番目だ。1番目から5番目ではない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + v.at(0) ; // 最初の要素: 1 + v.at(4) ; // 最後の要素: 5 +} +\end{lstlisting} + +ではなぜなのか。なぜ\texttt{vector}では\(n\)個の要素の順番を0番目から\(n-1\)番目として表現するのか。 + +実はC++に限らず、現在使われているすべてのプログラミングはインデックスを\texttt{0}から始めている。かつてはインデックスを\texttt{1}から始める言語も存在したが、そのような言語はいまは使われていない。 + +この疑問はエドガー・ダイクストラ(Edsger Wybe Dijkstra)\index{Dijkstra, Edsger Wybe}が``Why numbering should start at zero''(EWD831)で解説している。 + +2, 3, \ldots, 12の範囲の自然数を表現するのに、慣習的に以下の4つの表記がある。 +\begin{gather*} +\textrm{a)}\quad 2 \le i < 13\\ +\textrm{b)}\quad 1 < i \le 12\\ +\textrm{c)}\quad 2 \le i \le 12\\ +\textrm{d)}\quad 1 < i < 13 +\end{gather*} + +C++のイテレーターはa)を元にしている。 + +この4つのうち、a)とb)は上限から下限を引くと、範囲にある自然数の個数である11になる。 + +この性質はとても便利なのでC++でも、イテレーター同士の引き算ができるようになっている。イテレーター\texttt{i}, \texttt{j}\,(\(i \le j\))\,で\texttt{j - i}をした結果はイテレーターの範囲の要素の個数だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {2,3,4,5,6,7,8,9,10,11,12} ; + + auto i = std::begin(v) ; + auto j = std::end(v) ; + + // 11 + // イテレーターの範囲の要素の個数 + std::cout << j - i << "\n"s ; + + ++i ; // 先頭の次の要素を指す + // 10 + std::cout << j - i ; +} +\end{lstlisting} + +a)とb)はどちらがいいのだろうか。b)を元にイテレーターを設計すると以下のようになる。 + +\begin{lstlisting}[language={C++}] +// b)案を採用する場合 +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 最初の1つ前の架空の要素を指す + auto i = std::begin(v) ; + // 最後の要素を指す + auto j = std::end(v) ; + + // 最初の要素を指すようにする。 + ++i ; + + // iが最後の要素を指すとループを抜ける + for ( ; i != j ; ++i ) + { + std::cout << *i ; + } + // 最後の要素を処理する + std::cout << *i ; + + +} +\end{lstlisting} + +a)の方がよい。 + +\begin{lstlisting}[language={C++}] +// a)案を採用する場合 +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 最初の要素を指す + auto i = std::begin(v) ; +(@\ifTombow\pagebreak\fi@) + // 最後の次の要素を指す + auto j = std::end(v) ; + + // iが最後の次の要素を指すとループを抜ける + for ( ; i != j ; ++i ) + { + std::cout << *i ; + } + + // すべての要素について処理を終えている +} +\end{lstlisting} + +b)案では末尾から先頭まで後ろ向きに要素を一巡する操作はやりやすいが、実際には先頭から末尾まで一巡する操作の方が多い。 + +C++では要素の順番を数値で指し示すとき、最初の要素は0番目であり、次の要素は1番目であり、\(N\)個目の要素は\(N-1\)番目になっている。この数値で指し示すことを\texttt{添字}\index{そえじ@添字}とか\texttt{インデックス}\index{いんでつくす@インデックス}というがなぜ最初の要素を1番目にしないのか。 + +C++ではさまざまなところでa)を採用している。これを添字に適用すると、最初の要素が1番目から始まる場合、\(N\)個の要素を参照する添字の範囲は\(1 \le i < N+1\)になる。そのような場合、以下のようなコードになる。 + +\begin{lstlisting}[language={C++}] +// 最初の要素が1番目の場合 +int main() +{ + // 5個の要素を持つvector + std::vector v = {1,2,3,4,5} ; + + // iの値の範囲は1から5まで + for ( std::size_t i = 1 ; i < 6 ; ++i ) + { + std::cout << v.at(i) ; + } +} +\end{lstlisting} + +要素数は5個なのに\texttt{6}が出てくる。最初の要素が0番目の場合、\(N\)個の要素を参照する添字の範囲は\(0 \le i < N\)になる。 + +\begin{lstlisting}[language={C++}] +// 最初の要素が0番目の場合 +int main() +{ + // 5個の要素を持つvector + std::vector v = {1,2,3,4,5} ; + + // iの値の範囲は0から5まで + for ( std::size_t i = 0 ; i < 5 ; ++i ) + { + std::cout << v.at(i) ; + } +} +\end{lstlisting} + +一貫性のために最初の要素は0番目となっている。 + +また、空の集合にも対応できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 空 + std::vector v ; + + // 空なので何も出力されない + for ( auto i = std::begin(v), j = std::end(v) ; + i != j ; ++i ) + { + std::cout << *i ; + } +} +\end{lstlisting} + +変数\texttt{v}は空なので\texttt{i != j}は\texttt{false}となり、\texttt{for}文の中の文は一度も実行されない。 diff --git a/TeX/015-reference.tex b/TeX/015-reference.tex new file mode 100644 index 0000000..3204141 --- /dev/null +++ b/TeX/015-reference.tex @@ -0,0 +1,307 @@ +\hyperchapter{ch15}{lvalueリファレンスとconst}{lvalueリファレンスとconst} + +\begin{quote} +\begin{flushright} +ポップカルチャーリファレンスというのは\\ +流行の要素をさり気なく作品中に取り入れることで、\\ +流行作品を知っている読者の笑いを誘う手法である\\ +--- キャプテン・オブビウス、ポップカルチャーリファレンスについて +\end{flushright} +\end{quote} + +\hypersection{ch1501}{lvalueリファレンス} +\index{lvalue@\texttt{lvalue}リファレンス} + +変数に変数を代入すると、代入元の値が代入先にコピーされる。代入先の値を変更しても、コピーされた値が変わるだけで、代入元にはいっさい影響がない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; + int b = 2 ; + + b = a ; + // b == 1 + + b = 3 ; + // a == 1 + // b == 3 +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +これは関数も同じだ。 + +\begin{lstlisting}[language={C++}] +void assign_3( int x ) +{ + x = 3 ; +} + +int main() +{ + int a = 1 ; + assign_3( a ) ; + + // a == 1 +} +\end{lstlisting} + +しかし、ときには変数の値を直接書き換えたい場合がある。このとき\texttt{lvalue}リファレンス(reference)が使える。\texttt{lvalue}リファレンスは変数に\,\texttt{\&}\,\index{\&@\texttt{\&}}を付けて宣言する + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; + int & ref = a ; + + ref = 3 ; + + // a == 3 + // refはaなので同じく3 +} +\end{lstlisting} + +この例で、変数\texttt{ref}は変数\texttt{a}への参照(リファレンス)\index{さんしよう@参照}\index{りふあれんす@リファレンス}なので、変数\texttt{a}と同じように使える。 + +\texttt{lvalue}リファレンスは必ず初期化しなければならない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // エラー + int & ref ; +} +\end{lstlisting} + +\texttt{lvalue}リファレンスは関数でも使える。 + +\begin{lstlisting}[language={C++}] +void f( int & x ) +{ + x = 3 ; +} + +int main() +{ + int a = 1 ; + f( a ) ; + + // a == 3 +} +\end{lstlisting} + +選択ソートで2つの変数の値を交換する必要があったことを覚えているだろうか。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {3,2,1,4,5} ; + + // 0番目と2番目の要素を交換したい + auto temp = v.at(0) ; + v.at(0) = v.at(2) ; + v.at(2) = temp ; +} +\end{lstlisting} + +いちいち交換のために別の変数\texttt{temp}を作って3回代入を書くのは面倒だ。これを関数にしてしまいたい。 + +\begin{lstlisting}[language={C++}] +// 値を交換 +swap( v.at(0), v.at(2) ) ; +\end{lstlisting} + +このような関数\texttt{swap}は普通に書くことはできない。 + +\begin{lstlisting}[language={C++}] +// この実装は正しくない +auto swap = []( auto a, auto b ) +{ + auto temp = a ; + a = b ; + b = temp ; +} ; +\end{lstlisting} + +この実装では、変数は単にコピーされるだけなので、関数の呼び出し元には何の影響もない。 + +これを\texttt{lvalue}リファレンスに変えると、関数の呼び出し元の変数の値を交換する関数\texttt{swap}が作れる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// lvalueリファレンス +auto swap = []( auto & a, auto & b ) +{ + auto temp = a ; + a = b ; + b = temp ; +} ; +\end{lstlisting} + +C++の標準ライブラリには\texttt{std::swap}\index{swap@\texttt{swap}}があるので、読者はわざわざこのような関数を自作する必要はない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; + int b = 2 ; + + std::swap( a, b ) ; + + // a == 2 + // b == 1 +} +\end{lstlisting} + +ところで、この章では一貫して\texttt{lvalue}リファレンスと書いているのに気が付いただろうか。\texttt{lvalue}とは何なのか、\texttt{lvalue}ではないリファレンスはあるのか。その疑問はあとの章で解決する。 + +\hypersection{ch1502}{const} +\index{const@\texttt{const}} + +値を変更したくない変数は、\texttt{const}を付けることで変更を禁止できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; + x = 1 ; // OK、変更できる + + const int y = 0 ; + y = 0 ; // エラー、 変更できない。 +} +\end{lstlisting} + +\texttt{const}はちょっと文法が変わっていて混乱する。例えば、\texttt{const int}でも\texttt{int const}でも意味が同じだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + // 意味は同じ + const int x = 0 ; + int const y = 0 ; +} +\end{lstlisting} + +\texttt{const}は\texttt{lvalue}リファレンスと組み合わせることができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; + + int & ref = x ; + // OK + ++ref ; + + const int & const_ref = x ; + + // エラー + ++const_ref ; +} +\end{lstlisting} + +\texttt{const}は本当に文法が変わっていて混乱する。\texttt{const int \&}と\texttt{int const \&}は同じ意味だが、\texttt{int \& const}はエラーになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 0 ; + + // OK、意味は同じ + const int & b = a ; + int const & c = a ; + + // エラー + int & const d = a ; +} +\end{lstlisting} + +これはとても複雑なルールで決まっているので、こういうものだとあきらめて覚えるしかない。 + +\texttt{const}が付いていない型のオブジェクトを\texttt{const}な\texttt{lvalue}リファレンスで参照することができる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + // constの付いていない型のオブジェクト + int x = 0 ; + + // OK + int & ref = x ; + // OK、constは付けてもよい + const int & cref = x ; +} +\end{lstlisting} + +\texttt{const}の付いている型のオブジェクトを\texttt{const}の付いていない\texttt{lvalue}リファレンスで参照することはできない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // constの付いている型のオブジェクト + const int x = 0 ; + + // エラー、 constがない + int & ref = x ; + + // OK、constが付いている + const int & cref = x ; +} +\end{lstlisting} + +\texttt{const}の付いている\texttt{lvalue}リファレンスは何の役に立つのかというと、関数の引数を取るときに役に立つ。 + +例えば以下のコードは非効率的だ。 + +\begin{lstlisting}[language={C++}] +void f( std::vector v ) +{ + std::cout << v.at(1234) ; +} + +int main() +{ + // 10000個の要素を持つvector + std::vector v(10000) ; + + f( v ) ; +} +\end{lstlisting} + +なぜかというと、関数の引数に渡すときに、変数\texttt{v}はコピーされるからだ。 + +リファレンスを使うと不要なコピーをしなくて済む。 + +\begin{lstlisting}[language={C++}] +void f( std::vector & v ) +{ + std::cout << v.at(1234) ; +} +\end{lstlisting} + +しかし、リファレンスで受け取ると、うっかり変数を変更してしまった場合、その変更が関数の呼び出し元に反映されてしまう。 + +\begin{lstlisting}[language={C++}] +// 値を変更するかもしれない +void f( std::vector & v ) ; + +int main() +{ + // 要素数10000のvector + std::vector v(10000) ; + + f(v) ; + + // 値は変更されているかもしれない +} +\end{lstlisting} + +このとき、\texttt{const}な\texttt{lvalue}リファレンスを使うと、引数に取った値を変更しないことを保証できる。 + +\begin{lstlisting}[language={C++}] +void f( std::vector const & v ) ; +\end{lstlisting} + diff --git a/TeX/016-algorithm.tex b/TeX/016-algorithm.tex new file mode 100644 index 0000000..5098b0b --- /dev/null +++ b/TeX/016-algorithm.tex @@ -0,0 +1,1204 @@ +\hyperchapter{ch16}{アルゴリズム}{アルゴリズム} + +アルゴリズム\index{あるごりずむ@アルゴリズム}は難しい。アルゴリズム自体の難しさに加え、アルゴリズムを正しくコードで表記するのも難しい。そこでC++ではアルゴリズム自体をライブラリにしている。ライブラリとしてのアルゴリズムを使うことで、読者はアルゴリズムを自前で実装することなく、すでに正しく実装されたアルゴリズムを使うことができる。 + +\hypersection{ch1601}{for\texttt{\_}each} +\index{for\_each@\texttt{for\_each}} + +例えば\texttt{vector}の要素を先頭から順番に標準出力するコードを考えよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + for ( + auto i = std::begin(v), + j = std::end(v) ; + i != j ; + ++i ) + { + std::cout << *i ; + } +} +\end{lstlisting} + +このコードを書くのは難しい。このコードを書くには、イテレーターで要素の範囲を取り、ループを実行するごとにイテレーターを適切にインクリメントし、イテレーターが範囲内であるかどうかの判定をしなければならない。 + +アルゴリズムを理解するだけでも難しいのに、正しくコード書くのはさらに難しい。例えば以下はコンパイルが通る完全に合法なC++だが間違っている。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + for ( + auto i = std::begin(v), + j = std::end(v) ; + i == j ; + ++i ) + { + std::cout << i ; + } +} +\end{lstlisting} + +間違っている箇所がわかるだろうか。 + +まず比較の条件が間違っている。\texttt{i != j}となるべきところが\texttt{i == j}となっている。出力する部分も間違っている。イテレーター\texttt{i}が指し示す値を得るには\,\texttt{*i}\,としなければならないところ、単に\texttt{i}としている。 + +毎回このようなイテレーターのループをする\texttt{for}文を書くのは間違いの元だ。ここで重要なのは、要素のそれぞれに対して\texttt{std::cout {<}{<} *i ;}を実行するということだ。要素を先頭から末尾まで順番に処理するというのはライブラリにやってもらいたい。 + +そこでこの処理を関数に切り出してみよう。イテレーター\texttt{[first,last)}を渡すと、イテレーターを先頭から末尾まで順番に処理してくれる関数は以下のように書ける。 + +\begin{lstlisting}[language={C++}] +auto print_all = []( auto first, auto last ) +{ + // ループ + for ( auto iter = first ; iter != last ; ++iter ) + { + // 重要な処理 + std::cout << *iter ; + } +} ; + +int main() +{ + std::vector v = {1,2,3,4,5} ; + + print_all( std::begin(v), std::end(v) ) ; +} +\end{lstlisting} + +関数\texttt{print\_all}は便利だが、重要な処理がハードコードされている。例えば要素の集合のうち100以下の値だけ出力したいとか、値を2倍して出力したいとか、値を出力するたびに改行を出力したいという場合、それぞれに関数を書く必要がある。 + +\begin{lstlisting}[language={C++}] +// 値が100以下なら出力 +auto print_if_le_100 = []( auto first, auto last ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { // 特別な処理 + if ( *iter <= 100 ) + std::cout << *iter ; + } +} ; + +// 値を2倍して出力 +auto print_twice = []( auto first, auto last ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { // 特別な処理 + std::cout << 2 * (*iter) ; + } +} ; + +// 値を出力するたびに改行を出力 +auto print_with_newline = []( auto first, auto last ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { // 特別な処理 + std::cout << *iter << "\n"s ; + } +} ; +\end{lstlisting} + +これを見ると、\texttt{for}文によるイテレーターのループはまったく同じコードだとわかる。 + +まったく同じ\texttt{for}文を手で書くのは間違いの元だ。同じコードはできれば書きたくない。ここで必要なのは、共通な処理は一度書くだけで済ませ、特別な処理だけを記述すれば済むような方法だ。 + +この問題を解決するには、問題を分割することだ。問題を「\texttt{for}文によるループ」と「特別な処理」に分けることだ。 + +ところで、関数は変数に代入できる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + // 変数に代入された関数 + auto print = []( auto x ) { std::cout << x ; } ; + + // 変数に代入された関数の呼び出し + print(123) ; +} +\end{lstlisting} + +変数に代入できるということは、関数の引数として関数に渡せるということだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 関数を引数に取り呼び出す関数 + auto call_func = []( auto func ) + { + func(123) ; + } ; + + // 引数を出力する関数 + auto print = []( auto x ) { std::cout << x ; } ; + + call_func( print ) ; + + // 引数を2倍して出力する関数 + auto print_twice = []( auto x ) { std::cout << 2*x ; } ; + + call_func( print_twice ) ; +} +\end{lstlisting} + +すると、要素ごとの特別な処理をする関数を引数で受け取り、要素ごとに関数を適用する関数を書くとどうなるのか。 + +\begin{lstlisting}[language={C++}] +auto for_each = []( auto first, auto last, auto f ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { + f( *iter ) ; + } +} ; +\end{lstlisting} + +この関数はイテレーターをループで回す部分だけを実装していて、要素ごとの処理は引数に取った関数に任せている。さっそく使ってみよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 引数を出力する関数 + auto print_value = []( auto value ) { std::cout << value ; } ; + + for_each( std::begin(v), std::end(v), print_value ) ; + + // 引数を2倍して出力する関数 + auto print_twice = []( auto value ) { std::cout << 2 * value ; } ; + + for_each( std::begin(v), std::end(v), print_twice ) ; + + // 引数を出力したあとに改行を出力する関数 + auto print_with_newline = []( auto value ) { std::cout << value << "\n"s ; } ; + + for_each( std::begin(v), std::end(v), print_with_newline ) ; +} +\end{lstlisting} + +関数は変数に代入しなくても使えるので、上のコードは以下のようにも書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 引数を出力する + for_each( std::begin(v), std::end(v), + []( auto value ) { std::cout << value ; } ) ; + + // 引数を2倍して出力する + for_each( std::begin(v), std::end(v), + []( auto value ) { std::cout << 2 * value ; } ) ; + + // 引数を出力したあとに改行を出力する関数 + for_each( std::begin(v), std::end(v), + []( auto value ) { std::cout << value << "\n"s ; } ) ; +} +\end{lstlisting} + +わざわざ\texttt{for}文を書かずに、問題の本質的な処理だけを書くことができるようになった。 + +このイテレーターを先頭から末尾までループで回し、要素ごとに関数を呼び出すという処理はとても便利なので、標準ライブラリには\texttt{std::for\_each( first, last, f)}\index{for\_each@\texttt{for\_each}}がある。使い方は同じだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + std::for_each( std::begin(v), std::end(v), + []( auto value ) { std::cout << value ; } ) ; +} +\end{lstlisting} + +C++17の時点ではまだ使えないが、将来のC++では、イテレーターを渡さずに、\texttt{vector}を直接渡すことができるようになる予定だ。 + +\begin{lstlisting}[language={C++}] +// C++20予定 + +int main() +{ + std::vector v = {1,2,3,4,5} ; + + std::for_each( v, []( auto value ) { std::cout << value ; } ) ; +} +\end{lstlisting} + +ところでもう一度\texttt{for\_each}の実装を見てみよう。 + +\begin{lstlisting}[language={C++}] +auto for_each = []( auto first, auto last, auto f ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { + f( *iter ) ; + } +} ; +\end{lstlisting} + +\texttt{f(*iter)}がとても興味深い。もし関数\texttt{f}がリファレンスを引数に取っていたらどうなるだろうか。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 引数をリファレンスで取って2倍にする関数 + auto twice = [](auto & value){ value = 2 * value ; } ; + + std::for_each( std::begin(v), std::end(v), twice ) ; + + // 引数を出力する関数 + auto print = [](auto & value){ std::cout << value << ", "s ; } ; + +(@\ifTombow\pagebreak\fi@) + // 2, 4, 6, 8, 10, + std::for_each( std::begin(v), std::end(v), print ) ; +} +\end{lstlisting} + +元の\texttt{vector}を書き換えることもできる。 + +\hypersection{ch1602}{all\texttt{\_}of/any\texttt{\_}of/none\texttt{\_}of} +\index{all\_of@\texttt{all\_of}}\index{any\_of@\texttt{any\_of}}\index{none\_of@\texttt{none\_of}} + +ほかのアルゴリズムも実装していくことで学んでいこう。 + +\texttt{all\_of(first, last, pred)}は、\texttt{[first,last)}の間のイテレーター\texttt{iter}のそれぞれに対して、\texttt{pred(*iter)}がすべて\texttt{true}を返すならば\texttt{true}、そうではないならば\texttt{false}を返すアルゴリズムだ。 + +この\texttt{all\_of}は要素がすべて条件を満たすかどうかを調べるのに使える。 + +\begin{lstlisting}[language={C++}] +// 要素がすべて偶数かどうか調べる関数 +auto is_all_of_odd = []( auto first, auto last ) +{ + return std::all_of( first, last, + []( auto value ) { return value % 2 == 0 ; } ) ; +} ; + +// 要素がすべて100以下かどうか調べる関数 +auto is_all_of_le_100 = []( auto first, auto last ) +{ + return std::all_of( first, last, + []( auto value ) { return value <= 100; } ) ; +} ; +\end{lstlisting} + +ところで、もし要素がゼロ個の、つまり空のイテレーターを渡した場合どうなるのだろうか。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 空のvector + std::vector v ; + + bool b = std::all_of( std::begin(v), std::end(v), + // 特に意味のない関数 + [](auto value){ return false ; } ) ; +} +\end{lstlisting} + +この場合、\texttt{all\_of}は\texttt{true}を返す。 + +実装は以下のようになる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +auto all_of = []( auto first, auto last, auto pred ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { + if ( pred( *iter ) == false ) + return false ; + } + + return true ; +} ; +\end{lstlisting} + +\texttt{[first,last)}が空かどうかを確認する必要はない。というのも、空であればループは一度も実行されないからだ。 + +\texttt{any\_of(first, last, pred)}は\texttt{[first,last)}の間のイテレーター\texttt{iter}それぞれに対して、\texttt{pred(*iter)}が1つでも\texttt{true}ならば\texttt{true}を返す。空の場合、すべて\texttt{false}の場合は\texttt{false}を返す。 + +\texttt{any\_of}は要素に1つでも条件を満たすものがあるかどうかを調べるのに使える。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 要素に1つでも3が含まれているか? + // true + bool has_3 = std::any_of( std::begin(v), std::end(v), + []( auto x ) { return x == 3 ;} ) ; + + // 要素に1つでも10が含まれているか? + // false + bool has_10 = std::any_of( std::begin(v), std::end(v), + []( auto x ) { return x == 10 ;} ) ; +} +\end{lstlisting} + +これも実装してみよう。 + +\begin{lstlisting}[language={C++}] +auto any_of = []( auto first, auto last, auto pred ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { + if ( pred( *iter ) ) + return true ; + } + return false ; +} ; +\end{lstlisting} + +\texttt{none\_of(first, last, pred)}は\texttt{[first,last)}の間のイテレーター\texttt{iter}それぞれに対して、\texttt{pred(*iter)}がすべて\texttt{false}ならば\texttt{true}を返す。空の場合は\texttt{true}を返す。それ以外は\texttt{false}を返す。 + +\texttt{none\_of}はすべての要素が条件を満たさない判定に使える。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 値は100か? + auto is_100 = [](auto x){ return x == 100 ; } ; + + bool b = std::none_of( std::begin(v), std::end(v), is_100 ) ; +} +\end{lstlisting} + +これも実装してみよう。 + +\begin{lstlisting}[language={C++}] +auto none_of = []( auto first, auto last, auto pred ) +{ + for ( auto iter = first ; first != last ; ++iter ) + { + if ( pred(*iter) ) + return false ; + } + return true ; +} ; +\end{lstlisting} + +\hypersection{ch1603}{find/find\texttt{\_}if} +\index{find@\texttt{find}}\index{find\_if@\index{find\_if}} + +\texttt{find( first, last, value )}はイテレーター\texttt{[first,last)}から\texttt{value}に等しい値を見つけて、そのイテレーターを返すアルゴリズムだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 3を指すイテレーター + auto pos = std::find( std::begin(v), std::end(v), 3 ) ; + + std::cout << *pos ; +} +\end{lstlisting} + +要素が見つからない場合は\texttt{last}が返る。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + auto pos = std::find( std::begin(v), std::end(v), 0 ) ; + + if ( pos != std::end(v) ) + { + std::cout << "Found."s ; + } + else + { + std::cout << "Not found."s ; + } +} +\end{lstlisting} + +イテレーターが\texttt{last}かどうかは実際に\texttt{last}と比較すればよい。 + +アルゴリズムを理解するには、自分で実装してみるとよい。さっそく\texttt{find}を実装してみよう。 + +\begin{lstlisting}[language={C++}] +auto find = []( auto first, auto last, auto const & value ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { + // 値を発見したらそのイテレーターを返す + if ( *iter == value ) + return iter ; + } + // 値が見つからなければ最後のイテレーターを返す + return last ; +} ; +\end{lstlisting} + +\texttt{value}が\texttt{auto const \& value}になっているのは、リファレンスによってコピーを回避するためと、変更が必要ないためだ。しかし、\texttt{int}や\texttt{double}のような単純な型については、わざわざ\texttt{const}な\texttt{lvalue}リファレンスを使う必要はない。 + +\texttt{find\_if(first, last, pred)}はイテレーター\texttt{[first,last)}から、要素を関数\texttt{pred}に渡したときに\texttt{true}を返す要素へのイテレーターを探すアルゴリズムだ。 + +関数\texttt{pred}についてはもう少し解説が必要だ。\texttt{pred}\index{pred@\texttt{pred}}とは\texttt{predicate}の略で、以下のような形をしている。 + +\begin{lstlisting}[language={C++}] +auto pred = []( auto const & value ) -> bool +{ + return true ; +} ; +\end{lstlisting} + +関数\texttt{pred}は値を1つ引数に取り、\texttt{bool}型を返す関数だ。 + +さっそく使ってみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,3,5,7,9,11,13,14,15,16} ; + + // 偶数ならばtrueを返す + auto is_even = []( auto value ) + { + return value % 2 == 0 ; + } ; + // 奇数ならばtrueを返す + auto is_odd = []( auto value ) + { + return value % 2 == 1 ; + } ; + + // 最初の偶数の要素 + auto even = std::find_if( std::begin(v), std::end(v), is_even ) ; + // 最初の奇数の要素 + auto odd = std::find_if( std::begin(v), std::end(v), is_odd ) ; +} +\end{lstlisting} + +実装はどうなるだろうか。 + +\begin{lstlisting}[language={C++}] +auto find_if = []( auto first, auto last, auto pred ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { + // predがtrueを返した最初のイテレーターを返す + if ( pred( *iter ) ) + return iter ; + } + + return last ; +} ; +\end{lstlisting} + +値との比較が関数になっただけだ。 + +つまりある値と比較する関数を渡したならば、\texttt{find\_if}は\texttt{find}と同じ動きをするということだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 引数が3の場合にtrueを返す関数 + auto is_3 = []( auto x ) { return x == 3 ; } ; + + // 最初に関数がtrueを返す要素へのイテレーターを探すfind_if + auto i = std::find_if( std::begin(v), std::end(v), is_3 ) ; + + // 最初に3と等しい要素へのイテレーターを返すfind + auto j = std::find( std::begin(v), std::end(v), 3 ) ; + + // 同じイテレーター + bool b = (i == j) ; +} +\end{lstlisting} + +実は、関数は特別な\texttt{[=]}を使うことで、関数の外側の値をコピーして使うことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int value = 123 ; + + auto f = [=]{ return value ; } ; + + f() ; // 123 +} +\end{lstlisting} + +特別な\texttt{[\&]}を使うことで、関数の外側の値をリファレンスで使うことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int value = 123 ; + + auto f = [&]{ ++value ; } ; + + f() ; + std::cout << value ; // 124 +} +\end{lstlisting} + +ということは、\texttt{find}は\texttt{find\_if}で実装することもできるということだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +auto find = []( auto first, auto last, auto value ) +{ + return std::find_if( first, last, + [&]( auto elem ) { return value == elem ; } ) ; +} ; +\end{lstlisting} + +\hypersection{ch1604}{count/count\texttt{\_}if} +\index{count@\texttt{count}}\index{count\_if@\texttt{count\_if}} + +\texttt{count(first, last, value)}は\texttt{[first,last)}の範囲のイテレーター\texttt{i}から\,\texttt{*i == value}\,になるイテレーター\texttt{i}の数を数える。 + +\texttt{count}は指定した値と同じ要素の数を数える関数だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,1,1,3,3} ; + + // 3 + auto a = std::count( std::begin(v), std::end(v), 1 ) ; + // 1 + auto b = std::count( std::begin(v), std::end(v), 2 ) ; + // 2 + auto c = std::count( std::begin(v), std::end(v), 3 ) ; +} +\end{lstlisting} + +実装してみよう。 + +\begin{lstlisting}[language={C++}] +auto count = []( auto first, auto last, auto value ) +{ + auto counter = 0u ; + for ( auto i = first ; i != last ; ++i ) + { + if ( *i == value ) + ++counter ; + } + return counter ; +} ; +\end{lstlisting} + +\texttt{count\_if(first, last, pred)}は\texttt{[first, last)}の範囲のイテレーター\texttt{i}から\texttt{pred(*i) != false}になるイテレーター\texttt{i}の数を返す。 + +\texttt{count\_if}は要素を数える対象にするかどうかを判定する関数を渡せる\texttt{count}だ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,1,1,3,3} ; + + // 奇数の数: 5 + auto a = std::count_if( std::begin(v), std::end(v), + [](auto x){ return x%2 == 1 ; } ) ; + + // 偶数の数: 1 + auto b = std::count_if( std::begin(v), std::end(v), + [](auto x){ return x%2 == 0 ; } ) ; + + // 2以上の数: 3 + auto c = std::count_if( std::begin(v), std::end(v), + [](auto x){ return x >= 2 ; } ) ; +} +\end{lstlisting} + +実装してみよう。 + +\begin{lstlisting}[language={C++}] +auto count = []( auto first, auto last, auto pred ) +{ + auto counter = 0u ; + for ( auto i = first ; i != last ; ++i ) + { + if ( pred(*i) != false ) + ++counter ; + } + return counter ; +} ; +\end{lstlisting} + +\hypersection{ch1605}{equal} +\index{equal@\texttt{equal}} + +これまでのアルゴリズムは1つのイテレーターの範囲だけを扱ってきた。アルゴリズムの中には複数の範囲を取るものもある。 + +\texttt{equal(first1, last1, first2, last2)}は\texttt{[first1, last1)}と\texttt{[first2, last2)}が等しい場合に\texttt{true}を返す。「等しい」というのは、要素の数が同じで、各要素がそれぞれ等しい場合を指す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector a = {1,2,3,4,5} ; + // aと等しい + std::vector b = {1,2,3,4,5} ; + // aと等しくない + std::vector c = {1,2,3,4,5,6} ; + // aと等しくない + std::vector d = {1,2,2,4,6} ; + + // true + bool ab = std::equal( + std::begin(a), std::end(a), + std::begin(b), std::end(b) ) ; + + // false + bool ac = std::equal( + std::begin(a), std::end(a), + std::begin(c), std::end(c) ) ; + + // false + bool ad = std::equal( + std::begin(a), std::end(a), + std::begin(d), std::end(d) ) ; +} +\end{lstlisting} + +実装は、まず要素数を比較し、等しくなければ\texttt{false}を返す。次に各要素を1つずつ比較し、途中で等しくない要素が見つかれば\texttt{false}を、最後まで各要素が等しければ\texttt{true}を返す。 + +イテレーターの範囲\texttt{[first, last)}の要素数は\texttt{last-first}で取得できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 最初の要素 + auto first = std::begin(v) ; + // 最後の1つ次の要素 + auto last = std::end(v) ; + + // 要素数: 5 + auto size = last - first ; + + // 最初の次の要素 + auto next = first + 1 ; + + // 4 + auto size_from_next = last - next ; +} +\end{lstlisting} + +\texttt{last-first}という表記はわかりにくいので、C++には\texttt{distance(first, last)}というライブラリが用意されている。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +auto distance = []( auto first, auto last ) +{ + return last - first ; +} ; +\end{lstlisting} + +これを使えばわかりやすく書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // 最初の要素 + auto first = std::begin(v) ; + // 最後の1つ次の要素 + auto last = std::end(v) ; + + // 要素数: 5 + auto size = std::distance( first, last ) ; + + // 4 + auto size_from_next = std::distance( first + 1, last ) ; +} +\end{lstlisting} + +あとは実装するだけだ(この実装は最も効率のいい実装ではない。理由についてはイテレーターの章を参照)。 + +\begin{lstlisting}[language={C++}] +auto equal = []( auto first1, auto last1, auto first2, auto last2) +{ + // 要素数が等しいことを確認 + auto size1 = std::distance( first1, last1 ) ; + auto size2 = std::distance( first2, last2 ) ; + + if ( size1 != size2 ) + // 要素数が等しくなかった + return false ; + + // 各要素が等しいことを確認 + for ( auto i = first1, j = first2 ; + i != last1 ; ++i, ++j ) + { + if ( *i != *j ) + // 等しくない要素があった + return false ; + } +(@\ifTombow\pagebreak\fi@) + // 各要素がすべて等しかった + return true ; +} ; +\end{lstlisting} + +\texttt{for}文の終了条件では\texttt{i != last1}だけを見ていて、\texttt{j != last2}は見ていないが、これは問題がない。なぜならば、この\texttt{for}文が実行されるのは、要素数が等しい場合だけだからだ。 + +関数\texttt{pred}を取る\texttt{equal(first1, last1, first2, last2, pred)}もある。この\texttt{pred}は\texttt{pred(a, b)}で、\texttt{a}と\texttt{b}が等しい場合に\texttt{true}、そうでない場合に\texttt{false}を返す関数だ。つまり\texttt{a == b}の\texttt{operator ==}\,の代わりに使う関数を指定する。 + +\texttt{equal}に関数を渡すことにより、例えば小数点以下の値を誤差として切り捨てるような処理が書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1.3, 2.2, 3.0, 4.9, 5.7} ; + std::vector w = {1.9, 2.4, 3.8, 4.5, 5.0} ; + + // 小数点以下は誤差として切り捨てる比較 + auto comp = []( auto a, auto b ) + { + return std::floor(a) == std::floor(b) ; + } ; + + bool b = std::equal( + std::begin(v), std::end(v), + std::begin(w), std::end(w), + comp ) ; +} +\end{lstlisting} + +\texttt{std::floor(x)}は浮動小数点数\texttt{x}の小数点数以下を切り捨てた結果を返す関数だ。\texttt{floor(0.999)}は\texttt{0.0}に、\texttt{floor(1.999)}は\texttt{1.0}になる。 + +本書をここまで読んできた読者であれば実装は自力でできるだろう。 + +\hypersection{ch1606}{search} +\index{search@\texttt{search}} + +\texttt{search(first1, last1, first2, last2)}はイテレーター\texttt{[first2, last2)}の範囲で示された連続した要素の並びがイテレーター\texttt{[first1, last1)}の範囲に存在すれば\texttt{true}、そうでない場合は\texttt{false}を返す。 + +こう書くと難しいが、例を見るとわかりやすい。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v1 = {1,2,3,4,5,6,7,8,9} ; + std::vector v2 = {4,5,6} ; + + // true + bool a = std::search( std::begin(v1), std::end(v1), std::begin(v2), std::end(v2) ) ; + + std::vector v3 = {1,3,5} ; + // false + bool a = std::search( std::begin(v1), std::end(v1), std::begin(v3), std::end(v3) ) ; +} +\end{lstlisting} + +この例では、\texttt{v1}の中に\texttt{v2}と同じ並びの\,\texttt{\{4,5,6\}}\,が存在するので\texttt{true}、\texttt{v3}と同じ並びの\,\texttt{\{1,3,5\}}\,は存在しないので\texttt{false}になる。 + +\texttt{search}の実装例はいまの読者にはまだ理解できない。\texttt{equal}や\texttt{search}を効率的に実装するにはイテレーターの詳細な理解が必要だ。 + +\hypersection{ch1607}{copy} +\index{copy@\texttt{copy}} + +これまでのアルゴリズムは\texttt{for\_each}を除き要素の変更をしてこなかった。\texttt{copy}は要素の変更をするアルゴリズムだ。 + +イテレーター\texttt{i}は\,\texttt{*i}で参照する要素の値として使うことができるほか、\texttt{*i = x}で要素に値\texttt{x}を代入できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1} ; + + auto i = std::begin(v) ; + + // 参照する要素を値として使う + std::cout << *i ; + // 参照する要素に値を代入する。 + *i = 2 ; +} +\end{lstlisting} + +\texttt{copy(first, last, result)}はイテレーター\texttt{[first, last)}の範囲の値を、先頭から順番にイテレーター\texttt{result}に書き込んでいくアルゴリズムだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector source = {1,2,3,4,5} ; + // 要素数5のvector + std::vector destination(5) ; + + std::copy( std::begin(source), std::end(source), std::begin(destination) ) ; + + // destinationの中身は{1,2,3,4,5} +} +\end{lstlisting} + +これは実質的に以下のような操作をしたのと等しい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector source = {1,2,3,4,5} ; + std::vector destination(5) ; + // 要素をそれぞれコピー + destination[0] = source[0] ; + destination[1] = source[1] ; + destination[2] = source[2] ; + destination[3] = source[3] ; + destination[4] = source[4] ; + +} +\end{lstlisting} + +イテレーター\texttt{result}は先頭のイテレーターのみで末尾のイテレーターは渡さない。イテレーター\texttt{result}はイテレーター\texttt{[first, last)}の範囲の要素数をコピーできるだけの要素数の範囲を参照していなければならない。 + +例えば以下の例はエラーになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector source = {1,2,3,4,5} ; + // 要素数3のvector + std::vector destination(3) ; + + // エラー + std::copy( std::begin(source), std::end(source), std::begin(destination) ) ; +} +\end{lstlisting} + +要素数が3しかない\texttt{vector}に5個の要素をコピーしようとしている。 + +\texttt{copy}の戻り値は\texttt{[first,last)}の要素数だけ進めたイテレーター\texttt{result}になる。これはつまり、\texttt{result + (last - first)}だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector source = {1,2,3,4,5} ; + std::vector destination(5) ; + + auto first = std::begin(source) ; + auto last = std::end(source) ; + auto result = std::begin(destination) ; + + auto returned = std::copy( first, last, result ) ; + + // true + bool b = (returned == (result + (last - first)) ; +} +\end{lstlisting} + +ここで、\texttt{last-first}は\texttt{source}の要素数の\texttt{5}なので、\texttt{result + 5}は\texttt{copy}の戻り値のイテレーターと等しい。 + +\texttt{copy}には\texttt{[first,last)}の範囲が\texttt{result}から続く範囲とオーバーラップしてはいけないという制約がある。 + +オーバーラップ\index{おばらつぷ@オーバーラップ}というのは、同じ要素を参照しているという意味だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3} ; + + // [first,last)とresultがオーバーラップしている + std::copy( std::begin(v), std::end(v), std::begin(v) ) ; +} +\end{lstlisting} + +オーバーラップした場合、\texttt{copy}の動作は保証されない。 + +実装例。 + +\begin{lstlisting}[language={C++}] +auto copy = []( auto first, auto last, auto result ) +{ + for ( auto iter = first ; iter != last ; ++iter, ++result ) + { *result = *iter ; } + + return result ; +} ; +\end{lstlisting} + +\clearpage +\hypersection{ch1608}{transform} +\index{transform@\texttt{transform}} + +\texttt{transform(first, last, result, op)}は\texttt{copy}に似ているが、\texttt{result}へのコピーが\,\texttt{*result = *iter;}\,ではなく、\texttt{*result = op(*iter);}になる。\texttt{op}は関数だ。 + +以下が実装例だ。\texttt{copy}とほぼ同じだ。 + +\begin{lstlisting}[language={C++}] +auto transform = []( auto first, auto last, auto result, auto op ) +{ + for ( auto iter = first ; iter != last ; ++iter, ++result ) + { *result = op(*iter) ; } + + return result ; +} ; +\end{lstlisting} + +使い方は\texttt{copy}と似ているが、値をコピーをする際に関数を適用することができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector a = {1,2,3,4,5} ; + + std::vector b(5) ; + std::transform( std::begin(a), std::end(a), std::begin(b), + [](auto x){ return 2*x ; } ) ; + // bは{2,4,6,8,10} + + + std::vector c(5) ; + std::transform( std::begin(a), std::end(a), std::begin(c), + [](auto x){ return x % 3 ; } ) ; + // cは{1,2,0,1,2} + + + std::vector d(5) ; + std::transform( std::begin(a), std::end(a), std::begin(d), + [](auto x){ return x < 3 ; } ) ; + // dは{true,true,false,false,false} +} +\end{lstlisting} + +\texttt{result}に代入されるのは関数\texttt{op}の戻り値だ。関数\texttt{op}は値を1つの引数で受け取り値を返す関数だ。 + +\clearpage +\hypersection{ch1609}{replace} +\index{replace@\texttt{replace}} + +\texttt{replace(first, last, old\_value, new\_value)}はイテレーター\texttt{[first,last)}の範囲のイテレーターが指す要素の値が\texttt{old\_value}に等しいものを\texttt{new\_value}に置換する関数だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector a = {1,2,3,3,4,5,3,4,5} ; + std::replace( std::begin(a), std::end(a), 3, 0 ) ; + // aは{1,2,0,0,4,5,0,4,5} +} +\end{lstlisting} + +実装も簡単。 + +\begin{lstlisting}[language={C++}] +auto replace = []( auto first, auto last, auto old_value, auto new_value ) +{ + for ( auto iter = first ; first != last ; ++iter ) + { + if ( *iter == old_value ) + *iter = new_value ; + } +} ; +\end{lstlisting} + +\hypersection{ch1610}{fill} +\index{fill@\texttt{fill}} + +\texttt{fill(first, last, value)}はイテレーター\texttt{[first,last)}の範囲をイテレーターが参照する要素に\texttt{value}を代入する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + std::fill( std::begin(v), std::end(v), 0 ) ; + // vは{0,0,0,0,0} +} +\end{lstlisting} + +\texttt{fill\_n(first, n, value)}はイテレーター\texttt{[first, first+n)}の範囲のイテレーターが参照する要素に\texttt{value}を代入する関数だ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + std::fill_n( std::begin(v), 5, 0 ) ; + // vは{0,0,0,0,0} +} +\end{lstlisting} + +実装例。 + +\begin{lstlisting}[language={C++}] +auto fill_n = []( auto first, auto n, auto value ) +{ + for ( auto i = 0 ; i != n ; ++i, ++first ) + { + *first = value ; + } +} ; +\end{lstlisting} + +\hypersection{ch1611}{generate} +\index{generate@\texttt{generate}} + +\texttt{generate}は\texttt{fill}に似ているが、値として\texttt{value}を取るのではなく、関数\texttt{gen}\index{gen@\texttt{gen}}を取る。 + +\texttt{generate(first, last, gen)}はイテレーター\texttt{[first, last)}の範囲のイテレーターが参照する要素に\texttt{gen()}を代入する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + auto gen_zero = [](){ return 0 ; } ; + std::generate( std::begin(v), std::end(v), gen_zero ) ; + // vは{0,0,0,0,0} +} +\end{lstlisting} + +\texttt{generate\_n(first, n, gen)}は\texttt{fill\_n}の\texttt{generate}版だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + auto gen_zero = []{ return 0 ; } ; + std::generate_n( std::begin(v), 5, gen_zero ) ; + // vは{0,0,0,0,0} +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +実装例は単純だ。 + +\begin{lstlisting}[language={C++}] +auto generate = []( first, last, gen ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + { + *iter = gen() ; + } +} ; + +auto generate_n = []( first, n, gen ) +{ + for ( auto i = 0u ; i != n ; ++i, ++iter ) + { + *iter = gen() ; + } +} ; +\end{lstlisting} + +\hypersection{ch1612}{remove} +\index{remove@\texttt{remove}} + +\texttt{remove(first, last, value)}はイテレーター\texttt{[first,last)}の範囲の参照する要素から、値\texttt{value}に等しいものを取り除く。そして新しい終端イテレーターを返す。 + +アルゴリズム\texttt{remove}が値を取り除くというとやや語弊がある。例えば以下のような数列があり、 +\begin{lstlisting}[style=terminal] +1, 2, 3 +\end{lstlisting} +この中から値\texttt{2}を\texttt{remove}のように取り除く場合、以下のようになる。 +\begin{lstlisting}[style=terminal] +1, 3, ? +\end{lstlisting} + +\texttt{remove}は取り除くべき値の入った要素を、後続の値で上書きする。この場合、1番目の\texttt{2}を2番目の\texttt{3}で上書きする。2番目は不定な状態になる。これは、\texttt{remove}アルゴリズムは2番目がどのような値になるかを保証しないという意味だ。 + +以下のような数列で値\texttt{2}を\texttt{remove}したとき +\begin{lstlisting}[style=terminal] +1,2,2,3,2,2,4 +\end{lstlisting} +以下のようになる。 +\begin{lstlisting}[style=terminal] +1,3,4,?,?,?,? +\end{lstlisting} + +\texttt{remove}の戻り値は、新しいイテレーターの終端を返す。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +auto last2 = remove( first, last, value ) ; +\end{lstlisting} + +この例では、\texttt{remove}は\texttt{[first, last)}から値\texttt{value}に等しい要素を取り除いたイテレーターの範囲を戻り値として返す。その戻り値が\texttt{last2}だ。\texttt{[first, last2)}が値を取り除いたあとの新しいイテレーターの範囲だ。 + +\texttt{remove}を呼び出しても元の\texttt{vector}の要素数が変わることはない。\texttt{remove}は\texttt{vector}の要素の値を変更するだけだ。 + +以上を踏まえて、以下が\texttt{remove}を使う例だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3} ; + + auto last = std::remove( std::begin(v), std::end(v), 2 ) ; + + // "13" + std::for_each( std::begin(v), last, + [](auto x) { std::cout << x ; } ) ; + + std::vector w = {1,2,2,3,2,2,4} ; + + auto last2 = std::remove( std::begin(w), std::end(w), 2 ) ; + + // "134" + std::for_each( std::begin(w), last2, + [](auto x) { std::cout << x ; } ) ; + +} +\end{lstlisting} + +\texttt{remove\_if(first, last, pred)}は、\texttt{[first, last)}の範囲の要素を指すイテレーター\texttt{i}のうち、関数\texttt{pred}に渡した結果\texttt{pred(*i)}が\texttt{true}になる要素を取り除くアルゴリズムだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 偶数の場合true、奇数の場合falseを返す関数 + auto is_even = []( auto x ) { return x%2 == 0 ; } ; + + std::vector v = { 1,2,3,4,5,6,7,8,9 } ; + // 偶数を取り除く + auto last = std::remove_if( std::begin(v), std::end(v), is_even ) ; + + // [ std::begin(v), last)は{1,3,5,7,9} +} +\end{lstlisting} + +\texttt{remove}は現在知っている知識だけではまだ完全に実装できない。以下は不完全な実装の例だ。\texttt{remove}を完全に理解するためには\texttt{ムーブセマンティクス}の理解が必要だ。 + +\begin{lstlisting}[language={C++}] +auto remove_if = []( auto first, auto last, auto pred ) +{ + // removeする最初の要素 + auto removing = std::find_if( first, last, pred ) ; + // removeする要素がなかった + if ( removing == last ) + return last ; + + // removeする要素の次の要素 + auto remaining = removing ; + ++remaining ; + + // removeする要素に上書きする + for ( ; remaining != last ; ++remaining ) + { + // 上書き元も取り除くのであればスキップ + if ( pred( *remaining ) == false ) + { + *removing = *remaining ; + ++removing ; + } + + } + // 新しい終端イテレーター + return removing ; +} ; +\end{lstlisting} + diff --git a/TeX/017-lambda.tex b/TeX/017-lambda.tex new file mode 100644 index 0000000..b82e74b --- /dev/null +++ b/TeX/017-lambda.tex @@ -0,0 +1,255 @@ +\hyperchapter{ch17}{ラムダ式}{ラムダ式} + +実は以下の形の関数は、「関数」\index{かんすう@関数}ではない。 + +\begin{lstlisting}[language={C++}] +auto function = []( auto value ) { return value } ; +\end{lstlisting} + +これは\texttt{ラムダ式}\index{らむだしき@ラムダ式}と呼ばれるC++の機能で、関数のように振る舞うオブジェクトを作るための式だ。 + +\hypersection{ch1701}{基本} + +\texttt{ラムダ式}の基本の文法は以下のとおり。 + +\begin{lstlisting}[style=grammar] +[](){} ; +\end{lstlisting} + +これを細かく分解すると以下のようになる。 + +\begin{lstlisting}[style=grammar] +[] // ラムダ導入子 +() // 引数リスト +{} // 複合文 +; // 文末 +\end{lstlisting} + +\texttt{ラムダ導入子}はさておく。 + +\texttt{引数リスト}\index{ひきすうりすと@引数リスト}\index{らむだしき@ラムダ式!ひきすうりすと@引数リスト}は通常の関数と同じように型名と名前を書ける。 + +\begin{lstlisting}[language={C++}] +void f( int x, double d ) { } + +[]( int x, double d ) { } ; +\end{lstlisting} + +\texttt{ラムダ式}では、\texttt{引数リスト}に\texttt{auto}キーワードが使える。 + +\begin{lstlisting}[language={C++}] +[]( auto x ) { } ; +\end{lstlisting} + +このように書くとどんな型でも受け取れるようになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto f = []( auto x ) + { std::cout << x ; } ; + + f(0) ; // int + f(1.0) ; // double + f("hello"s) ; // std::string +} +\end{lstlisting} + +\texttt{複合文}\index{ふくごうぶん@複合文}\index{らむだしき@ラムダ式!ふくごうぶん@複合文}は\,\texttt{\{\}}\,\index{\{\}@\texttt{\{\}}}だ。この\,\texttt{\{\}}\,の中に通常の関数と同じように複数の文を書くことができる。 + +\begin{lstlisting}[language={C++}] +[]() +{ + std::cout << "hello"s ; + int x = 1 + 1 ; +} ; +\end{lstlisting} + +最後の\texttt{文末}\index{ぶんまつ@文末}\index{らむだしき@ラムダ式!ぶんまつ@文末}は\texttt{文}の最後に付けるセミコロンだ。これは\,\texttt{"1+1 ;"}\,とするのと変わらない。\texttt{"1+1"}\,や\,\texttt{"[]()\{\}"}\,は\texttt{式}で、\texttt{文}は\texttt{式}を使うことができる。\texttt{式}だけが入った\texttt{文}を専門用語では\texttt{式文}と呼ぶが特に覚える必要はない。 + +\begin{lstlisting}[language={C++}] +1 + 1 ; // OK、式文 +[](){} ; // OK、式文 +\end{lstlisting} + +\texttt{ラムダ式}は\texttt{式}なので\texttt{式文}の中に書くことができる。 + +\texttt{ラムダ式}は\texttt{式}なので、そのまま\texttt{関数呼び出し}することもできる。 + +\begin{lstlisting}[language={C++}] +void f( std::string x ) +{ + std::cout << x ; +} + +int main() +{ + f( "hello"s ) ; + []( auto x ){ std::cout << x ; }( "hello"s ) ; +} +\end{lstlisting} + +これはわかりやすくインデントすると以下のようになる。 + +\begin{lstlisting}[language={C++}] +f // 関数 +( "hello"s ) ; // 関数呼び出し + +// ラムダ式 +[]( auto x ){ std::cout << x ; } +( "hello"s ) ; // 関数呼び出し +\end{lstlisting} + +ラムダ式が引数を1つも取らない場合、\texttt{引数リスト}は省略できる。 + +\begin{lstlisting}[language={C++}] +// 引数を取らないラムダ式 +[](){} ; +// 引数リストは省略できる +[]{} ; +\end{lstlisting} + +ラムダ式の戻り値の型は\texttt{return}文\index{return@\texttt{return}文}から推定される。 + +\begin{lstlisting}[language={C++}] +// int +[]{ return 0 ; } ; +// double +[]{ return 0.0 ; } ; +// std::string +[]{ return "hello"s ; } ; +\end{lstlisting} + +\texttt{return}文で複数の型を返した場合は推定ができないのでエラーになる。 + +\begin{lstlisting}[language={C++}] +[]( bool b ) +{ + if ( b ) + return 0 ; + else + return 0.0 ; +} ; +\end{lstlisting} + +戻り値の型を指定したい場合は\texttt{引数リスト}のあとに\,\texttt{->}\,\index{->@\texttt{->}}を書き、型名を書く。 + +\begin{lstlisting}[language={C++}] +[]( bool b ) -> int +{ + if ( b ) + return 0 ; + else + // doubleからintへの変換 + return 0.0 ; +} ; +\end{lstlisting} + +戻り値の型の推定は通常の関数も同じだ。 + +\begin{lstlisting}[language={C++}] +// int +auto f() { return 0 ; } + +// 戻り値の型の明示的な指定 +auto f() -> int { return 0 ; } +\end{lstlisting} + +\hypersection{ch1702}{キャプチャー} +\index{きやぷちや@キャプチャー}\index{らむだしき@ラムダ式!きやぷちや@キャプチャー} + +\texttt{ラムダ式}は書かれている関数のローカル変数を使うことができる。これを\texttt{キャプチャー}という。\texttt{キャプチャー}は通常の関数にはできない\texttt{ラムダ式}の機能だ。 + +\begin{lstlisting}[language={C++}] +void f() +{ + // ローカル関数 + auto message = "hello"s ; + + [=](){ std::cout << message ; } ; +} +\end{lstlisting} + +\texttt{キャプチャー}には\texttt{コピーキャプチャー}と\texttt{リファレンスキャプチャー}がある。 + +\hypersubsection{ch170201}{コピーキャプチャー} +\index{こぴきやぷちや@コピーキャプチャー}\index{らむだしき@ラムダ式!こぴきやぷちや@コピーキャプチャー} + +\texttt{コピーキャプチャー}は変数をコピーによってキャプチャーする。 + +\texttt{コピーキャプチャー}をするには、\texttt{ラムダ式}を\texttt{[=]}\index{[=]@\texttt{[=]}}\index{らむだしき@ラムダ式![=]@\texttt{[=]}}と書く。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; + // コピーキャプチャー + [=]{ return x ; } ; +} +\end{lstlisting} + +\texttt{コピーキャプチャー}した変数はラムダ式の中で変更できない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; + // エラー + [=]{ x = 0 ; } ; +} +\end{lstlisting} + +変更できるようにする方法もあるのだが、通常は使われない。 + +\hypersubsection{ch170202}{リファレンスキャプチャー} +\index{りふあれんすきやぷちや@リファレンスキャプチャー}\index{らむだしき@ラムダ式!りふあれんすきやぷちや@リファレンスキャプチャー} + +\texttt{リファレンスキャプチャー}は変数をリファレンスによってキャプチャーする。 + +\texttt{リファレンス}を覚えているだろうか。リファレンスは初期化時の元の変数を参照する変数だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; + // 通常の変数 + int y = x ; + + // 変数を変更 + y = 1 ; + // xの値は変わらない + + // リファレンス + int & ref = x ; + + // リファレンスを変更 + ref = 1 ; + // xの値が変わる +} +\end{lstlisting} + +\texttt{リファレンスキャプチャー}を使うには、\texttt{ラムダ式}を\texttt{[\&]}\index{[\&]@\texttt{[\&]}}\index{らむだしき@ラムダ式![\&]@\texttt{[\&]}}と書く。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; + [&] { return x ; } ; +} +\end{lstlisting} + +\texttt{リファレンスキャプチャー}した変数を\texttt{ラムダ式}の中で変更すると、元の変数が変更される。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; + auto f = [&]{ ++x ; } ; + + f() ; // x == 1 + f() ; // x == 2 + f() ; // x == 3 +} +\end{lstlisting} + +ラムダ式についてはまだいろいろな機能があるが、本書での解説はここまでとする。 diff --git a/TeX/018-class.tex b/TeX/018-class.tex new file mode 100644 index 0000000..3d9b647 --- /dev/null +++ b/TeX/018-class.tex @@ -0,0 +1,503 @@ +\hyperchapter{ch18}{クラスの基本}{クラスの基本} + +C++はもともとC言語に\texttt{クラス}の機能を追加することを目的とした言語だった。 + +\texttt{クラス}\index{くらす@クラス}とは何か。クラスにはさまざまな機能があるが、最も基本的な機能としては以下の2つがある。 + +\begin{itemize} +\item + 変数をまとめる +\item + まとめた変数に関数を提供する +\end{itemize} + +この章は\texttt{クラス}の数ある機能のうち、この2つの機能だけを説明する。 + +\hypersection{ch1801}{変数をまとめる} +\index{へんすう@変数}\index{くらす@クラス!へんすう@変数} + +2次元座標上の点\texttt{(x,y)}を表現するプログラムを書くとする。 + +とりあえず\texttt{int}型で表現してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 表現 + int point_x = 0; + int point_y = 0; +} +\end{lstlisting} + +これはわかりやすい。ところでものは相談だが、点は複数表現したい。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + int x1 = 0 ; + int y1 = 0 ; + + int x2 = 0 ; + int y2 = 0 ; + + int x3 = 0 ; + int y3 = 0 ; +} +\end{lstlisting} + +これはわかりにくい。ところで点はユーザーがいくつでも入力できるものとしよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector xs ; + std::vector ys ; + + // xs.at(i)とys.at(i)は同じ点のための変数 + + int x {} ; + int y {} ; + while ( std::cin >> x >> y ) + { + xs.push_back(x) ; + ys.push_back(y) ; + } +} +\end{lstlisting} + +これはとてもわかりにくい。 + +ここで\texttt{クラス}の出番だ。\texttt{クラス}を使うと点を表現するコードは以下のように書ける。 + +\begin{lstlisting}[language={C++}] +struct point +{ + int x = 0 ; + int y = 0 ; +} ; + +int main() +{ + point p ; + + std::cout << p.x << p.y ; +} +\end{lstlisting} + +点を複数表現するのもわかりやすい。 + +\begin{lstlisting}[language={C++}] +point p1 ; +point p2 ; +point p3 ; +\end{lstlisting} + +ユーザーが好きなだけ点を入力できるプログラムもわかりやすく書ける。 + +\begin{lstlisting}[language={C++}] +struct point +{ + int x = 0 ; + int y = 0 ; +} ; + +int main() +{ + std::vector ps ; + + int x { } ; + int y { } ; + + while( std::cin >> x >> y ) + { + ps.push_back( point{ x, y } ) ; + } +} +\end{lstlisting} + +これが\texttt{クラス}の変数をまとめる機能だ。 + +\texttt{クラス}を定義するには、キーワード\texttt{struct}\index{struct@\texttt{struct}}\index{くらす@クラス!struct@\texttt{struct}}に続いて\texttt{クラス名}\index{くらすめい@クラス名}を書く。 + +\begin{lstlisting}[style=grammar] +struct class_name +{ + +} ; +\end{lstlisting} + +変数は\,\texttt{\{\}}\,の中に書く。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int a = 0 ; + double b = 0.0 ; + std::string c = "hello"s ; +} ; +\end{lstlisting} + +このクラスの中に書かれた変数のことを、\texttt{データメンバー}\index{でためんば@データメンバー}\index{くらす@クラス!でためんば@データメンバー}という。正確には変数ではない。 + +定義した\texttt{クラス}は変数として宣言して使うことができる。\texttt{クラス}の\texttt{データメンバー}を使うには、クラス名に引き続いてドット文字を書きデータメンバー名を書く。 +\index{.@\texttt{.}} + +\begin{lstlisting}[language={C++}] +// 名前と年齢を表現するクラスPerson +struct Person +{ + std::string name ; + int age ; +} ; + +int main() +{ + Person john ; + john.name = "john" ; + john.age = 20 ; +} +\end{lstlisting} + +\texttt{クラス}の\texttt{データメンバー}の定義は変数ではない。オブジェクトではない。つまり、それ自体にストレージが割り当てられてはいない。 + +\begin{lstlisting}[language={C++}] +struct S +{ + // これは変数ではない + int data ; +} ; +\end{lstlisting} + +クラスの変数を定義したときに、その変数のオブジェクト\index{おぶじえくと@オブジェクト}に紐付いたストレージが使われる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int data ; +} ; + +int main() +{ + S s1 ; // 変数 + // オブジェクトs1に紐付いたストレージ + s1.data = 0 ; + + S s2 ; + // 別のストレージ + s2.data = 1 ; + + // false + bool b = s1.data == s2.data ; +} +\end{lstlisting} + +クラスの変数を定義するときにデータメンバーを初期化できる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int x ; + int y ; + int z ; +} ; + +int main() +{ + S s { 1, 2, 3 } ; + // s.x == 1 + // s.y == 2 + // s.z == 3 +} +\end{lstlisting} + +クラスの初期化で\,\texttt{\{1,2,3\}}\,と書くと、クラスの最初のデータメンバーが\texttt{1}で、次のデータメンバーが\texttt{2}で、その次のデータメンバーが\texttt{3}で、それぞれ初期化される。 + +クラスをコピーすると、データメンバーがそれぞれコピーされる。 + +\begin{lstlisting}[language={C++}] +struct S { int a ; double b ; std::string c ; } ; + +int main() +{ + S a{123, 1.23, "123"} ; + // データメンバーがそれぞれコピーされる + S b = a ; +} +\end{lstlisting} + +\hypersection{ch1802}{まとめた変数に関数を提供する} + +分数を表現するプログラムを書いてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int num = 1 ; + int denom = 2 ; + + // 出力 + std::cout << static_cast(num) / static_cast(denom) ; +} +\end{lstlisting} + +分子\texttt{num}と分母\texttt{denom}はクラスにまとめることができそうだ。そうすれば複数の分数を扱うのも楽になる。 + +\begin{lstlisting}[language={C++}] +struct fractional +{ + int num ; + int denom ; +} ; + +int main() +{ + fractional x{1, 2} ; + + // 出力 + std::cout << static_cast(x.num) / static_cast(x.denom) ; +} +\end{lstlisting} + +ところで、この出力を毎回書くのが面倒だ。こういう処理は関数にまとめたい。 + +\begin{lstlisting}[language={C++}] +double value( fractional & x ) +{ + return static_cast(x.num) / static_cast(x.denom) ; +} + +int main() +{ + fractional x{ 1, 2 } ; + std::cout << value( x ) ; +} +\end{lstlisting} + +この関数\texttt{value}はクラス\texttt{fractional}専用だ。であれば、この関数をクラス自体に関連付けたい。そこでC++には\texttt{メンバー関数}\index{めんばかんすう@メンバー関数}\index{くらす@クラス!めんばかんすう@メンバー関数}という機能がある。 + +\texttt{メンバー関数}はクラスの中で定義する関数だ。 + +\begin{lstlisting}[language={C++}] +struct S +{ + void member_function( int x ) + { + return x ; + } +} ; +\end{lstlisting} + +\ifTombow\pagebreak\fi +\texttt{メンバー関数}はクラスの\texttt{データメンバー}を使うことができる。 + +\begin{lstlisting}[language={C++}] +struct fractional +{ + int num ; + int denom ; + + double value() + { + return static_cast(num) / static_cast(denom) ; + } +} ; +\end{lstlisting} + +メンバー関数を呼び出すには、クラスのオブジェクト\index{おぶじえくと@オブジェクト}に続いてドット文字を書き、メンバー関数名を書く。あとは通常の関数のように書く。 +\index{.@\texttt{.}} + +\begin{lstlisting}[language={C++}] +int main() +{ + fractional x{ 1, 2 } ; + std::cout << x.value() ; +} +\end{lstlisting} + +\texttt{メンバー関数}から使える\texttt{データメンバー}は、メンバー関数が呼ばれたクラスのオブジェクトのデータメンバーだ。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int x ; + void print() + { + std::cout << x ; + } +} ; + +int main() +{ + S s1(1) ; + s1.print() ; // 1 + + S s2(2) ; + s2.print() ; // 2 +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +この\texttt{print}を非メンバー関数として書くと以下のようになる。 + +\begin{lstlisting}[language={C++}] +void print( S & s ) +{ + std::cout << s.x ; +} +\end{lstlisting} + +メンバー関数は隠し引数としてクラスのオブジェクトを受け取っている関数だ。メンバー関数の呼び出しには、対応するクラスのオブジェクトが必要になる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + void f() { } +} ; + +int main() +{ + f() ; // エラー + S s ; + s.f() ; // OK +} +\end{lstlisting} + +\texttt{メンバー関数}はデータメンバーを変更することもできる。 + +\begin{lstlisting}[language={C++}] +struct X +{ + int data ; + void f() + { + data = 3 ; + } +} ; +\end{lstlisting} + +先ほどの分数クラスに値を設定するための\texttt{メンバー関数}を追加してみよう。 + +\begin{lstlisting}[language={C++}] +struct fractional +{ + int num ; + int denom ; + + void set( int num_ ) + { + num = num_ ; + denom = 1 ; + } +(@\ifTombow\pagebreak\fi@) + void set( int num_, int denom_ ) + { + num = num_ ; + denom = denom_ ; + } +} ; + +int main() +{ + fractional x ; + + + x.set(5) ; + // x.num == 5 + // x.denom == 1 + + x.set( 2, 3 ) ; + // x.num == 2 + // x.denom == 3 +} +\end{lstlisting} + +メンバー関数\texttt{set(num)}を呼び出すと、値が\(\frac{num}{1}\)になる。メンバー関数\texttt{set(num, denom)}を呼び出すと、値が\(\frac{num}{denom}\)になる。 + +ところで上のコードを見ると、\texttt{データメンバー}と引数の名前の衝突を避けるために、アンダースコアを使っている。 + +\texttt{データメンバー}と引数の名前が衝突するとどうなるのか。確かめてみよう。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int x ; + void f( int x ) + { + x = x ; + } +} ; + +int main() +{ + S s{0} ; + s.f(1) ; + + std::cout << s.x ; +} +\end{lstlisting} + +結果は\texttt{0}だ。メンバー関数\texttt{f}の中の名前\texttt{x}は引数名の\texttt{x}だからだ。 + +すでに名前は\texttt{スコープ}に属するということは説明した。実はクラスも\texttt{スコープ}\index{すこぷ@\texttt{スコープ}}を持つ。上のコードは以下のようなスコープを持つ。 +\index{くらすすこぷ@クラススコープ} + +\begin{lstlisting}[language={C++}] +// グローバル名前空間スコープ +int x ; + +struct S +{ + // クラススコープ + int x ; + + void f( int x ) + { + // 関数のブロックスコープ + x = x ; + } +} ; +\end{lstlisting} + +内側の\texttt{スコープ}は外側の\texttt{スコープ}の名前を隠す。そのため、クラススコープの\texttt{x}はグローバル名前空間スコープ\texttt{x}を隠す。関数のブロックスコープの\texttt{x}はクラススコープの\texttt{x}を隠す。 + +名前がどのスコープに属するかを明示的に指定することによって、隠された名前を使うことができる。 + +\begin{lstlisting}[language={C++}] +int x ; + +struct S +{ + int x ; + + void f( int x ) + { + // 関数のブロックスコープのx + x = 0 ; + // クラススコープのx + S::x = 0 ; + // グローバル名前空間のスコープ + ::x = 0 ; + } +} ; +\end{lstlisting} + +名前空間スコープを明示するために\texttt{namespace\_name::name}を使うように、クラススコープを明示するために\texttt{class\_name::name}を使うことができる。 + +\ifTombow\pagebreak\fi +これを使えば、分数クラスは以下のように書ける。 + +\begin{lstlisting}[language={C++}] +struct fractional +{ + int num ; + int denom ; + + void set( int num, int denom ) + { + fractional::num = num ; + fractional::denom = denom ; + } +} +\end{lstlisting} + diff --git a/TeX/019-operator-overloading.tex b/TeX/019-operator-overloading.tex new file mode 100644 index 0000000..43879e7 --- /dev/null +++ b/TeX/019-operator-overloading.tex @@ -0,0 +1,997 @@ +\hyperchapter{ch19}{より自然に振る舞うクラス}{より自然に振る舞うクラス} + +整数型の\texttt{int}について考えてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; + int b = a + a ; + int c = a + b ; +} +\end{lstlisting} + +同様のことを、前章の分数クラスで書いてみよう。 + +\begin{lstlisting}[language={C++}] +struct fractional +{ + int num ; + int denom ; +} ; + +fractional add( fractional & l, fractional & r ) +{ + // 分母が同じなら + if ( l.denom == r.denom ) + // 単に分子を足す + return fractional{ l.num + r.num, l.denom } ; + + // 分母を合わせて分子を足す + return fractional{ l.num * r.denom + r.num * l.denom, l.denom * r.denom } ; +} + +int main() +{ + fractional a{1,1} ; + fractional b = add(a, a) ; + fractional c = add(a, b) ; +} +\end{lstlisting} + +これは読みにくい。できれば以下のように書きたいところだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + fractional a = 1 ; + fractional b = a + a ; + fractional c = a + b ; +} +\end{lstlisting} + +C++ではクラスをこのように自然に振る舞わせることができる。 + +\hypersection{ch1901}{より自然な初期化} + +\texttt{int}型は初期化にあたって値を設定できる。 + +\begin{lstlisting}[language={C++}] +int a = 0 ; +int b(0) ; +int c{0} ; +\end{lstlisting} + +\texttt{クラス}でこのような初期化をするには、\texttt{コンストラクター}を書く。 +\index{こんすとらくた@コンストラクター}\index{くらす@クラス!こんすとらくた@コンストラクター}\index{くらす@クラス!しよきか@初期化} + +\begin{lstlisting}[language={C++}] +struct fractional +{ + int num ; + int denom ; + + // コンストラクター + fractional( int num ) + : num(num), denom(1) + { } +} ; + +int main() +{ + fractional a = 1 ; + fractional b = 2 ; +} +\end{lstlisting} + +\texttt{コンストラクター}は\texttt{クラス}の\texttt{特殊なメンバー関数}として定義する。\texttt{メンバー関数}としての\texttt{コンストラクター}は、名前がクラス名で、戻り値の型は記述しない。 + +\begin{lstlisting}[style=grammar] +struct class_name +{ + // コンストラクター + class_name() { } +} ; +\end{lstlisting} + +\texttt{コンストラクター}は\texttt{データメンバー}の初期化に特別な文法を持っている。関数の本体の前にコロンを書き、データメンバー名をそれぞれカンマで区切って初期化する。 +\index{くらす@クラス!しよきか@初期化}\index{こんすとらくた@コンストラクター!しよきか@初期化} + +\begin{lstlisting}[style=grammar] +struct class_name +{ + int data_member ; + + class_name( int value ) + : data_member(value) + { } + +} ; +\end{lstlisting} + +このとき、引数名とデータメンバー名が同じでもよい。 + +\begin{lstlisting}[style=grammar] +struct class_name +{ + int x ; + class_name( int x ) + : x(x) { } +} ; +\end{lstlisting} + +\texttt{x(x)}の最初の\texttt{x}は\texttt{class\_name::x}として、次の\texttt{x}は引数名の\texttt{x}として認識される。そのためこのコードは期待どおりに動く。 + +\texttt{コンストラクター}の特別なメンバー初期化を使わずに、\texttt{コンストラクター}の関数の本体で\texttt{データメンバー}を変更してもよい。 + +\begin{lstlisting}[style=grammar] +struct class_name +{ + int x ; + class_name( int x ) + { + class_name::x = x ; + } +} ; +\end{lstlisting} + +この場合、\texttt{x}は関数の本体が実行される前に一度初期化され、その後、値を代入されるという挙動の違いがある。 + +コンストラクターはクラスが初期化されるときに実行される。例えば以下のプログラムを実行すると、 +\begin{lstlisting}[language={C++}] +int main() +{ + S a(1) ; + S b(2) ; + S c(3) ; +} +\end{lstlisting} +以下のように出力される。 +\begin{lstlisting}[style=terminal] +123 +\end{lstlisting} + +\texttt{コンストラクター}のついでに\texttt{デストラクター}\index{ですとらくた@デストラクタ}\index{くらす@クラス!ですとらくた@デストラクタ}も学んでおこう。\texttt{コンストラクター}はクラスのオブジェクトが初期化されるときに実行されるが、\texttt{デストラクター}はクラスのオブジェクトが破棄\index{くらす@クラス!はき@破棄}されるときに実行される。 + +\texttt{デストラクター}の宣言は\texttt{コンストラクター}と似ている。違う点は、クラス名の前にチルダ文字を書くところだ。 + +\begin{lstlisting}[style=grammar] +struct S +{ + // デストラクター + ~S() + { + // オブジェクトの破棄時に実行される + } +} ; +\end{lstlisting} + +関数のローカル変数は、ブロックスコープを抜ける際に破棄される。破棄は構築の逆順に行われる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a ; + { + int b ; + // bが破棄される + } + int c ; +// cが破棄される +// aが破棄される +} +\end{lstlisting} + +さっそく初期化時と終了時に標準出力をするクラスで確かめてみよう。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int n ; + S( int n ) + : n(n) + { + std::cout << "constructed: "s << n << "\n"s ; + } + + ~S() + { + std::cout << "destructed: "s << n << "\n"s ; + } +} ; +\end{lstlisting} + +このクラスを以下のように使うと、 +\begin{lstlisting}[language={C++}] +int main() +{ + S a(1) ; + { S b(2) ; } + S c(3) ; +} +\end{lstlisting} +以下のように出力される。 +\begin{lstlisting}[style=terminal] +constructed: 1 +constructed: 2 +destructed: 2 +constructed: 3 +destructed: 3 +destructed: 1 +\end{lstlisting} + +この出力は以下のような意味だ。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + \texttt{a}が構築される +\item + \texttt{b}が構築される +\item + \texttt{b}が破棄される +\item + \texttt{c}が構築される +\item + \texttt{c}が破棄される +\item + \texttt{a}が破棄される +\end{enumerate} + +\texttt{b}はブロックスコープの終わりに達したので\texttt{a}の構築のあと、\texttt{c}の構築の前に破棄される。破棄は構築の逆順で行われるので、\texttt{a}よりも先に\texttt{c}が破棄される。 + +\texttt{コンストラクター}と\texttt{デストラクター}は戻り値を返さないので、\texttt{return文}には値を書かない。 +\index{こんすとらくた@コンストラクター!もどりち@戻り値}\index{ですとらくた@デストラクター!もどりち@戻り値}\index{return@\texttt{return}文} + +\begin{lstlisting}[language={C++}] +struct class_name +{ + class_name() + { + return ; + } +} ; +\end{lstlisting} + +\texttt{コンストラクター}は複数の引数\index{こんすとらくた@コンストラクター!ひきすう@引数}を取ることもできる。 + +\begin{lstlisting}[language={C++}] +struct fractional +{ + int num ; + int denom ; + + fractional( int num ) + : num(num), denom(1) + { } + + fractional( int num, int denom ) + : num(num), denom(denom) + { } +} ; + +int main() +{ + // fractional(int)が呼ばれる + fractional a = 1 ; + + // fractional(int,int)が呼ばれる + fractional b(1, 2) ; + fractional c{1, 2} ; +} +\end{lstlisting} + +複数の引数を取るコンストラクターを呼び出すには\,\texttt{"="}\,は使えない。\texttt{"()"}\,か\,\texttt{"\{\}"}\,を使う必要がある。 + +上のコードを見ると、コンストラクターは引数の数以外にやっていることはほとんど同じだ。こういう場合、コンストラクターを1つにする方法がある。 + +実はコンストラクターに限らず、関数は\texttt{デフォルト実引数}\index{でふおるとじつひきすう@デフォルト実引数}を取ることができる。書き方は仮引数に\,\texttt{"="}\,で値を書く。 +\index{かんすう@関数!でふおるとじつひきすう@デフォルト実引数}\index{こんすとらくた@コンストラクター!でふおるとじつひきすう@デフォルト実引数} + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +void f( int x = 0 ) +{ } + +int main() +{ + f() ; // f(0) + f(1) ; // f(1) +} +\end{lstlisting} + +\texttt{デフォルト実引数}を指定した関数の仮引数に実引数を渡さない場合、\texttt{デフォルト実引数}で指定した値が渡される。 + +ところで、\texttt{仮引数}\index{かりひきすう@仮引数}\index{かんすう@関数!かりひきすう@仮引数}、\texttt{実引数}\index{じつひきすう@実引数}\index{かんすう@関数!じつひきすう@実引数}という聞き慣れない言葉が出てきた。これは関数の引数を区別するための言葉だ。\texttt{仮引数}は関数の宣言の引数。実引数は関数呼び出しのときに引数に渡す値のことを意味する。 + +\begin{lstlisting}[language={C++}] +// xは仮引数 +void f( int x ) { } + +int main() +{ + // 123は仮引数xに対する実引数 + f( 123 ) ; +} +\end{lstlisting} + +\texttt{デフォルト実引数}は関数の実引数の一部を省略できる。 + +ただし、\texttt{デフォルト実引数}を使った以後の仮引数には、すべて\texttt{デフォルト実引数}がなければならない。 + +\begin{lstlisting}[language={C++}] +// OK +void f( int x, int y = 0, int z = 0 ) { } +// エラー +// zにデフォルト実引数がない +void g( int x, int y = 0, int z ) { } +\end{lstlisting} + +\texttt{デフォルト実引数}で途中の引数だけ省略することはできない。 + +\begin{lstlisting}[language={C++}] +void f( int x = 0, int y = 0, int z = 0) { } + +int main() +{ + // エラー + f( 1, , 2 ) ; +} +\end{lstlisting} + +\texttt{デフォルト実引数}を使うと、コンストラクターを1つにできる。 + +\begin{lstlisting}[language={C++}] +struct fractional +{ + int num ; + int denom ; + + fractional( int num, int denom = 1 ) + : num(num), denom(denom) + { } +} ; + +int main() +{ + fractional a = 1 ; + fractional b(1,2) ; + fractional c{1,2} ; +} +\end{lstlisting} + +コンストラクターの数を減らす方法はもう1つある。\texttt{デリゲートコンストラクター}\index{でりげとこんすとらくた@デリゲートコンストラクター}\index{こんすとらくた@コンストラクター!でりげと@デリゲート〜}だ。 + +\begin{lstlisting}[language={C++}] +struct fractional +{ + int num ; + int denom ; + + fractional( int num, int denom ) + : num(num), denom(denom) + { } + + // デリゲートコンストラクター + fractional( int num ) + : fractional( num, 1 ) + { } +} ; +\end{lstlisting} + +\texttt{デリゲートコンストラクター}は初期化処理を別のコンストラクターにデリゲート(丸投げ)する。丸投げ先のコンストラクターの初期化処理が終わり次第、デリゲートコンストラクターの関数の本体が実行される。 + +\begin{lstlisting}[language={C++}] +struct S +{ + S() + : S(1) + { + std::cout << "delegating constructor\n" ; + } + + S( int n ) + { + std::cout << "constructor\n" ; + } +} ; + +int main() +{ + S s ; +} +\end{lstlisting} + +このプログラムを実行すると、以下のように出力される。 + +\begin{lstlisting}[style=terminal] +constructor +delegating constructor +\end{lstlisting} + +まず\,\texttt{"S()"}\,が呼ばれるが、処理を\,\texttt{"S(int)"}\,にデリゲートする。\texttt{"S(int)"}\,の処理が終わり次第\,\texttt{"S()"}\,の関数の本体が実行される。そのためこのような出力になる。 + +コンストラクターを減らすのはよいが、減らしすぎても不便だ。以下の例を見てみよう。 + +\begin{lstlisting}[language={C++}] +struct A { } ; +struct B { B(int) { } } ; + +int main() +{ + A a ; // OK + B b ; // エラー +} +\end{lstlisting} + +クラス\texttt{A}の変数は問題ないのに、クラス\texttt{B}の変数はエラーになる。これはクラス\texttt{B}には引数を取らないコンストラクターがないためだ。 + +クラス\texttt{B}に引数を必要としないコンストラクターを書くと、具体的に引数を渡さなくても初期化ができるようになる。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[language={C++}] +struct B +{ + B() { } + B( int x ) { } +} ; + +int main() +{ + B b ; // OK +} +\end{lstlisting} + +もしくは、デフォルト引数を使ってもよい。 + +\begin{lstlisting}[language={C++}] +struct B +{ + B( int x = 0 ) { } +} ; +\end{lstlisting} + +もちろん、ユーザーが値を指定しなければならないようなクラスは値を指定するべきだ。 + +\begin{lstlisting}[language={C++}] +// 人間クラス +// 必ず名前が必要 +struct person +{ + std::string name + person( std::string name ) + : name(name) { } +} ; +\end{lstlisting} + +\hypersection{ch1902}{自然な演算子} + +\texttt{int}型は\,\texttt{+-*/}\,といった演算子を使うことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; + int b = 1 ; + a + b ; + a - b ; + a * b ; + a / b ; +} +\end{lstlisting} + +クラスも演算子を使った自然な記述ができる。クラスを演算子に対応させることを、\texttt{演算子のオーバーロード}\index{えんざんし@演算子!おばろど@オーバーロード}という。 + +分数クラスの足し算を考えよう。 + +\begin{itemize} +\item + 分母が同じならば分子を足す +\item + 分母が異なるならば互いの分母を掛けて、分母をそろえて足す +\end{itemize} + +コードにすると以下のようになる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +struct fractional +{ + int num ; + int denom ; + +// コンストラクターなど +} ; + +fractional add( fractional & l, fractional & r ) +{ + // 分母が同じなら + if ( l.denom == r.denom ) + // 単に分子を足す + return fractional{ l.num + r.num, l.denom } ; + + // 分母を合わせて分子を足す + return fractional{ l.num * r.denom + r.num * l.denom, l.denom * r.denom } ; +} +\end{lstlisting} + +しかし、この関数\texttt{add}を使ったコードは以下のようになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + fractional a{1,2} ; + fractional b{1,3} ; + + auto c = add(a, b) ; +} +\end{lstlisting} + +これはわかりにくい。できれば、以下のように書きたい。 + +\begin{lstlisting}[language={C++}] +auto c = a + b ; +\end{lstlisting} + +C++では演算子\index{えんざんし@演算子}は関数として扱うことができる。演算子の名前は\texttt{operator op}で、例えば\,\texttt{+演算子}の名前は\texttt{operator +}になる。 + +関数\texttt{operator +}は引数を2つ取り、戻り値を返す関数だ。 +\index{\protect{+}@\texttt{\protect{+}}} + +\begin{lstlisting}[language={C++}] +fractional operator +( fractional & l, fractional & r ) +{ + // 分母が同じなら + if ( l.denom == r.denom ) + // 単に分子を足す + return fractional{ l.num + r.num, l.denom } ; + else +(@\ifTombow\pagebreak\fi@) + // 分母を合わせて分子を足す + return fractional{ l.num * r.denom + r.num * l.denom, l.denom * r.denom } ; +} +\end{lstlisting} + +このように\texttt{operator +}を書くと、以下のようなコードが書ける。 + +\begin{lstlisting}[language={C++}] +auto c = a + b ; +\end{lstlisting} + +同様に、引き算は\texttt{operator -}、掛け算は\texttt{operator *}、割り算は\texttt{operator /}\,だ。 +\index{\protect{-}@\texttt{\protect{-}}}\index{\protect{*}@\texttt{\protect{*}}}\index{/@\texttt{/}} + +以下に関数の宣言を示すので実際に分数の計算を実装してみよう。 + +\begin{lstlisting}[language={C++}] +fractional operator -( fractional & l, fractional & r ) ; +fractional operator *( fractional & l, fractional & r ) ; +fractional operator /( fractional & l, fractional & r ) ; +\end{lstlisting} + +引き算は足し算とほぼ同じだ。 + +\begin{lstlisting}[language={C++}] +fractional operator -( fractional & l, fractional & r ) +{ + // 分母が同じ + if ( l.denom == r.denom ) + return fractional{ l.num - r.num, l.denom } ; + else + return fractional{ l.num * r.denom - r.num * l.denom, l.denom * r.denom } ; +} +\end{lstlisting} + +掛け算と割り算は楽だ。 + +\begin{lstlisting}[language={C++}] +fractional operator *( fractional & l, fractional & r ) +{ + return fractional{ l.num * r.num, l.denom * r.denom } ; +} + +fractional operator /( fractional & l, fractional & r ) +{ + return fractional{ l.num * r.denom, l.denom * r.num } ; +} +\end{lstlisting} + +\clearpage +\hypersection{ch1903}{演算子のオーバーロード} +\index{えんざんし@演算子!おばろど@オーバーロード} + +\hypersubsection{ch190301}{二項演算子} +\index{にこうえんざんし@二項演算子}\index{えんざんし@演算子!にこう@二項〜} + +C++にはさまざまな演算子があるが、多くが\texttt{二項演算子}と呼ばれる演算子だ。\texttt{二項演算子}は2つの引数を取り、値を返す。 + +\begin{lstlisting}[language={C++}] +a + b ; +a - b ; +a * b ; +a / b ; +\end{lstlisting} + +このような演算子は\texttt{operator +}のように、キーワード\texttt{operator}に続いて演算子の文字を書くことで、関数名とする。あとは通常の関数と変わらない。 + +\begin{lstlisting}[language={C++}] +struct S { } ; + +S add( S a, S b ) ; +S operator + ( S a, S b ) ; +\end{lstlisting} + +戻り値の型は何でもよい。 + +\begin{lstlisting}[language={C++}] +struct S { } ; + +int operator +( S, S ) { return 0 ; } +void operator -( S, S ) { } + +int main() +{ + S s ; + int x = s + s ; + s - s ; // 戻り値はない +} +\end{lstlisting} + +演算子としてではなく、関数と同じように呼び出すこともできる。 + +\begin{lstlisting}[language={C++}] +struct S { } ; + +// S f( S, S )のようなもの +S operator + ( S, S ) { } + +int main() +{ + S s ; + // f(s,s)のようなもの + operator +(s,s) ; +} +\end{lstlisting} + +\texttt{演算子のオーバーロード}では、少なくとも1つのユーザー定義された型がなければならない。つまり以下のような演算子のオーバーロードはできないということだ。 + +\begin{lstlisting}[language={C++}] +int operator +( int, int ) ; +int operator +( int, double ) ; +\end{lstlisting} + +二項演算子には\texttt{オペランド}\index{おぺらんど@オペランド}と呼ばれる式を取る。 + +\begin{lstlisting}[language={C++}] +a + b ; +\end{lstlisting} + +この場合、二項演算子\texttt{operator +}には\texttt{a}, \texttt{b}という2つのオペランドがある。 + +二項演算子をオーバーロードする場合、最初の引数が最初のオペランド、次の引数が次のオペランドに対応する。 + +\begin{lstlisting}[language={C++}] +struct X { } ; +struct Y { } ; + +void operator +( X, Y ) { } + +int main() +{ + X x ; + Y y ; + + // OK + x + y ; + + // エラー + // operator +(Y,X)は存在しない + y + x ; +} +\end{lstlisting} + +そのため、上の例で\,\texttt{"x+y"}\,と\,\texttt{"y+x"}\,を両方使いたい場合は、 +\begin{lstlisting}[language={C++}] +void operator +(Y,X) { } +\end{lstlisting} +も必要だ。 + +\ifTombow\pagebreak\fi +現実のコードでは、二項演算子のオーバーロードは以下のように書くことが多い。 + +\begin{lstlisting}[language={C++}] +struct S { } ; + +// 引数名はさまざま +S operator +( S const & left, S const & right ) +{ + +} +\end{lstlisting} + +\texttt{const \&}\index{const \&@\texttt{const \&}}という特別な書き方をする。\texttt{\&}についてはすでに学んだように、リファレンスだ。リファレンスを使うことによって値をコピーせずに効率的に使うことができる。 + +\texttt{const}というのは値を変更しない変数を宣言する機能だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; + x = 1 ; // OK + + int const y = 0 ; + y = 0 ; // エラー +} +\end{lstlisting} + +\texttt{const}を付けると値を変更できなくなる。 + +一般に\texttt{operator +}のような演算子は、オペランドに渡した変数を書き換えない処理をすることが期待されている。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 1 ; + int b = 1 ; + + // a, bは書き換わらない + int c = a + b ; +} +\end{lstlisting} + +もちろん、\texttt{operator +}をオーバーロードして引数をリファレンスで取り、値を書き換えるような処理を書くこともできる。ただ、通常はそのような処理をすることはない。 + +しかし、処理の効率のためにリファレンスは使いたい。 + +そのようなときに、\texttt{const}かつリファレンスを使うと、効率的で値の変更ができないコードが書ける。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +struct IntLike{ int data ;} ; + +IntLike operator + ( IntLike const & l, IntLike const & r ) +{ + return IntLike{ l.data + r.data } +} +\end{lstlisting} + +\texttt{const}リファレンスの変数をうっかり書き換えてしまった場合、コンパイラーが検出してくれるので、バグを未然に発見することができる。 + +\hypersubsection{ch190302}{単項演算子} +\index{たんこうえんざんし@単項演算子}\index{えんざんし@演算子!たんこう@単項〜} + +\texttt{単項演算子}はオペランドを1つしか取らない演算子のことだ。 + +\texttt{単項演算子}についてはまだ説明していないものも多い。例えば、\texttt{operator +}\index{\protect{+}@\texttt{\protect{+}}}や\texttt{operator -}\index{\protect{-}@\texttt{\protect{-}}}\,がある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 1 ; + +x ; // 1: operator + + -x ; // -1: operator - +} +\end{lstlisting} + +\texttt{単項演算子}は引数を1つしか取らない関数として書く。 + +\begin{lstlisting}[language={C++}] +struct IntLike{ int data ;} ; + +IntLike operator +( IntLike const & obj ) +{ + return obj ; +} + +IntLIke operator -( IntLike const & obj ) +{ + return IntLike{ -obj.data } ; +} +\end{lstlisting} + +\clearpage +\hypersubsection{ch190303}{インクリメント/デクリメント} + +\texttt{インクリメント演算子}と\texttt{デクリメント演算子}はやや変わっている。この演算子には、オペランドの前に書く前置演算子(\,\texttt{++i}\,)と、あとに書く後置演算子(\,\texttt{i++}\,)がある。 +\index{いんくりめんとえんざんし@インクリメント演算子}\index{えんざんし@演算子!いんくりめんと@インクリメント〜} +\index{でくりめんとえんざんし@デクリメント演算子}\index{えんざんし@演算子!でくりめんと@デクリメント〜} +\index{ぜんちえんざんし@前置演算子}\index{えんざんし@演算子!ぜんち@前置〜} +\index{こうちえんざんし@後置演算子}\index{えんざんし@演算子!こうち@後置〜} +\index{\protect{++}@\texttt{\protect{++}}} +\index{{-}{-}@\texttt{{-}{-}}} + +\begin{lstlisting}[language={C++}] +int main() +{ + int i = 0 ; + ++i ; + i++ ; + + --i ; + i-- ; +} +\end{lstlisting} + +前置演算子を評価すると、演算子を評価したあとの値になる。 + +\begin{lstlisting}[language={C++}] +int i = 0 ; +++i ; // 1 +i ; // 1 +\end{lstlisting} + +一方、後置演算子を評価すると、演算子を評価する前の値になる。 + +\begin{lstlisting}[language={C++}] +int i = 0 ; +i++ ; // 0 +i ; // 1 +\end{lstlisting} + +さらに前置演算子を評価した結果はリファレンスになるので代入やさらなる演算子の適用ができる。 + +\begin{lstlisting}[language={C++}] +int i = 0 ; +++i = 0 ; // iは0 +++++i ; // iは2 + +i++ = 0 ; // エラー +i++++ ; // エラー +\end{lstlisting} + +インクリメントとデクリメントの前置演算子は、単項演算子と同じ方法で書くことができる。 +\index{ぜんちえんざんし@前置演算子}\index{えんざんし@演算子!ぜんち@前置〜} + +\begin{lstlisting}[language={C++}] +struct IntLike { int data ; } ; + +IntLike & operator ++( IntLike & obj ) +{ + ++obj.data ; + return obj ; +} +IntLike & operator --( IntLike & obj ) +{ + --obj.data ; + return obj ; +} +\end{lstlisting} + +引数を変更するので\texttt{const}ではないリファレンスを使う。戻り値は引数をそのままリファレンスで返す。 + +もちろん、この実装はインクリメントとデクリメントの挙動を自然に再現したい場合の実装だ。以下のような挙動を実装することも可能だ。 + +\begin{lstlisting}[language={C++}] +struct S { } ; + +void operator ++( S const & s ) +{ + std::cout << "increment!\n" ; +} + +int main() +{ + + S s ; + ++s ; +} +\end{lstlisting} + +演算子のオーバーロードは演算子の文法で関数を呼べるという機能で、その呼び出した結果の関数が何をしようとも自由だからだ。 + +後置演算子は少し変わっている。以下が後置演算子の実装だ。 +\index{こうちえんざんし@後置演算子}\index{えんざんし@演算子!こうち@後置〜} + +\begin{lstlisting}[language={C++}] +struct IntLike { int data ; } ; + +IntLike operator ++( IntLike & obj, int ) +{ + auto temp = obj ; + ++obj.data ; + return temp ; +} +IntLike operator --( IntLike & obj, int ) +{ + auto temp = obj ; + --obj.data ; + return temp ; +} +\end{lstlisting} + +後置演算子は2つ目の引数として\texttt{int}型を取る。この引数はダミーで前置演算子と後置演算子を区別する以外の意味はない。意味はないので引数名は省略している。 + +\begin{lstlisting}[language={C++}] +struct S { } ; + +// 前置演算子 +void operator ++( S ) ; +// 後置演算子 +void operator ++( S, int ) ; +\end{lstlisting} + +後置演算子はオペランドである引数を変更するが、戻り値は変更する前の値だ。なので変更前の値をまずコピーしておき、そのコピーを返す。 + +\hypersubsection{ch190304}{メンバー関数での演算子のオーバーロード} + +実は演算子のオーバーロードはメンバー関数で書くことも可能だ。 + +例えば、 +\begin{lstlisting}[language={C++}] +S s ; +s + s ; +\end{lstlisting} +を可能にするクラス\texttt{S}に対する\texttt{operator +}は、 +\index{\protect{+}@\texttt{\protect{+}}} +\begin{lstlisting}[language={C++}] +struct S { } +S operator + ( S const &, S const & ) ; +\end{lstlisting} +でも実装できるが、メンバー関数としても実装できる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + S operator +( S const & right ) + { + return S{} ; + } +} ; +\end{lstlisting} + +演算子のオーバーロードをメンバー関数で書く場合、最初のオペランドがメンバー関数の属するクラスのオブジェクト、2つ目のオペランドが1つ目の引数になる。 + +\begin{lstlisting}[language={C++}] +struct IntLike +{ + int data ; + + IntLike operator +( IntLike const & right ) + { + return IntLike { data + right.data } ; + } +} ; + +int main() +{ + IntLike a(1) ; + IntLike b(2) ; + + IntLike c = a + b ; +} +\end{lstlisting} + +この場合、メンバー関数は変数\texttt{a}に対して呼ばれ、変数\texttt{b}が\texttt{right}となる。 + +普通のメンバー関数のように呼ぶこともできる。 + +\begin{lstlisting}[language={C++}] +IntLike c = a.operator +( b ) ; +\end{lstlisting} + +一見戸惑うかもしれないが、これは普通のメンバー関数呼び出しと何ら変わらない。 + +\begin{lstlisting}[language={C++}] +struct S +{ + void plus( S const & other ) { } + void operator +( S const & other ) { } +} ; + +int main() +{ + S a ; + S b ; + + // これはメンバー関数呼び出し + a.plus(b) ; + // これもメンバー関数呼び出し + a.operator +(b) ; + // 同じくメンバー関数呼び出し + a + b ; +} +\end{lstlisting} + +演算子のオーバーロードはフリー関数とメンバー関数のどちらで実装すればいいのだろうか。答えはどちらでもよい。ただし、ごく一部の演算子はメンバー関数でしか実装できない。 + +こうして、この章の冒頭にある演算子を使った自然な四則演算の記述が、自作のクラスでも可能になる。 diff --git a/TeX/020-array.tex b/TeX/020-array.tex new file mode 100644 index 0000000..1ece6be --- /dev/null +++ b/TeX/020-array.tex @@ -0,0 +1,153 @@ +\hyperchapter{ch20}{std::array}{std::array} + +\texttt{std::vector}\,を覚えているだろうか。\texttt{T}型の値をいくつでも保持できるクラスだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // int型の値を10個保持するクラス + std::vector v(10) ; + + // 0番目の値を1に + v.at(0) = 1 ; + + // イテレーターを取る + auto i = std::begin(v) ; +} +\end{lstlisting} + +この章では、\texttt{vector}と似ているクラス、\texttt{std::array}\,\index{array@\texttt{array}}を学ぶ。\texttt{array}\index{array@\texttt{array}}は\texttt{T}型の値を\texttt{N}個保持するクラスだ。 + +その使い方は一見\texttt{vector}と似ている。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // int型の値を10個保持するクラス + std::array a ; + + // 0番目の値を1に + a.at(0) = 1 ; + +(@\ifTombow\pagebreak\fi@) + // イテレーターを取る + auto i = std::begin(a) ; +} +\end{lstlisting} + +\texttt{vector}と違う点は、コンパイル時に要素数が固定されるということだ。 + +\texttt{vector}は実行時に要素数を決めることができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::size_t N{} ; + std::cin >> N ; + + // 要素数N + std::vector v(N) ; +} +\end{lstlisting} + +一方、\texttt{array}はコンパイル時に要素数を決める。標準入力から得た値は実行時のものなので、使うことはできない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::size_T N{} ; + std::cin >> N ; + + // エラー + std::array< int, N > a ; +} +\end{lstlisting} + +\texttt{vector}は実行時に要素数を変更することができる。メンバー関数\texttt{push\_back}は要素数を1増やす。メンバー関数\texttt{resize(sz)}は要素数を\texttt{sz}にする。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 要素数5 + std::vector v(5) ; + // 要素数6 + v.push_back(1) ; + // 要素数2 + v.resize(2) ; +} +\end{lstlisting} + +\texttt{array}は\texttt{push\_back}も\texttt{resize}も提供していない。 + +\texttt{vector}も\texttt{array}もメンバー関数\texttt{at(i)}\index{at@\texttt{at}}\index{array@\texttt{array}!at@\texttt{at}}で\texttt{i}番目の要素にアクセスできる。実は、\texttt{i}番目にアクセスする方法はほかにもある。\texttt{[i]}\index{[]@\texttt{[]}}を使う方法だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a ; + + // どちらも0番目の要素に1を代入 + a.at(0) = 1 ; + a[0] = 1 ; + + // どちらも0番目の要素を標準出力 + std::cout << a.at(0) ; + std::cout << a[0] ; +} +\end{lstlisting} + +\texttt{at(i)}と\texttt{[i]}の違いは、要素の範囲外にアクセスしたときの挙動だ。\texttt{at(i)}はエラー処理が行われる。\texttt{[i]}は何が起こるかわからない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 10個の要素を持つ + // 0番目から9番目までが妥当な範囲 + std::array a ; + + // エラー処理が行われる + // プログラムは終了する + a.at(10) = 0 ; + // 何が起こるかわからない + a[10] = 0 ; +} +\end{lstlisting} + +この理由は、\texttt{[i]}は要素数が妥当な範囲かどうかを確認する処理を行っていないためだ。その分余計な処理が発生しないが、間違えたときに何が起こるかわからないという危険性がある。通常は\texttt{at(i)}を使うべきだ。 + +実はこの\texttt{[i]}は\texttt{operator []}\index{[]@\texttt{[]}}というれっきとした演算子だ。演算子のオーバーロードもできる。例えば以下は任意個の要素を持ち、常にゼロを返す\texttt{array}のように振る舞う意味のないクラスだ。 + +\begin{lstlisting}[language={C++}] +// 常にゼロを返すクラス +// 何を書き込んでもゼロを返す +struct null_array +{ + int dummy ; + // 引数は無視 + int & operator [] ( std::size_t ) + { + dummy = 0 ; + return dummy ; + } +} ; + +int main() +{ + null_array a ; + + // 0 + std::cout << a[0] ; + // 0 + std::cout << a[999] ; + + a[100] = 0 ; + // 0 + std::cout << a[100] ; +} +\end{lstlisting} + +なぜ\texttt{vector}という実行時に要素数を設定でき実行時に要素数を変更できる便利なクラスがありながら、\texttt{array}のようなコンパイル時に要素数が決め打ちで要素数の変更もできないようなクラスもあるのだろうか。その理由は\texttt{array}と\texttt{vector}はパフォーマンスの特性が異なるからだ。\texttt{vector}はストレージ(メモリー)の動的確保をしている。ストレージの動的確保は実行時の要素数を変更できるのだが、そのために予測不可能な非決定的なパフォーマンス特性を持つ。\texttt{array}はストレージの動的確保を行わない。この結果実行時に要素数を変更することはできないが、予測可能で決定的なパフォーマンス特性を持つ。 + +その他の\texttt{array}の使い方は、\texttt{vector}とほぼ同じだ。 + +さて、これから\texttt{array}を実装していこう。実装を通じて読者はC++のクラスとその他の機能を学んでいくことになる。 diff --git a/TeX/021-three-virtues-of-a-programmer.tex b/TeX/021-three-virtues-of-a-programmer.tex new file mode 100644 index 0000000..be397ba --- /dev/null +++ b/TeX/021-three-virtues-of-a-programmer.tex @@ -0,0 +1,22 @@ +\hyperchapter{ch21}{プログラマーの三大美徳}{プログラマーの三大美徳} + +プログラミング言語Perlの作者、Larry Wallは著書『プログラミングPerl』の初版で以下のように宣言した。 +\index{Wall, Larry} + +\begin{quote} +読者はプログラマーの三大美徳である、怠惰、短気、傲慢を会得すべきである。 +\end{quote} +\index{ぷろぐらまのさんだいびとく@プログラマーの三大美徳} + +第2版の巻末の用語集では、以下のような定義が与えらた。 + +\begin{description} +\item[怠惰] +プログラマーは労力を削減するための労力を惜しまないこと。怠惰のために書いたプログラムは他人にも便利であり、そしてドキュメントを書くことにより自ら他人の質問に答えずに済むようにすること。これがプログラマーの第一の美徳である。これが本書の書かれた理由である。 +\item[短気] +コンピューターが怠惰であるときにプログラマーが感ずる怒り。短気によって書かれたプログラムは、単に労力を削減するばかりではなく、事前に解決しておく。少なくとも、すでに解決済みのように振る舞う。これがプログラマーの第二の美徳である。 +\item[傲慢] +ゼウスも罰したもう過剰なまでの驕り。他人がそしりを入れられぬほどのプログラムを書く推進剤。これがプログラマーの第三の美徳である。 +\end{description} + +これから学ぶ\texttt{array}を実装するためのC++の機能を学ぶときに、このプログラマーの三大美徳のことを頭に入れておこう。 diff --git a/TeX/022-implement-array.tex b/TeX/022-implement-array.tex new file mode 100644 index 0000000..d4aa7ab --- /dev/null +++ b/TeX/022-implement-array.tex @@ -0,0 +1,233 @@ +\hyperchapter{ch22}{配列}{配列} +\index{はいれつ@配列} + +\hypersection{ch2201}{ナイーブなarray実装} + +\texttt{std::array}\index{array@\texttt{array}}を実装してみよう。すでにクラスを作る方法については学んだ。 + +\texttt{std::array}\,は\texttt{T}型の要素を\texttt{N}個保持するクラスだ。この\,\texttt{}\,についてはまだ学んでいないので、今回は\texttt{int}型を3個確保する。いままでに学んだ要素だけで実装してみよう。 + +\begin{lstlisting}[language={C++}] +struct array_int_3 +{ + int m0 ; + int m1 ; + int m2 ; +} ; +\end{lstlisting} + +そして\texttt{operator []}\index{[]@\texttt{[]}}を実装しよう。引数が\texttt{0}なら\texttt{m0}を、\texttt{1}なら\texttt{m1}を、\texttt{2}なら\texttt{m2}を返す。それ以外の値の場合、プログラムを強制的に終了させる標準ライブラリ、\texttt{std::abort}\index{abort@\texttt{abort}}を呼び出す。 + +\begin{lstlisting}[language={C++}] +struct array_int_3 +{ + int m0 ; int m1 ; int m2 ; + + int & operator []( std::size_t i ) + { + switch(i) + { + case 0 : + return m0 ; + case 1 : + return m1 ; + case 2 : + return m2 ; + default : + // 間違った引数 + // 強制終了 + std::abort() ; + } + } +} ; +\end{lstlisting} + +これは動く。では要素数を10個に増やした\texttt{array\_int\_10}はどうなるだろうか。要素数100個はどう書くのだろうか。この方法で実装するとソースコードが膨大になり、ソースコードを出力するソースコードを書かなければならなくなる。これは怠惰で短気なプログラマーには耐えられない作業だ。 + +\hypersection{ch2202}{配列} +\index{はいれつ@配列} + +\texttt{std::array}\index{array@\texttt{array}}を実装するには、\texttt{配列(array)}を使う。 + +\texttt{int}型の要素数10の配列\texttt{a}は以下のように書く。 +\index{はいれつ@配列!ようそ@要素} + +\begin{lstlisting}[language={C++}] +int a[10] ; +\end{lstlisting} + +\texttt{double}型の要素数5の配列\texttt{b}は以下のように書く。 + +\begin{lstlisting}[language={C++}] +double b[5] ; +\end{lstlisting} + +\texttt{配列}の要素数は\texttt{std::array}\,の\texttt{N}と同じようにコンパイル時定数でなければならない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::size_t size ; + std::cin >> size ; + // エラー + int a[size] ; +} +\end{lstlisting} + +配列は\,\texttt{=\{1,2,3\}}\,のように初期化できる。 +\index{はいれつ@配列!しよきか@初期化} + +\begin{lstlisting}[language={C++}] +int a[5] = {1,2,3,4,5} ; +double b[3] = {1.0, 2.0, 3.0 } ; +\end{lstlisting} + +配列の要素にアクセスするには\texttt{operator []}を使う。 +\index{はいれつ@配列![]@\texttt{[]}} + +\begin{lstlisting}[language={C++}] +int main() +{ + int a[5] = {1,2,3,4,5} ; + + // 4 + std::cout << a[3] ; + + a[2] = 0 ; + // {1,2,0,4,5} +} +\end{lstlisting} + +配列にはメンバー関数はない。\texttt{at(i)}や\texttt{size()}のような便利なメンバー関数はない。 + +配列のサイズは\texttt{sizeof}で取得できる。配列のサイズは配列の要素の型のサイズ掛けることの要素数のサイズになる。 +\index{はいれつ@配列!さいず@サイズ}\index{sizeof@\texttt{sizeof}}\index{はいれつ@配列!sizeof@\texttt{sizeof}} + +\begin{lstlisting}[language={C++}] +int main() +{ + auto print = [](auto s){ std::cout << s << "\n"s ; } ; + int a[5] ; + print( sizeof(a) ) ; + print( sizeof(int) * 5 ) ; + + double b [5] ; + print( sizeof(b) ) ; + print( sizeof(double) * 5 ) ; +} +\end{lstlisting} + +\texttt{sizeof}は型やオブジェクトのバイト数を取得するのに対し、\texttt{vector}や\texttt{array}のメンバー関数\texttt{size()}は要素数を取得する。この違いに注意すること。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto print = [](auto s){ std::cout << s << "\n"s ; } ; + std::array a ; + + // aのバイト数 + print( sizeof(a) ) ; + // 要素数: 5 + print( a.size() ) ; + +} +\end{lstlisting} + +\texttt{配列}はとても低級な機能だ。その実装はある型を連続してストレージ上に並べたものになっている。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int a[5] ; +\end{lstlisting} +のような配列があり、\texttt{int}型が4バイトの環境では、20バイトのストレージが確保され、その先頭の4バイトが最初の0番目の要素に、その次の4バイトが1番目の要素になる。最後の4番目の要素は最後の4バイトになる。 + +\begin{figure}[htbp] + \centering + \includegraphics[scale=1.0]{fig/fig22-01.eps} + \label{fig:22-01} +\end{figure} + +配列にはメンバー関数がない上、コピーもできない。\texttt{std::array}\index{array@\texttt{array}}はコピーできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a[5] = {1,2,3,4,5} ; + // エラー、 コピーできない + int b[5] = a ; + + std::array c = {1,2,3,4,5} ; + // OK、コピーできる + std::array d = c ; +} +\end{lstlisting} + +\texttt{配列}は低級で使いにくいので、\texttt{std::array}という配列をラップした高級なライブラリが標準で用意されている。 + +さて、配列の使い方は覚えたので、さっそく\texttt{std::array\_int\_10}を実装してみよう。 + +まずクラスのデータメンバーとして配列を宣言する。 + +\begin{lstlisting}[language={C++}] +struct array_int_10 +{ + int storage[10] ; +} ; +\end{lstlisting} + +配列はコピーできないが、クラスのデータメンバーとして宣言した配列は、クラスのコピーの際に、その対応する順番の要素がそれぞれコピーされる。 + +\begin{lstlisting}[language={C++}] +struct array_int_3 { int storage [3] ; } ; + +int main() +{ + array_int_10 a = { 0,1,2 } ; + + array_int_10 b = a ; + // b.storage[0] == a.storage[0] + // b.storage[1] == a.storage[1] + // b.storage[2] == a.storage[2] +} +\end{lstlisting} + +これはあたかも以下のように書いたかのように動く。 + +\begin{lstlisting}[language={C++}] +struct array_int_3 +{ + int storage[3] ; + + array_int_3( array_int_3 const & other ) + { + std::copy( + std::begin(other.storage), std::end(other.storage), + std::begin(storage) + ) ; + + } +} +\end{lstlisting} + +\texttt{operator []}も実装しよう。 + +\begin{lstlisting}[language={C++}] +struct array_int_10 +{ + int storage[10] ; + + int & operator [] ( std::size_t i ) + { + return storage[i] ; + } +} ; + +int main() +{ + array_int_10 a = {0,1,2,3,4,5,6,7,8,9} ; + a[3] = 0 ; + std::cout << a[6] ; +} +\end{lstlisting} + +\texttt{std::array}にはまださまざまなメンバーがある。1つずつ順番に学んでいこう。 diff --git a/TeX/023-template.tex b/TeX/023-template.tex new file mode 100644 index 0000000..c46cae4 --- /dev/null +++ b/TeX/023-template.tex @@ -0,0 +1,398 @@ +\hyperchapter{ch23}{テンプレート}{テンプレート} + +\hypersection{ch2301}{問題点} + +前章で我々は\,\texttt{'std::array'}\,のようなものを実装した。C++を何も知らなかった我々がとうとうクールなキッズは皆やっているというクラスを書くことができた。素晴らしい成果だ。 + +しかし、我々の書いた\,\texttt{'array\_int\_10'}\,は\,\texttt{'std::array'}\,とは異なる。 + +\begin{lstlisting}[language={C++}] +// 標準ライブラリ +std::array a ; +// 我々のクラス +array_int_10 a ; +\end{lstlisting} + +もし要素数を20個にしたければ\texttt{array\_int\_20}を新たに書かなければならない。すると\texttt{array\_int\_1}とか\texttt{array\_int\_10000}のようなクラスを無数に書かなければならないのだろうか。要素の型を\texttt{double}にしたければ\texttt{array\_double\_10}が必要だ。 + +しかし、そのようなクラスはほとんど同じような退屈な記述の羅列になる。 + +\begin{lstlisting}[language={C++}] +struct array_int_1 +{ + int storage[1] ; + int & operator []( std::size_t i ) + { return storage[i] ; } +} ; + +// array_int_2, array_int_3, ... + +(@\ifTombow\pagebreak\fi@) +struct array_int_10000 +{ + int storage[10000] ; + int & operator []( std::size_t i ) + { return storage[i] ; } +} ; + +struct array_double_1 +{ + double storage[1] ; + double & operator []( std::size_t i ) + { return storage[i] ; } +} ; + +// array_double_2, array_double_3, ... +\end{lstlisting} + +これは怠惰で短気なプログラマーには耐えられない作業だ。C++にはこのような退屈なコードを書かなくても済む機能がある。しかしその前に、引数について考えてみよう。 + +\hypersection{ch2302}{関数の引数} + +1を2倍する関数を考えよう。 + +\begin{lstlisting}[language={C++}] +int one_twice() +{ + return 1 * 2 ; +} +\end{lstlisting} + +上出来だ。では2を2倍する関数を考えよう。 + +\begin{lstlisting}[language={C++}] +int two_twice() +{ + return 2 * 2 ; +} +\end{lstlisting} + +素晴らしい。では3を2倍する関数、4を2倍する関数\ldots と考えていこう。 + +ここまで読んで\texttt{three\_twice}や\texttt{four\_twice}を思い浮かべた読者にはプログラマーに備わるべき美徳が欠けている。怠惰で短気で傲慢なプログラマーはそんなコードを書かない。引数\index{ひきすう@引数}\index{かんすう@関数!ひきすう@引数}を使う。 + +\begin{lstlisting}[language={C++}] +int twice( int n ) +{ + return n * 2 ; +} +\end{lstlisting} + +具体的な値を2倍する関数を値の数だけ書くのは面倒だ。具体的な値は定めず、引数で外部から受け取る。そして引数を2倍して返す。引数は汎用的なコードを任意の値に対して対応させるための機能だ。 + +\hypersection{ch2303}{関数のテンプレート引数} + +\texttt{twice}をさまざまな型に対応させるにはどうすればいいだろう。例えば\texttt{int}型と\texttt{double}型に対応させてみよう。 + +\begin{lstlisting}[language={C++}] +int twice( int n ) +{ + return n * 2 ; +} + +double twice( double n ) +{ + return n * 2.0 ; +} +\end{lstlisting} + +整数型には\texttt{int}のほかにも、\texttt{short}, \texttt{long}, \texttt{long long}といった型がある。浮動小数点数型には\texttt{float}と\texttt{long double}もある。ということは以下のような関数も必要だ。 + +\ifTombow\enlargethispage{5mm}\fi +\begin{lstlisting}[language={C++}] +short twice( short n ) +{ + return n * 2 ; +} + +long twice( long n ) +{ + return n * 2 ; +} + +long long twice( long long n ) +{ + return n * 2 ; +} + +float twice( float n ) +{ + return n * 2 ; +} + +long double twice( long double n ) +{ + return n * 2 ; +} +\end{lstlisting} + +ところで、整数型には符号付きと符号なしの2種類があるということは覚えているだろうか? + +\begin{lstlisting}[language={C++}] +int twice( int n ) +{ + return n * 2 ; +} + +unsigned int twice( unsigned int n ) +{ + return n * 2 ; +} + +// short, long, long longに対しても同様 +\end{lstlisting} + +C++ではユーザーが整数型のように振る舞うクラスを作ることができる。整数型を複数使って巨大な整数を表現できるクラスも作ることができる。 + +\begin{lstlisting}[language={C++}] +// 多倍長整数クラス +// unsigned long longが256個分の整数の実装 +struct bigint +{ + unsigned long long storage[256] ; +} ; + +bigint operator * ( bigint const & right, int ) +{ + return // 実装 +} +\end{lstlisting} + +このクラスに対応するには当然、以下のように書かなければならない。 + +\begin{lstlisting}[language={C++}] +bigint twice( bigint n ) +{ + return n * 2 ; +} +\end{lstlisting} + +そろそろ怠惰と短気を美徳とするプログラマー読者は耐えられなくなってきただろう。これまでのコードは、単にある型\texttt{T}\index{T@\texttt{T}}に対して、 +\begin{lstlisting}[language={C++}] +T twice( T n ) +{ + return n * 2 ; +} +\end{lstlisting} +と書いているだけだ。型\texttt{T}がコピーと\texttt{operator *(T, int)}に対応していればいい。型\texttt{T}の具体的な型について知る必要はない。 + +関数が具体的な値を知らなくても引数によって汎用的なコードを書けるように、具体的な型を知らなくても汎用的なコードを書けるようになりたい。その怠惰と短気に答えるのが\texttt{テンプレート}だ。 + +\hypersection{ch2304}{テンプレート} + +通常の関数が値を引数に取ることができるように、テンプレートは型を引数に取ることができる。 + +テンプレート\index{てんぷれと@テンプレート}は以下のように宣言する。 +\index{template@\texttt{template}} + +\begin{lstlisting}[style=grammar] +template < typename T > + 宣言 +\end{lstlisting} + +テンプレートを関数に使う\texttt{関数テンプレート}\index{かんすうてんぷれと@関数テンプレート}\index{てんぷれと@テンプレート!かんすう@関数〜}は以下のように書く。 + +\begin{lstlisting}[language={C++}] +template < typename T > +T twice( T n ) +{ + return n * 2 ; +} + +int main() +{ + twice( 123 ) ; // int + twice( 1.23 ) ; // double +} +\end{lstlisting} + +\texttt{template < typename T >}\,は型\texttt{T}\index{T@\texttt{T}}を\texttt{テンプレート引数}\index{てんぷれとひきすう@テンプレート引数}\index{てんぷれと@テンプレート!ひきすう@引数}に取る。テンプレートを使った宣言の中では、\texttt{T}が型として扱える。 + +\begin{lstlisting}[language={C++}] +template < typename T > +T f( T n ) +{ + T x = n ; +} +\end{lstlisting} + +\texttt{関数}が\texttt{引数}を取るように、\texttt{テンプレート}は\texttt{テンプレート引数}を取る。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// テンプレートはテンプレート引数template_parameterを取る +template < typename template_parameter > +// 関数は引数function_parameterを取る +// 引数の型はtemplate_parameter +void f( template_parameter function_parameter ) +{ +} +\end{lstlisting} + +\texttt{テンプレート}が「使われる」ときに、\texttt{テンプレート引数}に対する具体的な型が決定する。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void f( T const & x ) +{ + std::cout << x ; +} + +int main() +{ + // Tはint + f( 0 ) ; + // Tはdouble + f( 0.0 ) ; + // Tはstd::string + f( "hello"s ) ; +} +\end{lstlisting} + +\texttt{テンプレート}を使うときに自動で\texttt{テンプレート引数}を推定してくれるが、\texttt{}\,を使うことで明示的に\texttt{テンプレート引数}を\texttt{T}型に指定することもできる。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void f( T const & x ) +{ + std::cout << x ; +} + +int main() +{ + // Tはint + f(0) ; + + // Tはdouble + // int型0からdouble型0.0への変換が行われる + f( 0 ) ; +} +\end{lstlisting} + +\texttt{テンプレート引数}は型ではなく整数型の値を渡すこともできる。 + +\begin{lstlisting}[language={C++}] +template < int N > +void f() +{ + std::cout << N ; +} + +int main() +{ + // Nは0 + f<0>() ; + // Nは123 + f<123>() ; +} +\end{lstlisting} + +ただし、\texttt{テンプレート引数}はコンパイル時にすべてが決定される。なので\texttt{テンプレート引数}に渡せる値はコンパイル時に決定できるものでなければならない。 + +\begin{lstlisting}[language={C++}] +template < int N > +void f() { } + +int main() +{ + // OK + f<1+1>() ; + + int x{} ; + std::cin >> x ; + // エラー + f() ; +} +\end{lstlisting} + +\texttt{テンプレート引数}がコンパイル時に決定されるということは、配列のサイズのようなコンパイル時に決定されなければならない場面でも使えるということだ。 + +\begin{lstlisting}[language={C++}] +template < std::size_t N > +void f() +{ + int buffer[N] ; +} + +int main() +{ + // 配列bufferのサイズは10 + f<10>() ; + // サイズは12 + f<12>() ; +} +\end{lstlisting} + +テンプレートを使ったコードは、与えられた\texttt{テンプレート引数}に対して妥当でなければならない。 + +\begin{lstlisting}[language={C++}] +template < typename vec > +void f( vec & v ) +{ + v.push_back(0) ; +} + +int main() +{ + std::vector a ; + // OK + f( a ) ; + std::vector b ; + // OK + // intからdoubleへの変換 + f( b ) ; + + std::vector c ; + // エラー + // intからstd::stringに変換はできない + f( c ) ; + + // エラー + // int型はメンバー関数push_backを持っていない + f( 0 ) ; +} +\end{lstlisting} + +\hypersection{ch2305}{クラステンプレート} + +\texttt{テンプレート}は\texttt{クラス}にも使える。\texttt{関数テンプレート}は\texttt{関数}の前に\texttt{テンプレート}を書くように、 +\begin{lstlisting}[style=grammar] +template < typename T > // テンプレート +void f( ) ; // 関数 +\end{lstlisting} +\texttt{クラステンプレート}\index{くらすてんぷれと@クラステンプレート}\index{てんぷれと@テンプレート!くらす@クラス〜}は\texttt{クラス}の前に\texttt{テンプレート}を書く。 +\begin{lstlisting}[style=grammar] +template < typename T > // テンプレート +struct S { } ; // クラス +\end{lstlisting} +\index{template@\texttt{template}} + +関数の中でテンプレート引数名を型や値として使えるように、 +\begin{lstlisting}[language={C++}] +template < typename T, T N > +T value() +{ + return N : +} + +int main() +{ + value() ; + value() ; +} +\end{lstlisting} +クラスの中でもテンプレート引数名を型や値として使える。 +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + T storage[N] ; + + T & operator [] ( std::size_t i ) + { + return storage[i] ; + } +} ; +\end{lstlisting} + +なんと、もう\,\texttt{'std::array'}\,が完成してしまった。 diff --git a/TeX/024-more-array.tex b/TeX/024-more-array.tex new file mode 100644 index 0000000..342bb0c --- /dev/null +++ b/TeX/024-more-array.tex @@ -0,0 +1,459 @@ +\hyperchapter{ch24}{arrayをさらに実装}{arrayをさらに実装} + +\texttt{'std::array'}\,をもっと実装していこう。前章では以下のような簡単な\,\texttt{'array'}\,を実装した。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + T storage[N] ; + + T & operator [] ( std::size_t i ) + { + return storage[i] ; + } +} ; +\end{lstlisting} + +実は\texttt{std::array}はこのように書かれていない。この章では、\texttt{'array'}\,の実装を\,\texttt{'std::array'}\,に近づけていく。 + +\hypersection{ch2401}{ネストされた型名} + +エイリアス宣言\index{えいりあすせんげん@エイリアス宣言}を覚えているだろうか。型名に別名\index{かためい@型名!べつめい@別名}を付ける機能だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + using number = int ; + number x = 123 ; +} +\end{lstlisting} + +エイリアス宣言はクラスの中でも使うことができる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + using number = int ; + number data ; +} ; + +int main() +{ + S s{123} ; + + S::number x = s.data ; +} +\end{lstlisting} + +クラスの中で宣言されたエイリアス宣言による型名を、\texttt{ネストされた型名}\index{かためい@型名!ねすとされた@ネストされた〜}という。\texttt{std::array}ではテンプレート引数を直接使う代わりに、\texttt{ネストされた型名}が使われている。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + using value_type = T ; + using reference = T & ; + + using size_type = std::size_t ; + + value_type storage[N] ; + + reference operator [] ( size_type i ) + { + return storage[i] ; + } +} ; +\end{lstlisting} + +こうすると、\texttt{T \&}のようなわかりにくい型ではなく\texttt{reference}のようにわかりやすい名前を使える。さらに、クラス外部から使うこともできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + using array_type = std::array ; + array_type a = {1,2,3,4,5} ; + array_type::value_type x = 0 ; + array_type::reference ref = a[0] ; +} +\end{lstlisting} + +もちろんこれは\texttt{auto}で書くこともできるが、 +\begin{lstlisting}[language={C++}] +int main() +{ + using array_type = std::array ; + array_type a = {1,2,3,4,5} ; + auto x = 0 ; + auto ref = a[0] ; +} +\end{lstlisting} +信じられないことに昔のC++には\texttt{auto}がなかったのだ。その他、さまざまな利点があるのだが、そのすべてを理解するには、まだ読者のC++力が足りない。 + +\hypersection{ch2402}{要素数の取得: size()} +\index{array@\texttt{array}!ようそすう@要素数} + +\texttt{std::array}\,には\texttt{size()}というメンバー関数がある。要素数を返す。 + +\texttt{array}の場合、\texttt{N}を返せばよい。 +\index{array@\texttt{array}!size@\texttt{size}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a ; + a.size() ; // 5 + + std::array b ; + b.size() ; // 10 +} +\end{lstlisting} + +さっそく実装しよう。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + using size_type = std::size_t ; + + size_type size() ; + // ... 省略 +} ; +\end{lstlisting} + +ここでは\texttt{size}の宣言だけをしている。 + +関数は宣言と定義が分割できる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=grammar] +// 関数の宣言 +void f() ; +// 関数の定義 +void f() { } +\end{lstlisting} + +メンバー関数も宣言と定義が分割できる。 + +\begin{lstlisting}[style=grammar] +// クラスの宣言 +struct S +{ + // メンバー関数の宣言 + void f() ; +} ; + +// メンバー関数の定義 +void S::f() { } +\end{lstlisting} + +メンバー関数の定義をクラス宣言の外で書くには、関数名がどのクラスに属するのかを指定しなければならない。これには\texttt{クラス名::}を使う。この場合、\texttt{S::f}だ。 +\index{くらす@クラス!::@\texttt{::}} + +\hypersection{ch2403}{メンバー関数のconst修飾} +\index{くらす@クラス!const@\texttt{const}修飾}\index{const@\texttt{const}修飾}\index{めんばかんすう@メンバー関数!const@\texttt{const}修飾} + +\texttt{const}を付けた変数は値を変更できなくなることはすでに学んだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; + x = 1 ; + int const cx = 0 ; + cx = 0 ; // エラー +} +\end{lstlisting} + +\texttt{const}は変更する必要のない場面でうっかり変更することを防いでくれるとても便利な機能だ。\texttt{'array'}\,は大きいので関数の引数として渡すときにコピーするのは非効率的だ。なのでコピーを防ぐリファレンスで渡したい。 + +\texttt{std::array}\,を受け取って要素をすべて出力する関数を書いてみよう。 + +\begin{lstlisting}[language={C++}] +template < typename Array > +void print( Array & c ) +{ + for ( std::size_t i = 0 ; i != c.size() ; ++i ) + { + std::cout << c[i] ; + } +} + +int main() +{ + std::array a = {1,2,3,4,5} ; + print( a ) ; +} +\end{lstlisting} + +関数\texttt{print}がテンプレートなのは任意の\texttt{T}と\texttt{N}を使った\texttt{std::array}\,を受け取れるようにするためだ。 + +関数のリファレンスを引数として渡すと、関数の中で変更できてしまう。しかし、上の例のような関数\texttt{print}では、引数を書き換える必要はない。この関数を使う人間も、引数を勝手に書き換えないことを期待している。この場合、\texttt{const}を付けることで値の変更を防ぐことができる。 + +\begin{lstlisting}[language={C++}] +template < typename Container > +void print( Container const & c ) +{ + for ( std::size_t i = 0 ; i != c.size() ; ++i ) + { + std::cout << c[i] ; + } +} +\end{lstlisting} + +ではさっそくこれまで実装してきた自作の\texttt{array}クラスを使ってみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + array a = {1,2,3,4,5} ; + + print( a ) ; // エラー +} +\end{lstlisting} + +なぜかエラーになってしまう。 + +この理由はメンバー関数を呼び出しているからだ。 + +クラスのメンバー関数はデータメンバーを変更できる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int data {} ; + void f() + { + ++data ; + } +} ; + +int main() +{ + S s ; + s.f() ; // s.dataを変更 +} +\end{lstlisting} + +ということは、\texttt{const S}はメンバー関数\texttt{f()}を呼び出すことができない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + S s ; + S const & ref = s ; + + ++ref.data ; // エラー + ref.f() ; // エラー +} +\end{lstlisting} + +ではメンバー関数\texttt{f()}がデータメンバーを変更しなければいいのだろうか。試してみよう。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int data {} ; + void f() + { + // 何もしない + } +} ; + +int main() +{ + S const s ; + s.f() ; // エラー +} +\end{lstlisting} + +まだエラーになる。この理由を完全に理解するためには、まだ説明していない\texttt{ポインター}という機能について学ばなければならない。ポインターの説明はこの次の章で行うとして、いまはさしあたり必要な機能である\texttt{メンバー関数のconst修飾}を説明する。 +\index{const@\texttt{const}修飾}\index{めんばかんすう@メンバー関数!const@\texttt{const}修飾} + +\texttt{const}を付けていないメンバー関数を\texttt{const}なクラスのオブジェクトから呼び出せない理由は、メンバー関数がデータメンバーを変更しない保証がないからだ。その保証を付けるのが\texttt{メンバー関数のconst修飾}だ。 + +メンバー関数は関数の引数のあと、関数の本体の前に\texttt{const}を書くことで\texttt{const}修飾できる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + void f() const + { } +} ; + +int main() +{ + S s ; + s.f() ; // OK + + S const cs ; + cs.f() ; // OK + +} +\end{lstlisting} + +\texttt{const}修飾されたメンバー関数は\texttt{const}なクラスのオブジェクトからでも呼び出すことができる。 + +\texttt{const}修飾されたメンバー関数と、\texttt{const}修飾されていないメンバー関数が両方ある場合、クラスのオブジェクトの\texttt{const}の有無によって適切なメンバー関数が呼び出される。 + +\begin{lstlisting}[language={C++}] +struct S +{ + void f() { } // 1 + void f() const { } // 2 +} ; + +int main() +{ + S s ; + s.f() ; // 1 + + S const cs ; + cs.f() ; // 2 +} +\end{lstlisting} + +そしてもう1つ重要なのは、\texttt{const}修飾されたメンバー関数がデータメンバーへのリファレンスを返す場合、 +\begin{lstlisting}[language={C++}] +struct S +{ + int data {} ; + // データメンバーへのリファレンスを返す + int & get() + { + return data ; + } +} ; +\end{lstlisting} +\texttt{const}修飾されたメンバー関数は自分のデータメンバーを変更できないので、データメンバーの値を変更可能なリファレンスを返すことはできない。そのため以下のようになる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int data {} ; + int & get() + { + return data ; + } + + // const版 + // constリファレンスを返すので変更不可 + int const & get() const + { + return data ; + } +} ; +\end{lstlisting} + +自作の\,\texttt{'array'}\,の\texttt{operator []}を\texttt{const}に対応させよう。\texttt{'std::array'}\,は\texttt{const}なリファレンスを\texttt{const\_reference}というネストされた型名にしている。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + T storage[N] ; + + using reference = T & ; + using const_reference = T const & ; + + // 非const版 + reference operator [] ( std::size_t i ) + { + return storage[i] ; + } + // const版 + const_reference operator [] ( std::size_t i ) const + { + return storage[i] ; + } +} ; +\end{lstlisting} + +これで\texttt{const array}にも対応できるようになった。 + +\clearpage +\hypersection{ch2404}{先頭と末尾の要素:front/back} +\index{array@\texttt{array}!せんとうのようそ@先頭の要素}\index{array@\texttt{array}!まつびのようそ@末尾の要素} + +メンバー関数\texttt{front}は最初の要素へのリファレンスを返す。\texttt{back}は最後の要素へのリファレンスを返す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + int & f = a.front() ; // 1 + int & b = a.back() ; // 5 +} +\end{lstlisting} + +\texttt{front/back}には\texttt{reference}を返すバージョンと\texttt{const\_reference}を返すバージョンがある。 +\index{front@\texttt{front}}\index{array@\texttt{array}!front@\texttt{front}} +\index{back@\texttt{back}}\index{array@\texttt{array}!back@\texttt{back}} + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + T storage[N] ; + + using reference = T & ; + using const_reference = T const & ; + + reference front() + { return storage[0] ; } + const_reference front() const + { return storage[0] ; } + + reference back() + { return storage[N-1] ; } + const_reference back() const + { return storage[N-1] ; } + +} ; +\end{lstlisting} + +\clearpage +\hypersection{ch2405}{全要素に値を代入: fill} +\index{array@\texttt{array}!あたいをだいにゆう@値を代入} +\index{fill@\texttt{fill}}\index{array@\texttt{array}!fill@\texttt{fill}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + a.fill(0) ; + // aは{0,0,0,0,0} +} +\end{lstlisting} + +すでにアルゴリズムで実装した\,\texttt{'std::fill'}\,と同じだ。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + T storage[N] ; + + void fill( T const & u ) + { + for ( std::size_t i = 0 ; i != N ; ++i ) + { + storage[i] = u ; + } + } + +} ; +\end{lstlisting} + +しかし、せっかく\texttt{std::fill}があるのだから以下のように書きたい。 + +\begin{lstlisting}[language={C++}] +void fill( T const & u ) +{ + std::fill( begin(), end(), u ) ; +} +\end{lstlisting} + +残念ながらこれは動かない。なぜならば、自作の\texttt{array}はまだ\texttt{begin()/end()}と\texttt{イテレーター}に対応していないからだ。これは次の章で学ぶ。 diff --git a/TeX/025-array-iterator.tex b/TeX/025-array-iterator.tex new file mode 100644 index 0000000..8db3f0b --- /dev/null +++ b/TeX/025-array-iterator.tex @@ -0,0 +1,1171 @@ +\hyperchapter{ch25}{arrayのイテレーター}{arrayのイテレーター} +\index{いてれた@イテレーター}\index{array@\index{array}!いてれた@イテレーター} + +\hypersection{ch2501}{イテレーターの中身} + +自作の\texttt{array}をイテレーターに対応させる前に、まず\,\texttt{'std::array'}\,のイテレーターについてひと通り調べよう。 + +イテレーターは\texttt{std::begin/std::end}で取得する。 +\index{begin@\texttt{begin}}\index{end@\texttt{end}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + auto first = std::begin(a) ; + auto last = std::end(a) ; +} +\end{lstlisting} + +\texttt{std::begin/std::end}は何をしているのか見てみよう。 + +\begin{lstlisting}[language={C++}] +namespace std +{ + template < typename C > + auto begin( C & c ) + { return c.begin() ; } + + template < typename C > + auto begin( C const & c ) + { return c.begin() ; } + +(@\ifTombow\pagebreak\fi@) + template < typename C > + auto end( C & c ) + { return c.end() ;} + + template < typename C > + auto end( C const & c ) + { return c.end() ;} +} +\end{lstlisting} + +なんと、単に引数に対してメンバー関数\texttt{begin/end}を呼び出してその結果を返しているだけだ。 + +さっそく確かめてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + auto iter = a.begin() ; + std::cout << *iter ; // 1 + ++iter ; + std::cout << *iter ; // 2 +} +\end{lstlisting} + +確かに動くようだ。 + +すると自作の\texttt{array}でイテレーターに対応する方法がわかってきた。 + +\begin{lstlisting}[language={C++}] +// イテレーターを表現するクラス +struct array_iterator { } ; + +template < typename T, std::size_t N > +struct array +{ + // イテレーター型 + using iterator = array_iterator ; + + // イテレーターを返すメンバー関数 + iterator begin() ; + iterator end() ; + + // その他のメンバー +} ; +\end{lstlisting} + +イテレーターに対応するには、おおむねこのような実装になるとみていいだろう。おそらく細かい部分で微調整が必要になるが、いまはこれでよしとしよう。ではイテレーターが具体的に何をするかを見ていこう。 + +すでに学んだように、イテレーターは\texttt{operator *}\,で参照する要素の値を取得できる。また書き込みもできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + auto iter = a.begin() ; + int x = *iter ; // 1 + *iter = 0 ; + // aは{0,2,3,4,5} +} +\end{lstlisting} + +問題を簡単にするために、これまでに作った自作の\texttt{array}で最初の要素にアクセスする方法を考えてみよう + +\begin{lstlisting}[language={C++}] +array a = {1,2,3,4,5} ; +int x = a[0] ; // 1 +a[0] = 0 ; +\end{lstlisting} + +このことから考えると、先頭要素を指すイテレーターは\texttt{operator *}\,\index{\protect{*}@\texttt{\protect{*}}}をオーバーロードして先頭要素をリファレンスで返せばよい。 + +\begin{lstlisting}[language={C++}] +struct array_iterator_int_5_begin +{ + array & a ; + + array::reference operator *() + { + return a[0] ; + } +} ; +\end{lstlisting} + +しかし、この実装では\texttt{array}\,にしか対応できない。\texttt{array}\,や\texttt{array}\,には対応できない。なぜなら、\texttt{array}に渡すテンプレート実引数が違うと、別の型になるからだ。 + +\texttt{array\_iterator}でさまざまな\texttt{array}を扱うにはどうすればいいのか。テンプレートを使う。 + +\begin{lstlisting}[language={C++}] +template < typename Array > +struct array_iterator_begin +{ + Array & a ; + + array_iterator_begin( Array & a ) + : a( a ) { } + + // エラー + // Array::referenceは型ではない + Array::reference operator *() + { + return a[0] ; + } +} ; +\end{lstlisting} + +しかしなぜかエラーだとコンパイラーに怒られる。この理由を説明するのはとても難しい。気になる読者は近所のC++グルに教えを請おう。ここでは答えだけを教える。 + +\texttt{T::Y}において、\texttt{T}がテンプレート引数に依存する名前で、\texttt{Y}がネストされた型名の場合、\texttt{typename}キーワード\index{typename@\texttt{typename}}を付けなければならない。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void f() +{ + // typenameが必要 + typename T::Y x = 0 ; +} + +struct S +{ + using Y = int ; +} ; + +int main() +{ + // T = S + // T::Y = int + f() ; +} +\end{lstlisting} + +わかっただろうか。わからなくても無理はない。この問題を理解するにはテンプレートに対する深い理解が必要だ。理解した暁には読者はC++グルとして崇拝されているだろう。 + +さしあたって必要なのは\texttt{Array::reference}の前に\texttt{typename}キーワードを付けることだ。 + +\begin{lstlisting}[language={C++}] +typename Array::reference +array_iterator_begin::operator * () +{ + return a[0] ; +} +\end{lstlisting} + +どうやら最初の要素を読み書きするイテレーターはできたようだ。\texttt{array}側も実装して試してみよう。 + +\texttt{array}側の実装にはまだ現時点では完全に理解できない黒魔術が必要だ。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + T storage[N] ; + // 黒魔術1: array + using iterator = array_iterator_begin ; + iterator begin() + // 黒魔術2: *this + // 黒魔術3: iterator(*this) + { return iterator(*this) ; } +} ; +\end{lstlisting} + +黒魔術1は\texttt{array\_iterator\_begin}\,の中にある。この\texttt{array}は\texttt{array}\,と同じ意味になる。つまり全体としては、\texttt{array\_iterator\_begin}{>}}\,と書いたものと同じだ。クラステンプレートの中でクラス名を使うと、テンプレート実引数をそれぞれ指定したものと同じになる。 + +\begin{lstlisting}[language={C++}] +template < typename A, typename B, typename C > +struct S +{ + void f() + { + // Sと同じ + S s ; + } +} ; +\end{lstlisting} + +黒魔術2は\,\texttt{*this}\index{\protect{*}this@\texttt{\protect{*}this}}だ。\texttt{*this}はメンバー関数を呼んだクラスのオブジェクトへのリファレンスだ。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int data {} ; + // *thisはメンバー関数が呼ばれたSのオブジェクト + S & THIS() { return *this ; } +} ; + +int main() +{ + S s1 ; + + s1.THIS().data = 123 ; + // 123 + std::cout << s1.data ; + +(@\ifTombow\pagebreak\fi@) + S s2 ; + s2.THIS().data = 456 ; + // 456 + std::cout << s2.data ; +} +\end{lstlisting} + +クラスのメンバー関数は対応するクラスのオブジェクトに対して呼ばれる。本来ならばクラスのオブジェクトをリファレンスで取るような形になる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int data {} ; + void set(int x) + { + data = x ; + } +} ; + +int main() +{ + S object ; + object.set(42) ; +} +\end{lstlisting} +というコードは、ほぼ同じことを以下のようにも書ける。 +\begin{lstlisting}[language={C++}] +struct S +{ + int data {} ; +} ; + +void set( S & object, int x ) +{ + object.data = x ; +} + +int main() +{ + S ojbect ; + set( object, 42 ) ; +} +\end{lstlisting} + +クラスの意義は変数と関数を結び付けることだ。このように変数と関数がバラバラではわかりにくいので、メンバー関数という形で\texttt{object.set(...)}のようにわかりやすく呼び出せるし、その際クラス\texttt{S}のオブジェクトは変数\texttt{object}であることが文法上わかるので、わざわざ関数の実引数の形で書くことは省略できるようにしている。 + +メンバー関数の中で、メンバー関数が呼ばれているクラスのオブジェクトを参照する方法が\,\texttt{*this}だ。 + +しかしなぜ\,\texttt{*this}なのか。もっとわかりやすいキーワードでもいいのではないか。なぜ\,\texttt{*}\,が付いているのか。この謎を理解するためには、これまたポインターの理解が必要になるが、それは次の章で学ぶ。 + +黒魔術3は\texttt{iterator(*this)}だ。クラス名に\texttt{()}や\,\texttt{\{\}}\,を続けると、コンストラクターを呼び出した結果のクラスの値を得ることができる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + S() { } + S( int ) { } + S( int, int ) { } +} ; + +int main() +{ + S a = S() ; + S b = S(0) ; + S c = S(1,2) ; + + S d = S{} ; + S e = S{0} ; + S f = S{1,2} ; +} +\end{lstlisting} + +黒魔術の解説が長くなった。本題に戻ろう。 + +\texttt{array\_iterator\_begin}は先頭の要素しか扱えない。イテレーターで先頭以外の別の要素を扱う方法を思い出してみよう。 + +イテレーターは\texttt{operator ++}で次の要素を参照する。\texttt{operator {-}{-}}\,で前の要素を参照する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + auto iter = a.begin() ; + *iter ; // 1 + ++iter ; + *iter ; // 2 + --iter ; + *iter ; // 1 +} +\end{lstlisting} + +この\texttt{operator ++}と\texttt{operator {-}{-}}\,はイテレーターへのリファレンスを返す。なぜならば、以下のように書けるからだ。 + +\begin{lstlisting}[language={C++}] +*++iter ; +*++++iter ; +\end{lstlisting} + +以上を踏まえて、自作の\texttt{array\_iterator}の宣言を書いてみよう。 + +\begin{lstlisting}[language={C++}] +template < typename Array > +struct array_iterator +{ + Array & a ; + + array_iterator( Array & a ) + : a( a ) { } + + // 次の要素を指す + array_iterator & operator ++() ; + // 前の要素を指す + array_iterator & operator --() ; + + // いま参照している要素へのリファレンスを返す + Array::reference operator *() ; +} ; +\end{lstlisting} + +イテレーターの実装で先頭の要素を参照するのは\texttt{a[0]}だった。その次の要素を参照するには\texttt{a[1]}だ。その次の要素は\texttt{a[2]}となり、その前の要素は\texttt{a[1]}だ。 + +\begin{lstlisting}[language={C++}] +array a = {1,2,3,4,5} ; + +auto iter = a.begin() ; // 最初の要素 +*iter ; // 1 +++iter ; // 次の要素 +*iter ; // 2 +--iter ; // 前の要素、 つまり最初の要素 +*iter ; // 1 +\end{lstlisting} + +では最初の要素の前の要素や、最後の要素の次の要素を参照しようとするとどうなるのか。 + +\begin{lstlisting}[language={C++}] +auto first = a.begin() ; +--first ; +*first ; // 最初の前の要素? +auto last = a.end() ; +++last ; // +*last ; // 最後の次の要素? +\end{lstlisting} + +これはエラーになる。このようなエラーを起こさないように務めるのはユーザーの責任で、イテレーター実装者の責任ではない。しかし、必要であればイテレーターの実装者はこのようなエラーを防ぐような実装もできる。それはあとの章で学ぶ。ここでは、こういう場合が起こることは考えなくてもよいとしよう。 + +これを考えていくと、イテレーターの実装をどうすればいいのかがわかってくる。 + +\texttt{array\_iterator}の\texttt{operator *}\,は\texttt{a[i]}を返す。 + +\begin{lstlisting}[language={C++}] +typename Array::reference array_iterator::operator *() +{ + return a[i] ; +} +\end{lstlisting} + +\texttt{i}は\texttt{std::size\_t}型のデータメンバーで、イテレーターが現在参照している\texttt{i}番目の要素を記録している。 + +ということは先ほどの\texttt{array\_iterator}の宣言にはデータメンバー\texttt{i}を追加する修正が必要だ。 + +\begin{lstlisting}[language={C++}] +template < typename Array > +struct array_iterator +{ + Array & a ; + std::size_t i ; + + array_iterator( Array & a, std::size_t i ) + : a( a ), i(i) { } + + // いま参照している要素へのリファレンスを返す + Array::reference operator *() + { + return a[i] ; + } + + // その他のメンバー +} ; +\end{lstlisting} + +そして、\texttt{array}側にも新しい\texttt{array\_iterator}への対応が必要になる。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + using iterator = array_iterator ; + + // 先頭要素のイテレーター + iterator begin() + { + return array_iterator( *this, 0 ) ; + } + + // 最後の次の要素へのイテレーター + iterator end() + { + return array_iterator( *this, N ) ; + } +} ; +\end{lstlisting} + +何度も書くように、インデックスは\texttt{0}から始まる。要素が\(N\)個ある場合、最初の要素は0番目で、最後の要素は\(N-1\)番目だ。 + +インクリメント演算子\texttt{operator ++}にも対応しよう。 + +\begin{lstlisting}[language={C++}] +array_iterator & array_iterator::operator ++() +{ + ++i ; + return *this ; +} +\end{lstlisting} + +これで最低限のイテレーターは実装できた。さっそく試してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + array a = {1,2,3,4,5} ; + + auto iter = a.begin() ; + + std::cout << *iter ; // 1 + ++iter ; + std::cout << *iter ; // 2 +} +\end{lstlisting} + +実は\texttt{operator ++}\index{\protect{++}@\texttt{\protect{++}}}は2種類ある。前置演算子と後置演算子だ。 +\index{ぜんちえんざんし@前置演算子}\index{えんざんし@演算子!ぜんち@前置〜} +\index{こうちえんざんし@後置演算子}\index{えんざんし@演算子!こうち@後置〜} + +\begin{lstlisting}[language={C++}] +int main() +{ + int i = 0 ; + + // 前置 + std::cout << ++i ; // 1 + // 後置 + std::cout << i++ ; // 1 + std::cout << i ; // 2 +} +\end{lstlisting} + +\texttt{int}型では、前置\texttt{operator ++}はオペランドの値を1加算した値にする。後置\texttt{operator ++}はオペランドの値を1加算するが、式を評価した結果は前のオペランドの値になる。 + +\begin{lstlisting}[language={C++}] +++i ; // i+1 +i++ ; // i、ただしiの値はi+1 +\end{lstlisting} + +後置\texttt{operator ++}のオーバーロードは以下のように書く。 + +\begin{lstlisting}[language={C++}] +struct IntLike +{ + int data {} ; + + // 前置 + IntLike & operator ++() + { + ++data ; + return *this ; + } + // 後置 + IntLike operator ++(int) + { + IntLike copy = *this ; + ++*this ; + return copy ; + } +} ; +\end{lstlisting} + +このコードは慣れないとわかりにくいが、妥当な理由のあるコードだ。順番に説明しよう。 + +まず演算子オーバーロードの宣言だ。 + +\begin{lstlisting}[language={C++}] +// 前置 +IntLike & operator ++() ; +// 後置 +IntLike operator ++(int) ; +\end{lstlisting} + +前置はリファレンスを返す。前置演算子の適用結果はさらに変更できるようにするためだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int i { } ; + + ++++i ; +} +\end{lstlisting} + +もちろん、リファレンスを返さない実装は可能だ。そもそも何も値を返さない\texttt{void}を使うことも可能だ。 + +\begin{lstlisting}[language={C++}] +struct S +{ + void operator ++() { } +} ; +\end{lstlisting} + +ただし、その場合\texttt{operator ++}に対して通常期待されるコードが書けなくなる。理由がない限り演算子の自然な挙動を目指すべきだ。 + +前置と後置は区別できる必要がある。C++はその区別の方法として、\texttt{int}型の仮引数を1つ取る\texttt{operator ++}を後置演算子だと認識する文法を採用した。この\texttt{int}型の実引数は前置と後置を区別するためだけのもので、値に意味はない。 + +\begin{lstlisting}[language={C++}] +struct S +{ + void operator ++( int x ) + { + // 値に意味はない。 + std::cout << x ; + } +} ; + +int main() +{ + S s ; + // 演算子としての使用 + s++ ; + // メンバー関数としての使用 + s.operator++(123) ; +} +\end{lstlisting} + +値に意味はないが、演算子として使用した場合、値は\texttt{0}になるというどうでもいい仕様がある。メンバー関数として使用すると好きな値を渡せるというこれまたどうでもいい仕様がある。テストには出ないので覚える必要はない。 + +前置は自然な挙動のためにリファレンスを返すが、後置はリファレンスではなくコピーした値を返す。 + +\begin{lstlisting}[language={C++}] +// 後置 +IntLike IntLike::operator ++(int) +{ + // コピーを作る + IntLike copy = *this ; + // 演算子が呼ばれたオブジェクトをインクリメントする + // 前置インクリメント演算子を呼んでいる + ++*this ; + // 値が変更されていないコピーを返す + return copy ; +} +\end{lstlisting} + +このように実装すると、後置として自然な挙動が実装できる。 + +\texttt{++*this}は後置インクリメント演算子が呼ばれたオブジェクトに対して前置インクリメント演算子を使用している。わかりにくければ前置インクリメントと同じ処理を書いてもいい。 + +\begin{lstlisting}[language={C++}] +IntLike IntLike::operator ++(int) +{ + IntLike copy = *this ; + // 同じ処理 + ++data ; + return copy ; +} +\end{lstlisting} + +\texttt{IntLike}のように簡単な処理であればこれでもいいが、もっと複雑な何行もある処理の場合は、すでに実装した前置インクリメントを呼び出した方が楽だ。コードの重複を省けるのでインクリメントの処理を変更するときに、2箇所に同じ変更をしなくても済む。 + +以上を踏まえて、\texttt{array\_iterator}に後置インクリメント演算子を実装しよう。 + +\begin{lstlisting}[language={C++}] +array_iterator array_iterator::operator ++(int) +{ + array_iterator copy = *this ; + ++*this ; + return copy ; +} +\end{lstlisting} + +デクリメント演算子\texttt{operator {-}{-}}\,の実装はインクリメント演算子\texttt{operator ++}と同じだ。ただ処理がインクリメントではなくデクリメントになっているだけだ。 +\index{{-}{-}@\texttt{{-}{-}}} + +\begin{lstlisting}[language={C++}] +// 前置 +array_iterator & array_iterator::operator --() +{ + -- i ; + return *this ; +} +// 後置 +array_iterator array_iterator::operator --(int) +{ + array_iterator copy = *this ; + --*this ; + return copy ; +} +\end{lstlisting} + +ここまでくればイテレーターに必要な操作はあと1つ。比較\index{いてれた@イテレーター!ひかく@比較}だ。 + +イテレーターは同じ要素を指している場合に等しい。つまり、オペレーター\texttt{a}と\texttt{b}が同じ要素を指しているならば、\texttt{a == b}は\texttt{true}で\texttt{a != b}は\texttt{false}だ。違う要素を指しているならば\texttt{a == b}は\texttt{false}で\texttt{a != b}は\texttt{true}だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + auto a = a.begin() ; + auto b = a.begin() ; + + // true + bool b1 = (a == b) ; + // false + bool b2 = (a != b) ; + ++a ; + // false + bool b3 = (a == b) ; + // true + bool b4 = (a != b) ; +} +\end{lstlisting} + +イテレーターは比較ができるので、イテレーターが終端に到達するまでループを回すことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + // 変数宣言 + for ( auto iter = std::begin(a), + last = std::end(a) ; + // 終了条件 + iter != last ; + // ループごとの処理 + ++iter ) + { + std::cout << *iter ; + } +} +\end{lstlisting} + +イテレーターは比較ができるので、各種アルゴリズムに渡すことができる。 + +\texttt{array\_iterator}の比較は、単にデータメンバー\texttt{i}の比較でよい。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +bool array_iterator::operator ==( array_iterator const & right ) +{ + return i == right.i ; +} +bool array_iterator::operator !=( array_iterator const & right ) +{ + return i != right.i ; +} +\end{lstlisting} + +これで自作の\texttt{array}と\texttt{array\_iterator}はアルゴリズムに渡せるようになった。 + +\begin{lstlisting}[language={C++}] +int main() +{ + array a = {1,2,3,4,5} ; + + std::for_each( std::begin(a), std::end(a), + [](auto x){ std::cout << x ; } ) ; +} +\end{lstlisting} + +\hypersection{ch2502}{残りのイテレーターの実装} + +\texttt{std::array}や\texttt{std::vector}のイテレーターはとても柔軟にできている。 + +例えばイテレーター\texttt{i}の参照する要素を3つ進めたい場合を考えよう。 + +\begin{lstlisting}[language={C++}] +++i ; // 1 +++i ; // 2 +++i ; // 3 +\end{lstlisting} + +これは非効率的だ。もっと効率的なイテレーターの進め方として、\texttt{operator +=}\,がある。 +\index{\protect{+=}@\texttt{\protect{+=}}} + +\begin{lstlisting}[language={C++}] +i += 3 ; +\end{lstlisting} + +\texttt{i += n}はイテレーター\texttt{i}を\texttt{n}回進める。 + +\texttt{operator +}もある。 +\index{\protect{+}@\texttt{\protect{+}}} + +\begin{lstlisting}[language={C++}] +auto j = i + 3 ; +\end{lstlisting} + +イテレーター\texttt{j}の値はイテレーター\texttt{i}を3つ進めた値になる。イテレーター\texttt{i}の値は変わらない。 + +実装は簡単だ。データメンバー\texttt{i}に対して同じ計算をする。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +template < typename Array > +struct array_iterator +{ + Array & a ; + std::size_t i ; + + array_iterator & operator += ( std::size_t n ) + { + i += n ; + return *this ; + } + + array_iterator operator + ( std::size_t n ) const + { + auto copy = *this ; + copy += n ; + return copy ; + } +} ; +\end{lstlisting} + +\texttt{operator +}はオペランドの値を変更しないので\texttt{const}にできる。 + +同様に、\texttt{operator -=}\,と\texttt{operator -}\,もある。上を参考に自分で実装してみよう。 +\index{{-}=@\texttt{{-}=}}\index{\protect{-}@\texttt{\protect{-}}} + +\texttt{operator +}によって任意の\texttt{n}個先の要素を使うことができるようになったので、イテレーター\texttt{i}の\texttt{n}個先の要素を参照したければ、以下のように\,\texttt{*(i+n)}も書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + std::cout << a[3] ; // 4 + + auto i = a.begin() ; + + std::cout << *(i + 3) ; // 4 +} +\end{lstlisting} + +カッコが必要なのは、演算子の評価順序の都合だ。\texttt{*i + 3}は\texttt{(*i) + 3}であり、\texttt{i}の指す要素に対して\,\texttt{+3}される。\texttt{*(i+3)}は\texttt{i}の指す要素の3つ先の要素の値を読む。 + +イテレーター\texttt{i}の\texttt{n}個先の要素を読み書きするのにいちいち\,\texttt{*(i+n)}と書くのは面倒なので、\texttt{std::array}や\texttt{std::vector}のイテレーターには\texttt{operator []}がある。これを使うと\texttt{i[n]}と書ける。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + std::cout << a[3] ; // 4 + + auto i = a.begin() ; + + std::cout << *(i + 3) ; // 4 +} +\end{lstlisting} + +\texttt{operator []}の実装は文字どおり\,\texttt{*(i+n)}と同じことをするだけでよい。 + +\begin{lstlisting}[language={C++}] +template < typename Array > +struct array_iterator +{ + typename Array::reference + operator [] ( std::size_t n ) const + { + return *( *this + n ) ; + } + + // その他のメンバー +} ; +\end{lstlisting} + +この\texttt{operator []}は、\texttt{array\_iterator}のデータメンバーを変更しないので\texttt{const}修飾できる。 + +\texttt{*this}というのはこのイテレーターのオブジェクトなので、それに対してすでに実装済みの\texttt{operator +}を適用し、その結果に\texttt{operator *}を適用している。既存の実装を使わない場合、\texttt{return}文は以下のようになる。 + +\begin{lstlisting}[language={C++}] +return a[i+n] ; +\end{lstlisting} + +こちらの方が一見簡単なように見えるが、\texttt{operator +}\,や\texttt{operator *}\,の実装が複雑な場合、この方法では同じコードを複数の箇所に書かなければならず、コードを修正するときは同じ変更を複数の箇所に行わなければならない。すでに実装したメンバー関数は積極的に使って楽をしていこう。 + +イテレーターは大小比較ができる。 + +\begin{lstlisting}[language={C++}] +a < b ; +a <= b ; +a > b ; +a >= b ; +\end{lstlisting} + +イテレーターの大小はどういう意味を持つのか。\texttt{array}のようにイテレーターが線形に順序のある要素を参照している場合で、前の要素を参照しているイテレーターはあとの要素を参照しているイテレーターより小さい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + auto a = std::begin(a) ; + auto b = a + 1 ; + + a < b ; // true + a <= b ; // true + a > b ; // false + a >= b ; // false +} +\end{lstlisting} + +自作の\texttt{array}の場合、単にデータメンバー\texttt{i}を比較する。 + +\begin{lstlisting}[language={C++}] +template < typename Array > +struct array_iterator +{ + Array & a ; + std::size_t i ; + + bool operator < ( array_iterator const & right ) const + { + return i < right.i ; + } +} +\end{lstlisting} + +残りの演算子も同様に実装できる。 + +\hypersection{ch2503}{constなイテレーター: const\texttt{\_}iterator} +\index{constないてれた@constなイテレーター}\index{いてれた@イテレーター!constな@constな〜} +\index{const\_iterator@\texttt{const\_iterator}}\index{いてれた@イテレーター!const\_iterator@\texttt{const\_iterator}} + +\texttt{std::array}\,は通常のイテレーターである\texttt{std::array::iterator}のほかに、\texttt{const}なイテレーターである\texttt{std::array::const\_iterator}を提供している。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + // iterator + std::array::iterator iter = a.begin() ; +(@\ifTombow\pagebreak\fi@) + // const_iterator + std::array::const_iterator const_iter = a.cbegin() ; +} +\end{lstlisting} + +\texttt{const\_iterator}は\texttt{const iterator}ではない。\texttt{const\_iterator}とはそれ自体が型名だ。\texttt{const}というのは型名を修飾する別の機能だ。 + +そのため、\texttt{const}の有無の2種類の状態と、\texttt{iterator}, \texttt{const\_iterator}の2つの型を掛け合わせた、以下の型が存在する。 + +\begin{itemize} +\item + \texttt{iterator} +\item + \texttt{const iterator} +\item + \texttt{const\_iterator} +\item + \texttt{const const\_iterator} +\end{itemize} + +\begin{lstlisting}[language={C++}] +int main() +{ + using Array = std::array ; + + // iterator + Array::iterator i ; + // const iterator + const Array::iterator c_i ; + // const_iterator + Array::const_iterator ci ; + // const const_iterator + const Array::const_iterator c_ci ; +} +\end{lstlisting} + +\texttt{const\_iterator}は\texttt{iterator}とは別の型だ。自作の\texttt{array}に実装するならば以下のようになる。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + using iterator = array_iterator ; + using const_iterator = array_const_iterator ; +} ; +\end{lstlisting} + +それぞれの型に対して、\texttt{const}キーワードを付けた型とそうでない型が存在する。 + +\texttt{const\_iterator}を得る方法はいくつかある。 + +\ifTombow\pagebreak\fi +%\vskip 1.0zw +\noindent +\(\bullet\) \textsf{\texttt{const}な\texttt{array}の\texttt{begin}/\texttt{end}を呼び出す} + +\begin{lstlisting}[language={C++}] +int main() +{ + // constなarray + const std::array a = {1,2,3,4,5} ; + + // const_iterator + auto i = a.begin() ; +} +\end{lstlisting} + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{\texttt{cbegin}/\texttt{cend}を呼び出す} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + + // const_iterator + auto i = a.cbegin() ; +} +\end{lstlisting} + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{\texttt{iterator}から\texttt{const\_iterator}への変換} + +\begin{lstlisting}[language={C++}] +int main() +{ + using Array = std::array ; + Array a = {1,2,3,4,5} ; + + // iterator + Array::iterator i = a.begin() ; + // iteratorからconst_iteratorへの変換 + Array::const_iterator j = i ; +} +\end{lstlisting} + +\texttt{const}キーワードはすでに学んだように、オブジェクトの値を変更できないようにする機能だ。 + +なぜ\texttt{const\_iterator}が存在するのか。\texttt{const iterator}ではだめなのか。その理由は、\texttt{const iterator}は値の変更ができないためだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + using Array = std::array ; + Array a = {1,2,3,4,5} ; + +(@\ifTombow\pagebreak\fi@) + // const iterator + const Array::iterator iter = a.begin() ; + + // エラー + // constなオブジェクトは変更できない + ++iter ; + + // Ok + // iterは変更していない + auto next_iter = iter + 1 ; +} +\end{lstlisting} + +\texttt{const\_iterator}ならばイテレーター自体の変更はできる。イテレーターが参照する要素の変更はできない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + using Array = std::array ; + Array a = {1,2,3,4,5} ; + + auto citer = a.begin() ; + + // OK + // イテレーター自体の変更 + ++citer ; + + // OK + // 要素を変更しない + std::cout << *citer ; + + // エラー + // 要素を変更している + *citer = 0 ; +} +\end{lstlisting} + +\texttt{const const\_iterator}は\texttt{const\_iterator}の\texttt{const}だ。\texttt{const const\_iterator}は\texttt{const iterator}と同じく、イテレーター自体の変更ができない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + using Array = std::array ; + Array a = {1,2,3,4,5} ; + + // const const_iterator + auto const iter = a.begin() ; + + // エラー + // constなオブジェクトは変更できない + ++iter ; + + // OK + // iterは変更していない + auto next_iter = iter + 1 ; +} +\end{lstlisting} + +\texttt{auto const}もしくは\texttt{const auto}を使うと、変数の型を自動で推定してくれるが、\texttt{const}が付くようになる。 + +\texttt{const\_iterator}はどう実装するのか。まず\texttt{array}にネストされた型名\texttt{const\_iterator}を追加する。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + using iterator = array_iterator ; + using const_iterator = array_const_iterator ; +} ; +\end{lstlisting} + +\texttt{array}に\texttt{const\_iterator}を返す\texttt{cbegin/cend}と、\texttt{const array}のときに\texttt{const\_iterator}を返す\texttt{begin}/\texttt{end}を追加する。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + using iterator = array_iterator ; + using const_iterator = array_const_iterator ; + + // const arrayのときにconst_iteratorを返す + const_iterator begin() const + { return const_iterator(*this, 0) ; } + const_iterator end() const + { return const_iterator(*this, N) ; } + + // 常にconst_iteratorを返す + const_iterator cbegin() const + { return const_iterator(*this, 0) ; } + const_iterator cend() const + { return const_iterator(*this, N) ; } + + // その他のメンバー +} ; +\end{lstlisting} + +あとは\texttt{array\_const\_iterator}\,を実装する。その実装は\texttt{array\_iterator}\,とほぼ同じだ。 + +\begin{lstlisting}[language={C++}] +template < typename Array > +struct array_const_iterator +{ + Array const & a ; + std::size_t i ; + + // コンストラクター + array_const_iterator( Array const & a, std::size_t i ) + : a(a), i(i) { } +} ; +\end{lstlisting} + +ただし、\texttt{const\_iterator}は\texttt{iterator}から変換できるので、 + +\begin{lstlisting}[language={C++}] +int main() +{ + using Array = std::array ; + Array a = {1,2,3,4,5} ; + + // iterator + auto i = a.begin() ; + + // iteratorからconst_iteratorへの変換 + Array::const_iterator j = i ; +} +\end{lstlisting} + +これに対応するために、\texttt{const\_iterator}のコンストラクターは\texttt{iterator}から変換するためのコンストラクターも持つ。 + +\begin{lstlisting}[language={C++}] +template < typename Array > +struct array_const_iterator +{ + Array const & a ; + std::size_t i ; + + // array_iteratorからの変換コンストラクター + array_const_iterator( typename array_iterator::iterator const & iter ) + : a( iter.a ), i( iter.i ) { } +} ; +\end{lstlisting} + +残りのメンバー関数は\texttt{iterator}とほぼ同じだ。 + +例えば\texttt{operator ++}は完全に同じだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// iterator版 +array_iterator & array_iterator::operator++() +{ + ++i ; + return *this ; +} +// const_iterator版 +array_const_iterator & array_const_iterator::operator ++() +{ + ++i ; + return *this ; +} +\end{lstlisting} + +\texttt{operator *}\,や\texttt{operator []}は\texttt{const}なリファレンスを返す。 + +\begin{lstlisting}[language={C++}] +typename Array::const_reference operator *() const +{ + return a[i] ; +} + +typename Array::const_reference operator []( std::size_t i ) const +{ + return *(*this + i) ; +} +\end{lstlisting} + +このために、\texttt{array}クラスにもネストされた型名\texttt{const\_reference}を宣言しておく。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + using const_reference = T const & ; +} ; +\end{lstlisting} + +残りは\texttt{iterator}の実装を参考に読者が自分で実装してみよう。 diff --git a/TeX/026-exception.tex b/TeX/026-exception.tex new file mode 100644 index 0000000..6c73789 --- /dev/null +++ b/TeX/026-exception.tex @@ -0,0 +1,485 @@ +\hyperchapter{ch26}{傲慢なエラー処理:\\例外}{傲慢なエラー処理:例外} +\index{れいがい@例外}\index{えらしより@エラー処理} + +\hypersection{ch2601}{例外を投げる} + +\texttt{std::array}の実装方法はほとんど解説した。読者は\texttt{std::array}の実装方法を知り、確固たる自信の元に\texttt{std::array}を使えるようになった。ただし、1つだけ問題がある。 + +\texttt{"std::array"}\,のユーザーはあらかじめ設定した要素数を超える範囲の要素にアクセスすることができてしまう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 妥当な要素はa[0]のみ + std::array a = {1} ; + + // エラー、 範囲外 + a[1000] = 0 ; +} +\end{lstlisting} + +\texttt{array}を自力で実装できる傲慢な読者としては、ユーザーごときが間違った使い方をできるのが許せない。間違いを起こした時点でエラーを発生させ、問題を知らしめ、対処できるようにしたい。 + +\texttt{operator []}に範囲外チェックを入れるのは簡単だ。問題は、エラーをユーザーに通知する方法がない。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +reference array::operator [] ( std::size_t i ) +{ + // 範囲外チェック + if ( i >= size() ) + { + // エラー検出 + // しかし何をreturnすればいいのだろう + } + + return storage[i] ; +} +\end{lstlisting} + +\texttt{operator []}は伝統的にエラーチェックをしない要素アクセスをするものだ。 + +\texttt{vector}で一番最初に説明した要素アクセスの方法であるメンバー関数\texttt{at}を覚えているだろうか。実はメンバー関数\texttt{at}はエラーチェック\index{えらちえつく@エラーチェック}をする。試してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1} ; + + std::cout << a.at(1000) = 0 ; +} +\end{lstlisting} + +以下が実行結果だ。 + +\begin{lstlisting}[style=terminal] +terminate called after throwing an instance of 'std::out_of_range' + what(): array::at: __n (which is 1000) >= _Nm (which is 1) +\end{lstlisting} + +何やらよくわからないがエラーのようだ。以下のような意味であることがわかる。 + +\begin{lstlisting}[style=terminal] +`std::out_of_range`がthrowされたあとにterminateが呼ばれた + what(): array_at: __n(値は1000) >= _Nm (値は1) +\end{lstlisting} + +どうやらエラーメッセージのようだ。わかりづらいメッセージだが、なんとなく言わんとすることはわかる。\texttt{\_Nm}が\texttt{array}の要素数で、\texttt{\_\_n}がメンバー関数\texttt{at}に渡した実引数だ。要素数\,\texttt{\_Nm}よりも\,\texttt{\_\_n}が大きい。 + +このエラー処理は、「例外」\index{れいがい@例外}を使って行われる。 + +例外は通常の処理をすっ飛ばして特別なエラー処理をする機能だ。何もエラー処理をしない場合、プログラムは終了する。例外を発生させることを、「例外を投げる」という。 + +例外は文字どおり投げるという意味の\texttt{throw}キーワードを使い、何らかの値を投げる(throw)。 +\index{throw@\texttt{throw}} + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// int型の値123を投げる +throw 123 ; + +// double型の値3.14を投げる +throw 3.14 ; + +std::array value = {1,2,3,4,5} ; + +// std::array型の変数valueの値を投げる +throw value ; +\end{lstlisting} + +この例では、\texttt{int}型、\texttt{double}型、\texttt{std::array}\,型の値を投げている。 + +一度例外が投げられると、通常の実行はすっ飛ばされる。 + +以下は\texttt{0}を入力すると例外を投げるプログラムだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 0を入力するなよ、 絶対するなよ + std::cout << "Don't type 0. >"s ; + + int input {} ; + std::cin >> input ; + + /// 入力が0なら例外を投げる + if ( input == 0 ) + throw 0 ; + + // 通常の処理 + std::cout << "Success!\n"s ; +} +\end{lstlisting} + +このプログラムを実行すると、非\texttt{0}を入力した場合、\texttt{"Success!{\textbackslash}n"}\,が出力される。\texttt{0}を入力した場合、例外が投げられる。例外が投げられると、通常の実行はすっ飛ばされる。エラー処理はしていないので、プログラムは終了する。 + +\texttt{std::array}や\texttt{std::vector}のメンバー関数\texttt{at(n)}は\texttt{n}が要素数を超える場合、例外を投げている。 + +\begin{lstlisting}[language={C++}] +array::reference array::at( std::size_t n ) +{ + if ( n >= size() ) + throw 何らかの値 + + return storage[n] ; +} +\end{lstlisting} + +投げる例外は、\texttt{std::out\_of\_range}\index{out\_of\_range@\texttt{out\_of\_range}}というクラスの値だ。このクラスを完全に説明するのは現時点では難しいが、以下のように振る舞うと考えておこう。 + +\begin{lstlisting}[language={C++}] +namespace std { + +struct out_of_range +{ + // エラー内容の文字列を受け取るコンストラクター + out_of_range( std::string const & ) ; + // エラー内容の文字列を返すメンバー関数 + auto what() ; +} ; + +} +\end{lstlisting} + +とりあえず使ってみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::out_of_range err("I am error.") ; + + // I am error. + std::cout << err.what() ; +} +\end{lstlisting} + +コンストラクターでエラー内容を表現した文字列を受け取り、メンバー関数\texttt{what}\index{what@\texttt{what}}でエラー内容の文字列を取得する。 + +必要な情報はすべて学んだ。あとはメンバー関数\texttt{at}を実装するだけだ。 + +\begin{lstlisting}[language={C++}] +array::reference array::at( std::size_t n ) +{ + if ( n >= size() ) + throw std::out_of_range("Error: Out of Range") ; + + return storage[n] ; +} +\end{lstlisting} + +\clearpage +\hypersection{ch2602}{例外を捕まえる} + +現状では、エラーを発見して例外を投げたら即座にプログラムが終了してしまう。投げた例外を途中で捕まえて、プログラムを通常の実行に戻す機能がほしい。その機能が「例外のキャッチ」\index{れいがい@例外!きやつち@キャッチ}だ。 + +例外のキャッチには\texttt{try}キーワードと\texttt{catch}キーワードを使う。 +\index{try@\texttt{try}}\index{catch@\texttt{catch}} + +\begin{lstlisting}[style=grammar] +try { + // 例外を投げるコード +} catch( 型 名前 ) +{ + エラー処理 +} +\end{lstlisting} + +\texttt{try \{\}}ブロックの中で投げられた例外は、\texttt{catch}で型が一致する場合にキャッチされる。例外がキャッチされた場合、\texttt{catch}のブロックが実行される。そして実行が再開される。 + +\begin{lstlisting}[language={C++}] +int main() +{ + + try { + throw 123 ; // int型 + } + // キャッチする + catch( int e ) + { + std::cout << e ; + } + + // 実行される + std::cout << "resumed.\n"s ; +} +\end{lstlisting} + +\texttt{catch}の型と投げられた例外の型が一致しない場合は、キャッチしない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + try { + throw 3.14 ; // double型 + } + // キャッチしない + catch( int e ) { } + +(@\ifTombow\pagebreak\fi@) + // 実行されない + std::cout << "You won't read this.\n"s ; +} +\end{lstlisting} + +\texttt{catch}は複数書くことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + try { + throw "error"s ; // std::string型 + } + // キャッチしない + catch( int e ) { } + // キャッチしない + catch( double e ) { } + // キャッチする + catch( std::string & e ) + { + std::cout << e ; + } +} +\end{lstlisting} + +\texttt{tryブロック}の中で投げられた例外は、たとえ複雑な関数呼び出しの奥底にある例外でもあますところなくキャッチされる。 + +\begin{lstlisting}[language={C++}] +void f() +{ + throw 123 ; +} + +void g() { f() ; } +void h() { g() ; } + + +int main() +{ + try { + h() ; + } + // キャッチされる + catch( int e ) { } +} +\end{lstlisting} + +関数\texttt{h}は関数\texttt{g}を呼び出し、関数\texttt{g}は関数\texttt{f}を呼び出し、関数\texttt{f}は例外を投げる。このように複雑な関数呼び出しの結果として投げられる例外もキャッチできる。 + +すでに学んだように、\texttt{std::array::at}に範囲外のインデックスを渡したときは\texttt{std::out\_of\_range}\index{out\_of\_range@\texttt{out\_of\_range}}クラスが例外として投げられる。これをキャッチしてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {0} ; + + try { a[1000] ; } + catch( std::out_of_range & e ) + { + // エラー内容を示す文字列 + std::cout << e.what() ; + } +} +\end{lstlisting} + +\hypersection{ch2603}{例外による巻き戻し} +\index{れいがい@例外!まきもどし@巻き戻し} + +例外が投げられた場合、その例外が投げられた場所を囲む\texttt{try}ブロックと対応する\texttt{catch}に到達するまで、関数呼び出しが巻き戻される。これを\texttt{スタックアンワインディング}(stack unwinding)\index{すたつくあんわいんでいんぐ@スタックアンワインディング}\index{れいがい@例外!すたつくあんわいんでいんぐ@スタックアンワインディング}という。 + +\begin{lstlisting}[language={C++}] +void f() { throw 0 ; } +void g() { f() ; } +void h() { g() ; } + +int main() +{ + try { h() ; } + catch( int e ) { } + +} +\end{lstlisting} + +この例では、関数\texttt{main}が関数\texttt{h}を呼び出し、その結果として最終的に関数\texttt{f}の中で例外が投げられる。投げられた例外は関数呼び出しを巻き戻して関数\texttt{main}の中の\texttt{try}ブロックまで到達し、対応する\texttt{catch}に捕まる。 + +もし関数\texttt{main}を抜けてもなお対応する\texttt{catch}がない場合はどうなるのか。 + +\begin{lstlisting}[language={C++}] +int main() +{ + throw 0 ; + // 対応するcatchがない +} +\end{lstlisting} + +その場合、\texttt{std::terminate()}\index{terminate@\texttt{terminate}}という関数が呼ばれる。この関数が呼ばれた場合、プログラムは終了する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // プログラムは終了する + std::terminate() ; +} +\end{lstlisting} + +\texttt{tryブロック}はネストできる。その場合、対応する\texttt{catch}が見つかるまで巻き戻しが起こる。 +\index{try@\texttt{try}!ねすと@ネスト} + +\begin{lstlisting}[language={C++}] +void f() +{ + try { throw 0 ; } + catch ( double e ) { } +} + +int main() +{ + try { // try 1 + try { // try 2 + f() ; + } catch( std::string & e ) { } + } catch ( int e ) + { + // ここで捕まる + } +} +\end{lstlisting} + +上のコードは複雑な\texttt{tryブロック}のネストが行われている。プログラムがどのように実行されるのかを考えてみよう。 + +まず関数\texttt{main}が関数\texttt{f}を呼び出す。関数\texttt{f}は例外を投げる。関数\texttt{f}の中の\texttt{try}ブロックは対応する\texttt{catch}がないので関数\texttt{main}に巻き戻る。関数\texttt{main}の内側の\texttt{try}ブロック、ソースコードでは\texttt{//{\nobreak} try 2} とコメントをしている\texttt{try}ブロックの\texttt{catch}には対応しない。さらに上の\texttt{try}ブロックに巻き戻る。\texttt{// try 1}の\texttt{tryブロック}の\texttt{catch}は\texttt{int}型なので、この\texttt{catch}に捕まる。 + +例外が投げられ、\texttt{スタックアンワインディング}による巻き戻しが発生した場合、通常のプログラムの実行は行われない。例えば以下のプログラムは何も出力しない。 + +\begin{lstlisting}[language={C++}] +void f() +{ + throw 0 ; + // 例外を投げたあとの実行 + std::cout << "function f\n"s ; +} + +void g() +{ + f() ; + // 関数fを呼んだあとの実行 + std::cout << "function g\n"s ; +} + +int main() +{ + g() ; + // 関数gを呼んだあとの実行 + std::cout << "function main\n"s ; +} +\end{lstlisting} + +\texttt{スタックアンワインディング}中に通常の実行は行われないが、変数の破棄は行われる。これはとても重要だ。変数が破棄されるとき、デストラクターが実行されるのを覚えているだろうか。 + +\begin{lstlisting}[language={C++}] +struct Object +{ + std::string name ; + // コンストラクター + Object( std::string const & name ) : name(name) + { std::cout << name << " is constructed.\n"s ; } + + // デストラクター + ~Object() + { std::cout << name << " is destructed.\n"s ; } +} ; + +int main() +{ + // 変数objが構築される + Object obj("obj"s) ; + + // 変数objが破棄される +} +\end{lstlisting} + +実行結果 + +\begin{lstlisting}[style=terminal] +obj is constructed. +obj is destructed. +\end{lstlisting} + +例外のスタックアンワインディングでは関数内の変数が破棄される。つまりデストラクターが実行される。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +void f() +{ + Object obj("f"s) ; + throw 0 ; +} + +void g() +{ + Object obj("g"s) ; + f() ; +} + +int main() +{ + Object obj("main"s) ; + + try { + g() ; + } catch( int e ) + { + std::cout << "caught.\n"s ; + } + +} +\end{lstlisting} + +このプログラムを実行した結果は以下のようになる。 + +\begin{lstlisting}[style=terminal] +main is constructed. +g is constructed. +f is constructed. +f is destructed. +g is destructed. +caught. +main is destructed. +\end{lstlisting} + +なぜこの順番に出力されるか考えてみよう。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + プログラムの実行は関数\texttt{main}から始まる。そのためまず\texttt{main}が構築される +\item + 関数\texttt{main}は関数\texttt{g}を呼ぶ。\texttt{g}が構築される +\item + 関数\texttt{g}は関数\texttt{f}を呼ぶ。\texttt{f}が構築される +\item + 関数\texttt{f}は例外を投げるので、\texttt{f}は破棄される +\item + 関数\texttt{g}に巻き戻ったが\texttt{catch}がないのでさらに巻き戻る。\texttt{g}が破棄される +\item + 関数\texttt{main}に巻き戻ったところ対応する\texttt{catch}があるのでスタックアンワインディングは停止する +\item + \texttt{caught.}が出力される +\item + \texttt{main}が破棄される +\end{enumerate} + +例外が投げられると通常の実行は飛ばされるので、例外が投げられるかもしれない処理のあとに、例外の有無にかかわらず絶対に実行したい処理がある場合は、クラスのデストラクターに書くとよい。 + +C++20以降では、標準ライブラリに\texttt{std::scope\_exit}\index{scope\_exit@\texttt{scope\_exit}}が追加される予定だ。\texttt{std::scope\_exit}は渡した関数オブジェクトをデストラクターで実行してくれる。 + +\begin{lstlisting}[language={C++}] +int f() +{ + auto ptr = new ; + std::scope_exit e( [&]{ delete ptr ; } ) ; + + // 処理 +} +\end{lstlisting} + +このように書くと、後続の処理で\texttt{return}で関数から戻ろうが、\texttt{throw}しようが、\texttt{delete ptr}が実行される。 diff --git a/TeX/027-pointer.tex b/TeX/027-pointer.tex new file mode 100644 index 0000000..2c9fdd7 --- /dev/null +++ b/TeX/027-pointer.tex @@ -0,0 +1,8 @@ +\hyperchapter{ch27}{ポインター}{ポインター} +\index{ぽいんた@ポインター} + +ポインターは難しいとよく言われる。世の中にはポインターのためにC言語とC++を挫折し、ほかの軟弱な言語に逃げるプログラマーがいる。ポインターしか解説していない本が出版される。Joel Spolsky\index{Spolsky, Joel}がエッセイを書く。 + +ポインターの理解は優秀なプログラマーとなるために必須である。ポインターを理解できない人間は優秀なプログラマーにはなれない。もし、本書を読んでポインターが理解できない場合、プログラマーには向いていないということだ。 + +ポインターを難しくしている原因には、意味上のポインターと、文法上のポインターと、ポインターの内部実装がある。いずれも難しいが、本書を読めばポインターは完全に理解できる。 diff --git a/TeX/028-pointer-semantics.tex b/TeX/028-pointer-semantics.tex new file mode 100644 index 0000000..6949161 --- /dev/null +++ b/TeX/028-pointer-semantics.tex @@ -0,0 +1,278 @@ +\hypersection{ch2701}{意味上のポインター} +\index{ぽいんた@ポインター!いみじようの@意味上の〜} + +\hypersubsection{ch270101}{リファレンスと同じ機能} + +ポインターはオブジェクトを参照\index{さんしよう@参照}するための機能だ。この点ではリファレンス\index{りふあれんす@リファレンス}と同じ機能を提供している。 + +リファレンスを覚えているだろうか。\texttt{T}型へのリファレンスは\texttt{T}型のオブジェクトそのものではなく、\texttt{T}型のオブジェクトへの参照だ。リファレンスへの操作は、参照したオブジェクトへの操作になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // int型のオブジェクト + int object = 0 ; + + // オブジェクトを変更 + object = 123 ; + + // 123 + std::cout << object ; + + // T型へのリファレンス + // objectを参照する + int & reference = object ; + + // objectが変更される + reference = 456 ; + + // 456 + std::cout << object ; + + // referenceはobjectを参照している + object = 789 ; + + // 参照するobjectの値 + // 789 + std::cout << reference ; +} +\end{lstlisting} + +リファレンスは宣言と同時に初期化する。リファレンスの参照先をあとから変えることはできない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 0 ; + + // rはxを参照する + int & r = x ; + + int y = 1 ; + + // xに1が代入される + r = y ; +} +\end{lstlisting} + +最後の\texttt{r = y ;}はリファレンス\texttt{r}の参照先を\texttt{y}に変えるという意味ではない。リファレンス\texttt{r}の参照先に\texttt{y}の値を代入するという意味だ。 + +ポインターはリファレンスに似ている。並べてみるとほとんど同じ意味だ。 + +\begin{itemize} +\item + \texttt{T}型へのリファレンスは\texttt{T}型のオブジェクトを参照する +\item + \texttt{T}型へのポインターは\texttt{T}型のオブジェクトを参照する +\end{itemize} + +\texttt{T}型へのリファレンス型が\texttt{T \&}\,であるのに対し、\texttt{T}型へのポインター型は\texttt{T *}\,だ。 +\index{りふあれんすがた@リファレンス型}\index{\&@\texttt{\&}}\index{ぽいんた@ポインター!りふあれんすがた@リファレンス型}\index{ぽいんた@ポインター!\&@\texttt{\&}} +\index{ぽいんたがた@ポインター型}\index{\protect{*}@\texttt{\protect{*}}}\index{ぽいんた@ポインター!ぽいんたがた@ポインター型}\index{ぽいんた@ポインター!*@\texttt{*}} + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// intへのリファレンス型 +using ref_type = int & ; +// intへのポインター型 +using ptr_type = int * ; +\end{lstlisting} + +リファレンスの初期化は、単に参照したい変数名をそのまま書けばよかった。 + +\begin{lstlisting}[language={C++}] +int object { } ; +int & reference = object ; +\end{lstlisting} + +ポインターの場合、参照したい変数名に、\texttt{\&}\,を付ける必要がある。 + +\begin{lstlisting}[language={C++}] +int object { } ; +int * pointer = &object ; +\end{lstlisting} + +リファレンスを経由してリファレンスが参照するオブジェクトを操作するには、単にリファレンス名を使えばよかった。 + +\begin{lstlisting}[language={C++}] +// 書き込み +reference = 0 +// 読み込み +int read = reference ; +\end{lstlisting} + +ポインターの場合、ポインター名に\,\texttt{*}\,を付ける必要がある。 + +\begin{lstlisting}[language={C++}] +// 書き込み +*pointer = 0 ; +// 読み込み +int read = *pointer ; +\end{lstlisting} + +ポインター名をそのまま使った場合、それは参照先のオブジェクトの値ではなく、ポインターという値になる。 + +\begin{lstlisting}[language={C++}] +// オブジェクト +int object { } ; + +// オブジェクトのポインター値で初期化 +int * p1 = &object + +// p1のポインター値で代入 +// つまりobjectを参照する +int * p2 = p1 ; +\end{lstlisting} + +このように比較すると、ポインターはリファレンスと同じ機能を提供していることがわかる。実際、リファレンスというのはポインターのシンタックスシュガーにすぎない。ポインターの機能を制限して、文法をわかりやすくしたものだ。 + +\hypersubsection{ch270102}{リファレンスと違う機能} + +リファレンスがポインターの機能制限版だというのであれば、ポインターにあってリファレンスにはない機能は何だろうか。代入と、何も参照しない状態だ。 + +\hypersubsection{ch270103}{代入} +\index{ぽいんた@ポインター!だいにゆう@代入} + +リファレンスは代入ができないが、ポインターは代入ができる。 + +\begin{lstlisting}[language={C++}] +int x { } ; +int y { } ; + +int & reference = x ; +// xにyの値を代入 +// リファレンスの参照先は変わらない +reference = y ; + +int * pointer = &x ; +// pointerの参照先をyに変更 +pointer = &y ; +\end{lstlisting} + +\hypersubsection{ch270104}{何も参照しない状態} + +リファレンスは必ず初期化しなければならない。 + +\begin{lstlisting}[language={C++}] +// エラー、 初期化されていない +int & reference ; +\end{lstlisting} + +そのため、リファレンスは常にオブジェクトを参照している。 + +ポインターは初期化しなくてもよい。 +\index{ぽいんた@ポインター!しよきか@初期化} + +\begin{lstlisting}[language={C++}] +int * pointer ; +\end{lstlisting} + +この場合、具体的に何かを参照していない状態になる。この場合にポインターの値はどうなるかはわからない。初期化のない整数の値がわからないのと同じだ。 + +\begin{lstlisting}[language={C++}] +// 値はわからない +int data ; +\end{lstlisting} + +わからない値の整数を読んだ結果は未定義\index{みていぎ@未定義}\index{ぽいんた@ポインター!みていぎ@未定義}だ。書くことはできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 値はわからない + int data ; + + // 未定義 + std::cout << data ; + + // OK + data = 0 ; +} +\end{lstlisting} + +このプログラムは未定義動作を含むので、プログラム全体がどのように実行されるかも未定義だ。 + +そしてここからがポインターの恐ろしいところだが、ポインターの場合にもこのわからない値は発生する。わからない値を持ったポインターの参照先への読み書きは未定義の挙動を引き起こす。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int * pointer ; + + // 未定義の挙動 + std::cout << *pointer ; + + // 未定義の挙動 + *pointer = 123 ; +} +\end{lstlisting} + +なぜ未定義の挙動になるかというと、わからない値のポインターは、たまたまどこかの妥当なオブジェクトを参照してしまっているかもしれないからだ。 + +未定義の挙動は恐ろしい。未定義の挙動が発生した場合、何が起こっても文句は言えない。なぜならばその挙動は本来存在するはずがないのだから。上のプログラムはコンパイル時にエラーになるかもしれないし、実行時にエラーになるかもしれない。いや、もっとひどいことにはエラーにならないかもしれない。そして人生、宇宙、すべてのものの答えと、あろうことか答えに対する質問まで出力するかもしれない。 + +\hypersubsection{ch270105}{明示的に何も参照しないポインター: nullptr} +\index{nullptr@\texttt{nullptr}} + +ポインターを未初期化にしていると、よくわからない値になってしまう。そのため、何も参照していないことを明示的に示すためのポインターの値、nullポインター値\index{nullぽいんたち@nullポインター値}がある。\texttt{nullptr}だ。 + +\begin{lstlisting}[language={C++}] +int * pointer = nullptr ; +\end{lstlisting} + +\texttt{nullptr}はどんな型へのポインターに対しても、何も参照していない値となる。 + +\begin{lstlisting}[language={C++}] +// doubleへのポインター +double * p1 = nullptr ; + +// std::stringへのポインター +std::string * p2 = nullptr ; +\end{lstlisting} + +C言語とC++では歴史的な理由で、\texttt{nullptr}のほかにも\texttt{NULL}\index{NULL@\texttt{NULL}}もnullポインター値 +\begin{lstlisting}[language={C++}] +int * pointer = NULL ; +\end{lstlisting} +C++ではさらに歴史的な理由で、\texttt{0}もnullポインター値として扱う。 +\begin{lstlisting}[language={C++}] +int * pointer = 0 ; +\end{lstlisting} + +ただし、nullポインター値が実際に0である保証はない。ポインターの値についてはあとで詳しく扱う。 + +\hypersubsection{ch270106}{無効な参照先の作り方} + +ポインターやリファレンスによって参照先が参照される時点では有効だったが、後に無効になる参照先を作ることができてしまう。 + +例えば以下のコードだ。 + +\begin{lstlisting}[language={C++}] +int * f() +{ + // 寿命は関数 + int variable {} ; + + return &variable ; +} + +int main() +{ + int * ptr = f() ; + // エラー + int read = *ptr ; +} +\end{lstlisting} + +このコードの問題は、関数\texttt{f}の中の変数\texttt{variable}の寿命は関数\texttt{f}の中だけで、呼び出し元に戻ったときには寿命が尽きるというところにある。変数\texttt{variable}へのポインターは変数\texttt{variable}の寿命が尽きたあとも存在してしまうので、存在しないオブジェクトにポインター経由でアクセスしようとしてエラーになる。 + +同じ問題はリファレンスでも起きるが、ポインターの方がこの問題を起こしやすい。 + +\begin{lstlisting}[language={C++}] +int & f() +{ + int variable {} ; + return variable ; +} +\end{lstlisting} + diff --git a/TeX/029-pointer-syntax.tex b/TeX/029-pointer-syntax.tex new file mode 100644 index 0000000..2ffb9a9 --- /dev/null +++ b/TeX/029-pointer-syntax.tex @@ -0,0 +1,1406 @@ +\hypersection{ch2702}{文法上のポインター} +\index{ぽいんた@ポインター!ぶんぽうじようの@文法上の〜} + +ポインターが難しいと言われる理由の1つに、ポインターの文法が難しい問題がある。 + +\hypersubsection{ch270201}{ポインターとconstの関係} + +型としてのポインターは、ある型\texttt{T}があるときに、\texttt{T}へのポインター型となる。 + +\texttt{T}へのポインター型は\texttt{T *}\,と書く。 +\index{ぽいんたがた@ポインター型}\index{\protect{*}@\texttt{\protect{*}}}\index{ぽいんた@ポインター!*@\texttt{*}} + +\begin{lstlisting}[language={C++}] +// intへのポインター型 +using t1 = int * ; +// doubleへのポインター型 +using t2 = double * ; +// std::stringへのポインター型 +using t3 = std::string * ; +// std::arrayへのポインター型 +using t4 = std::array * ; +// std::arrayへのポインター型 +using t5 = std::array * ; +\end{lstlisting} + +リファレンスや\texttt{const}も同じだ。 +\index{const@\texttt{const}} + +\begin{lstlisting}[language={C++}] +// int型へのポインター型 +using t1 = int * ; +// int型へのリファレンス型 +using t2 = int & ; +// どちらも同じconstなint型 +using t3 = const int ; +using t4 = int const ; +\end{lstlisting} + +\texttt{const int}と\texttt{int const}は同じ型だ。この場合、\texttt{const}は\texttt{int}型のあとに付いても前に付いても同じ意味になる。 + +すると当然の疑問が生じる。組み合わせるとどうなるのかということだ。 + +ポインター型へのリファレンス型はできる。 +\index{りふあれんすがた@リファレンス型} + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// int *型へのリファレンス +using type = int * & ; +\end{lstlisting} + +リファレンス型へのポインター型はできない。 + +\begin{lstlisting}[language={C++}] +// エラー、 できない +using error = int & * ; +\end{lstlisting} + +理由は、リファレンスへのポインターというのは意味がないからだ。ポインターへのリファレンスは意味がある。 + +リファレンスからポインターの値を得るには、参照先のオブジェクトと同じく\,\texttt{\&}\,\index{\&@\texttt{\&}}を使う。 + +\begin{lstlisting}[language={C++}] +int data { } ; +int & ref = data ; +// &dataと同じ +int * ptr = &ref ; +\end{lstlisting} + +リファレンスは参照先のオブジェクトとまったく同じように振る舞うのでリファレンス自体のポインターの値を得ることはできない。 + +ポインターのリファレンスを得るのは、ポインター以外の値とまったく同じだ。 + +\begin{lstlisting}[language={C++}] +int * ptr = nullptr ; +// ptrを参照する +int * & ref = ptr ; + +int data { } ; +// ptrの値が&dataになる。 +ref = &data ; +\end{lstlisting} + +\texttt{const}とポインターの組み合わせは難しい。 + +まず型\texttt{T}とその\texttt{const}版がある。 +\index{T@\texttt{T}!const@\texttt{const}版} + +\begin{lstlisting}[language={C++}] +using T = int ; +using const_T = const T ; +\end{lstlisting} + +そして型\texttt{T}とそのポインター版がある。 +\index{T@\texttt{T}!ぽいんたばん@ポインター版} + +\begin{lstlisting}[language={C++}] +using T = int ; +using T_pointer = T * ; +\end{lstlisting} + +これを組み合わせると、以下のようになる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// 型T +using T = int ; +// どちらもconstなT +using const_T_1 = const T ; +using const_T_2 = T const ; +// Tへのポインター +using T_pointer = T * ; + +// どちらもconstなTへのポインター +using const_T_pointer_1 = const T * ; +using const_T_pointer_2 = T const * ; + +// Tへのconstなポインター +using T_const_pointer = T * const ; + +// どちらもconstなTへのconstなポインター +using const_T_const_pointer_1 = const T * const ; +using const_T_const_pointer_2 = T const * const ; +\end{lstlisting} + +順番に見ていこう。まずは組み合わせない型から。 + +\begin{lstlisting}[language={C++}] +using T = int ; +// どちらもconstなT +using const_T_1 = const T ; +using const_T_2 = T const ; +// Tへのポインター +using T_pointer = T * ; +\end{lstlisting} + +\texttt{T}はここでは\texttt{int}型だ。\texttt{T}型はどんな型でもよい。 + +\texttt{const T}と\texttt{T const}が同じ型であることを思い出せば、\texttt{const\_T\_1}と\texttt{const\_T\_2}は同じ型であることがわかるだろう。 + +\texttt{T\_pointer}は\texttt{T}へのポインターだ。 + +次を見ていこう。 + +\begin{lstlisting}[language={C++}] +// どちらもconstなTへのポインター +using const_T_pointer_1 = const T * ; +using const_T_pointer_2 = T const * ; +\end{lstlisting} + +これはどちらも同じ型だ。\texttt{const}な\texttt{T}へのポインターとなる。わかりにくければ以下のように書いてもよい。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// constなT +using const_T = const int ; +// constなTへのポインター +using const_T_pointer = const_T * ; +\end{lstlisting} + +実際に使ってみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + const int data = 123 ; + // int const *でもよい + const int * ptr = &data ; + + // 読み込み + int read = *ptr ; +} +\end{lstlisting} + +\texttt{const}な\texttt{int}へのポインターなので、このポインターの参照先を変更することはできない。ポインターは変更できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + const int x {} ; + const int * ptr = &x ; + + // エラー + // constな参照先を変更できない + *ptr = 0 ; + + int y {} ; + // OK + // ポインターはconstではないので値が変更できる + ptr = &y ; +} +\end{lstlisting} + +\texttt{const}なのは\texttt{int}であってポインターではない。\texttt{const int *}、もしくは\texttt{int const *}\,は参照先の\texttt{int}が\texttt{const}なので、参照先を変更することができない。ポインターは\texttt{const}ではないので、ポインターの値は変更できる。 + +\texttt{const}な\texttt{T}型へのリファレンスで\texttt{const}ではない\texttt{T}型のオブジェクトを参照できるように、\texttt{const}な\texttt{T}型へのポインターから\texttt{const}ではない\texttt{T}型のオブジェクトを参照できる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + // constではない + int data { } ; + + // OK + const int & ref = data ; + // OK + const int * ptr = &data ; +} +\end{lstlisting} + +この場合、リファレンスやポインターは\texttt{const int}扱いなので、リファレンスやポインターを経由して読むことはできるが変更はできない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int data = 123 ; + const int * ptr = &data ; + // エラー + // 変更できない + *ptr = 0 ; + + // 変更できる + data = 0 ; +} +\end{lstlisting} + +その次は\texttt{const}なポインターだ。 + +\begin{lstlisting}[language={C++}] +// Tへのconstなポインター +using T_const_pointer = T * const ; +\end{lstlisting} + +これはポインターが\texttt{const}なのであって、\texttt{T}は\texttt{const}ではない。したがってポインターを経由して参照先を変更することはできるが、ポインターの値自体は変更できない型だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int data { } ; + + // constなポインター + int * const ptr = &data ; + + // OK、参照先は変更できる + *ptr = 1 ; + +(@\ifTombow\pagebreak\fi@) + // エラー、 値は変更できない + ptr = nullptr ; +} +\end{lstlisting} + +最後は\texttt{const}な\texttt{T}への\texttt{const}なポインターだ。 + +\begin{lstlisting}[language={C++}] +// どちらもconstなTへのconstなポインター +using const_T_const_pointer_1 = const T * const ; +using const_T_const_pointer_2 = T const * const ; +\end{lstlisting} + +これは\texttt{const}な\texttt{T}なので、ポインターを経由して参照先を変更できないし、\texttt{const}なポインターなのでポインターの値も変更できない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int data = 123 ; + + int const * const ptr = &data ; + + // OK、参照先は読める + int read = *ptr ; + // エラー、 参照先は変更できない + *ptr = 0 ; + // エラー、 ポインターは変更できない + ptr = nullptr ; +} +\end{lstlisting} + +\hypersubsection{ch270202}{ポインターのポインター} +\index{ぽいんた@ポインター!へのぽいんた@〜へのポインター} + +ポインター型というのは、「ある型\texttt{T}へのポインター」という形で表現できる。この型\texttt{T}にはどんな型でも使うことができる。ところで、ポインターというのは型だ。もし\texttt{T}がポインター型の場合はどうなるのだろう。 + +例えば、「\texttt{T}型へのポインター型」で、型\texttt{T}が「\texttt{U}型へのポインター型」の場合、全体としては「\texttt{U}型へのポインター型へのポインター型」になる。これはC++の文法では\texttt{U **}\,となる。 + +C++のコードで確認しよう。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[language={C++}] +// 適当なU型 +using U = int ; +// ポインターとしてのT型 +using T = U * ; +// T型へのポインター型 +// つまりU型へのポインター型へのポインター型 +// つまりU ** +using type = T * ; +\end{lstlisting} + +具体的に書いてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // int + int x = 123 ; + // intへのポインター + int * p = &x ; + // intへのポインターのポインター + int ** pp = &p ; + + + // 123 + // ポインターを経由したポインターを経由したxの読み込み + int value1 = **pp ; + + int y = 456 ; + // ポインターを経由した変数pの変更 + *pp = &y ; + + // 456 + // ポインターを経由したポインターを経由したyの読み込み + int value2 = **pp ; +} +\end{lstlisting} + +\texttt{x}は\texttt{int}だ。\texttt{p}は\texttt{int}へのポインターだ。ここまではいままでどおりだ。 + +\texttt{pp}は\texttt{int **}\,\index{\protect{**}@\texttt{\protect{**}}}\index{ぽいんた@ポインター!\protect{**}@\texttt{\protect{**}}}という型で、「\texttt{int}へのポインターへのポインター」型だ。このポインターの値のためには「\texttt{int}へのポインターのポインター」が必要だ。変数\texttt{p}のポインターは\,\texttt{\&p}で得られる。この場合、変数\texttt{p}は「\texttt{int}へのポインター」でなければならない。そうした場合、変数\texttt{p}のポインターは「\texttt{int}へのポインターのポインター」型の値になる。 + +変数\texttt{pp}は「\texttt{int}へのポインターのポインター」だ。変数\texttt{pp}の参照先の変数\texttt{p}を読み書きするには、\texttt{*pp}と書く。これはまだ「\texttt{int}へのポインター」だ。ここからさらに参照先の\texttt{int}型のオブジェクトにアクセスするには、その結果にさらに\,\texttt{*}\,を書く。結果として\,\texttt{**pp}となる。 + +わかりにくければ変数に代入するとよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int object { } ; + + int * a = &object ; + int ** b = &a ; + + // cとaは同じ値 + int * c = *pointer_to_pointer_to_object ; + + // objectに1が代入される + *c = 1 ; + // objectに2が代入される + **b = 2 ; +} +\end{lstlisting} + +リファレンスを使うという手もある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int object { } ; + + int * a = &object ; + int ** b = &a ; + + int & r1 = *a ; + + // objectに1が代入される + r1 = 1 ; + + int &r2 = **b ; + + // objectに2が代入される + r2 = 2 ; +} +\end{lstlisting} + +「ポインターへのポインター」があるということは、「ポインターへのポインターへのポインター」もあるということだろうか。もちろんある。 +\index{ぽいんた@ポインター!へのぽいんたへのぽいんた@〜へのポインターへのポインター} + +\begin{lstlisting}[language={C++}] +// intへのポインターへのポインターへのポインター型 +using type = int *** ; + +// intへのポインターへのポインターへのポインターへのポインター型 +// int **** +using pointer_to_type = type * ; +\end{lstlisting} + +もちろん\texttt{const}も付けられる。 + +\begin{lstlisting}[language={C++}] +using type = int const * const * const * const ; +\end{lstlisting} + +\clearpage +\hypersubsection{ch270203}{関数へのポインター} +\index{かんすう@関数!へのぽいんた@〜へのポインター}\index{ぽいんた@ポインター!かんすうへの@関数への〜} + +関数へのポインターを説明する前に、まず型としての関数を説明しなければならない。 + +関数にも型\index{かんすう@関数!かた@型}がある。例えば以下のような関数、 +\begin{lstlisting}[language={C++}] +int f( int ) ; +double g( double, double ) ; +\end{lstlisting} +の型は、 +\begin{lstlisting}[language={C++}] +using f_type = int ( int ) ; +using g_type = double ( double, double ) ; +\end{lstlisting} +となる。関数から関数名を取り除いたものが関数の型だ。すると関数へのポインター型は以下のようになる。 + +\begin{lstlisting}[language={C++}] +using f_pointer = f_type * ; +using g_pointer = g_type * ; +\end{lstlisting} + +さっそく試してみよう。 + +\begin{lstlisting}[language={C++}] +// 実引数を出力して返す関数 +int f( int x ) +{ + std::cout << x ; + return x ; +} + +int main() +{ + using f_type = int ( int ) ; + using f_pointer = f_type * ; + + f_pointer ptr = &f ; + + // 関数へのポインターを経由した関数呼び出し + (*ptr)(123) ; +} +\end{lstlisting} + +動くようだ。最後の関数呼び出しはまず参照先を得て\texttt{(*ptr)}、その後に関数呼び出し\texttt{(123)}をしている。これは面倒なので、C++では特別に関数へのポインターはそのまま関数呼び出しすることができるようになっている。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// 関数へのポインターを経由した関数呼び出し +ptr(123) ; +\end{lstlisting} + +ところで、変数\texttt{ptr}の宣言を、\texttt{f\_pointer}というエイリアス宣言を使わずに書くと、以下のようになる。 + +\begin{lstlisting}[language={C++}] +// 適当な関数 +int f( int ) { return 0 ; } + +// 変数ptrの宣言 +// int (int)へのポインター +int (*ptr)(int) = &f ; +\end{lstlisting} + +なぜこうなるのか。これを完全に理解するためにはC++の宣言子(declarator)という文法の詳細な理解が必要だ。 +\index{せんげんし@宣言子}\index{C++!せんげんし@宣言子} + +ここでは詳細を飛ばして重要な部分だけ伝えるが、型名のうちポインターであることを指定する\,\texttt{*}\,\index{\protect{*}@\texttt{\protect{*}}}は、名前にかかる。 + +\begin{lstlisting}[language={C++}] +// この*はnameにかかる +int * name ; +\end{lstlisting} + +つまり以下のような意味だ。 + +\begin{lstlisting}[language={C++}] +int (*name) ; +\end{lstlisting} + +型名だけを指定する場合、名前が省略される。 + +\begin{lstlisting}[language={C++}] +// 名前が省略されている +using type = int * ; +\end{lstlisting} + +つまり以下のような意味だ。 + +\begin{lstlisting}[language={C++}] +using type = int (*) ; +\end{lstlisting} + +そのため、\texttt{int * name( int )}と書いた場合、これは「\texttt{int}型の引数を取り、\texttt{int}型へのポインターを戻り値として返す関数」となる。 + +\begin{lstlisting}[language={C++}] +int * f( int ){ return nullptr ; } +\end{lstlisting} + +\ifTombow\pagebreak\fi +そうではなく、「\texttt{int}型の引数を取り\texttt{int}型の戻り値を返す関数へのポインター」を書きたい場合は、 + +\begin{lstlisting}[language={C++}] +using type = int (*)(int) ; +\end{lstlisting} +としなければならない。 + +変数の名前を入れる場所は以下のとおり。 + +\begin{lstlisting}[language={C++}] +using type = +int +( * // ポインター +// ここに変数が省略されている +)(int) ; +\end{lstlisting} + +なので、 +\begin{lstlisting}[language={C++}] +int (*ptr)(int) = nullptr ; +\end{lstlisting} +となる。あるいは以下のように書いてもいい。 + +\begin{lstlisting}[language={C++}] +using function_type = int (int) ; +using function_pointer_type = function_type * ; + +function_pointer_type ptr = nullptr ; +\end{lstlisting} + +関数へのポインターは型であり、値でもある。値であるということは、関数は引数として関数へのポインターを受け取ったり、関数へのポインターを返したりできるということだ。 + +さっそく書いてみよう。 + +\begin{lstlisting}[language={C++}] +int f( int x ) { return x ; } +using f_ptr = int (*) (int ) ; +// 関数へのポインターを引数に取り +// 関数へのポインターを戻り値として返す +// 関数g +f_ptr g( f_ptr p ) +{ + p(0) ; + return p ; +} + +int main() +{ + g(&f) ; +} +\end{lstlisting} + +これは動く。ところでこの関数\texttt{g}へのポインターはどう書けばいいのだろうか。つまり、 +\begin{lstlisting}[language={C++}] +auto ptr = &g ; +\end{lstlisting} +を\texttt{auto}を使わずに書くとどうなるのだろうか。 + +以下のようになる。 + +\begin{lstlisting}[language={C++}] +int (*(*ptr)(int (*)(int)))(int) = &g ; +\end{lstlisting} + +なぜこうなるのか。分解すると以下のようになる。 + +\begin{lstlisting}[language={C++}] +int (* // 戻り値型前半 + (*ptr) // 変数名 + (// 関数の引数 + int (*)(int) // 引数としての関数へのポインター + )// 関数の引数 + +)(int) // 戻り値の型後半 + + = &g ; // 初期化子 +\end{lstlisting} + +これはわかりにくい。戻り値の型を後ろに書く文法を使うと少し読みやすくなる。 + +\begin{lstlisting}[language={C++}] +auto (*ptr)( int (*)(int) ) -> int (*)(int) = &g ; +\end{lstlisting} + +これを分解すると以下のようになる。 + +\begin{lstlisting}[language={C++}] +auto // プレイスホルダー +(*ptr) // 変数名 +( int (*)(int) ) // 引数 +-> int (*)(int) // 戻り値の型 += &g ; // 初期化子 +\end{lstlisting} + +もちろん、これでもまだわかりにくいので、エイリアス宣言を使った方がよい。 + +\begin{lstlisting}[language={C++}] +using func_ptr = int(*)(int) ; + +auto (*ptr)(func_ptr) -> func_ptr = &g ; +\end{lstlisting} + +\clearpage +\hypersubsection{ch270204}{配列へのポインター} +\index{はいれつ@配列!へのぽいんた@〜へのポインター}\index{ぽいんた@ポインター!はいれつへの@配列への〜} + +配列へのポインターについて学ぶ前に、配列の型について学ぶ必要がある。 +\index{かた@型}\index{はいれつ@配列!かた@型} + +配列の型は、要素の型を\texttt{T}、要素数を\texttt{N}とすると、\texttt{T [N]}となる。 + +\begin{lstlisting}[language={C++}] +// 要素型int、要素数5の配列型 +using int5 = int [5] ; +// 要素型double、要素数10の配列型 +using double10 = double [10] ; +\end{lstlisting} + +関数型と同じく、ポインター宣言子である\,\texttt{*}\,は名前に付く。 + +\begin{lstlisting}[language={C++}] +// 要素型int、要素数5の配列へのポインター型 +using pointer_to_array_type = int (*)[5] ; + +int main() +{ + int a[5] ; + pointer_to_array_type ptr = &a ; +} +\end{lstlisting} + +エイリアス宣言を使わない変数の宣言は以下のようになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a[5] ; + int (*p)[5] = &a ; +} +\end{lstlisting} + +配列とポインターは密接に関係している。そのため、配列名は配列の先頭要素へのポインターに暗黙に変換される。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a[5] = {1,2,3,4,5} ; + + // &a[0]と同じ + int * ptr = a ; +} +\end{lstlisting} + +配列とポインターの関係については、ポインターの詳細で詳しく説明する。 + +\hypersubsection{ch270205}{ポインター型の作り方} + +\texttt{T}型へのポインター型は\texttt{T *}\,で作ることができる。 +\index{ぽいんた@ポインター!かた@型}\index{T*@\texttt{T *}} + +ただし、\texttt{T}が\texttt{int (int)}のような関数型である場合は、\texttt{int (*)(int)}になる。配列型の場合は要素数\texttt{N}まで必要で\texttt{T (*)[N]}になる。 + +エイリアス宣言で型に別名を付けると\texttt{T *}\,でよくなる。 + +\begin{lstlisting}[language={C++}] +using function_type = int (int) ; +using pointer_to_function_type = function_type * ; +\end{lstlisting} + +ポインターの型を書く際に、このようなことをいちいち考えるのは面倒だ。ここで必要のなのは、ある型\texttt{T}を受け取ったときに型\texttt{T *}\,を得るような方法だ。ところで、物覚えのいい読者は前にも似たような文章を読んだことに気が付くだろう。そう、テンプレートだ。 + +テンプレートは型を引数化できる機能だ。いままではクラスや関数にしか使っていなかったが、実はエイリアス宣言にも使えるのだ。 +\index{てんぷれと@テンプレート} + +\begin{lstlisting}[language={C++}] +template < typename T > +using type = T ; +\end{lstlisting} + +これは引数と同じ型になるエイリアステンプレートだ。使ってみよう。 +\index{えいりあすてんぷれと@エイリアステンプレート}\index{てんぷれと@テンプレート!えいりあす@エイリアス〜} + +\begin{lstlisting}[language={C++}] +template < typename T > using type = T ; + +// aはint +type a = 123 ; +// bはdouble +type b = 1.23 ; +// cはstd::vector +type> c = {1,2,3,4,5} ; +\end{lstlisting} + +\texttt{using type = int ;}というエイリアス宣言があるとき\texttt{type}の型は\texttt{int}だ。エイリアス宣言は新しい\texttt{type}という型を作るわけではない。 + +同様に、上のエイリアステンプレート\texttt{type}による\texttt{type}\,の型は\texttt{int}だ。新しい\texttt{type}\,という型ができるわけではない。 + +もう少し複雑な使い方もしてみよう。 + +\begin{lstlisting}[language={C++}] +// int +type> a = 0 ; +// int +type>> b = 0 ; +\end{lstlisting} + +\texttt{type}\,の型は\texttt{int}なので、それを引数に渡した\texttt{type< type >}\,も\texttt{int}だ。\texttt{type}\,をいくつネストしようとも\texttt{int}になる。 + +\begin{lstlisting}[language={C++}] +// std::vector +std::vector< type > a = {1,2,3,4,5} ; +// std::vector +type>> b = {1,2,3,4,5} ; +\end{lstlisting} + +\texttt{type}\,は\texttt{int}なので、\texttt{std::vector}{>}}\,は\texttt{std::vector}\,になる。それをさらに\texttt{type}\,で囲んでも同じ型だ。 + +\texttt{type}\,は面白いが何の役に立つのだろうか。\texttt{type}\,は型として使える。つまり\texttt{type *}\,はポインターとして機能するのだ。 +\index{type@\texttt{type}} + +\begin{lstlisting}[language={C++}] +template < typename T > using type = T ; + +// int * +type * a = nullptr ; +// int (*)(int) +type * b = nullptr ; +// int (*) [5] +type * c = nullptr ; +\end{lstlisting} + +\texttt{type *}\,は\texttt{int *}\,型だ。\texttt{type *}\,は\texttt{int(*)(int)}型だ。\texttt{type *}\,は\texttt{int (*) [5]}型だ。これでもう\,\texttt{*}\,をどこに書くかという問題に悩まされることはなくなった。 + +しかしわざわざ\texttt{type *}\,と書くのは依然として面倒だ。\texttt{T}型は引数で受け取っているのだから、最初からポインターを返してどうだろうか。 + +\begin{lstlisting}[language={C++}] +template < typename T > +using add_pointer_t = T * ; +\end{lstlisting} + +さっそく試してみよう。 + +\begin{lstlisting}[language={C++}] +// int * +add_pointer_t a = nullptr ; +// int ** +add_pointer_t b = nullptr ; +// int(*)(int) +add_pointer_t c = nullptr ; +// int(*)[5] +add_pointer_t d = nullptr ; +\end{lstlisting} + +どうやら動くようだ。もっと複雑な例も試してみよう。 + +\begin{lstlisting}[language={C++}] +// int ** +add_pointer_t> a = nullptr ; +\end{lstlisting} + +\texttt{add\_pointer\_t}\,は\texttt{int *}\,なので、その型を\texttt{add\_pointer\_t}\,で囲むとその型へのポインターになる。結果として\texttt{int **}\,になる。 + +ここで実装した\texttt{add\_pointer\_t}\,は\texttt{T}がリファレンスのときにエラーになる。 + +\begin{lstlisting}[language={C++}] +template < typename T > using add_pointer_t = T * ; +// エラー +add_pointer_t ptr = nullptr ; +\end{lstlisting} + +実は標準ライブラリにも\texttt{std::add\_pointer\_t}\,\index{add\_pointer\_t@\texttt{add\_pointer\_t}}があり、こちらはリファレンス\texttt{U \&}\,を渡しても、\texttt{U *}\,になる。 + +\begin{lstlisting}[language={C++}] +// OK +// int * +std::add_pointer_t ptr = nullptr ; +\end{lstlisting} + +標準ライブラリ\texttt{std::add\_pointer\_t}\,は、\texttt{T}がリファレンス型の場合、リファレンスは剝がしてポインターを付与するという実装になっている。これをどうやって実装するかについてだが、まだ読者の知識では実装できない。テンプレートについて深く学ぶ必要がある。いまは標準ライブラリに頼っておこう。 + +標準ライブラリにはほかにも、ポインターを取り除く\texttt{std::remove\_pointer\_t}\,もある。 + +\begin{lstlisting}[language={C++}] +// int +std::remove_pointer_t a = 0 ; +// int +std::remove_pointer_t< + std::add_pointer_t + > b = 0 ; +\end{lstlisting} + +\hypersubsection{ch270206}{クラスへのポインター} + +クラスへのポインターはいままでに学んだものと同じ文法だ。 +\index{ぽいんた@ポインター!くらすへの@クラスへの〜}\index{くらす@クラス!へのぽいんた@〜へのポインター} + +\begin{lstlisting}[language={C++}] +struct C { } ; + +int main() +{ + C object ; + C * pointer = &object ; +} +\end{lstlisting} + +ただし、ポインターを経由してメンバーにアクセスするのが曲者だ。 + +以下のようなメンバーにアクセスするコードがある。 + +\begin{lstlisting}[language={C++}] +struct C +{ + int data_member ; + void member_function() {} +} ; + +int main() +{ + C object ; + + object.data_member = 0 ; + object.member_function() ; +} +\end{lstlisting} + +これをポインターを経由して書いてみよう。 + +以下のように書くとエラーだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + C object ; + C * pointer = &object ; + + // エラー + *pointer.data_member = 0 ; + // エラー + *pointer.member_function() ; +} +\end{lstlisting} + +この理由は演算子の優先順位の問題だ。上の式は以下のように解釈される。 + +\begin{lstlisting}[language={C++}] +*(pointer.data_member) = 0 ; +*(pointer.member_function()) ; +\end{lstlisting} + +ポインターを参照する演算子\,\texttt{*}\,よりも、演算子ドット(\,\texttt{'.'}\,)の方が演算子の優先順位が高い。 + +このような式を可能にする変数\texttt{pointer}とは以下のようなものだ。 + +\begin{lstlisting}[language={C++}] +struct Pointer +{ + int data = 42 ; + int * data_member = &data ; + int * member_function() + { + return &data ; + } +} ; + +int main() +{ + Pointer pointer ; + + *pointer.data_member = 0; + *pointer.member_function() ; +} +\end{lstlisting} + +\texttt{pointer.data\_member}はポインターなのでそれに演算子\,\texttt{*}\,を適用して参照した上で\texttt{0}を代入している。 + +\texttt{pointer.member\_function()}は関数呼び出しで戻り値としてポインターを返すのでそれに演算子\,\texttt{*}\,を適用している。 + +演算子\,\texttt{*}\,を先にポインターの値である\texttt{pointer}に適用するには、括弧を使う。 + +\begin{lstlisting}[language={C++}] +(*pointer).data_member = 0 ; +(*pointer).member_function() ; +\end{lstlisting} + +リファレンスを使ってポインターを参照した結果をリファレンスに束縛して使うこともできる。 + +\begin{lstlisting}[language={C++}] +C & ref = *pointer ; +ref.data_member = 0 ; +ref.member_function() ; +\end{lstlisting} + +ただし、ポインターを介してクラスを扱う際に、毎回括弧を使ったりリファレンスを使ったりするのは面倒なので、簡単なシンタックスシュガーとして演算子\,\texttt{->}\,が用意されている。 +\index{->@\texttt{->}} + +\begin{lstlisting}[language={C++}] +pointer->data_member = 0 ; +pointer->member_function() ; +\end{lstlisting} + +\texttt{a->b}は、\texttt{(*(a))->b}と同じ意味になる。そのため、上は以下のコードと同じ意味になる。 + +\begin{lstlisting}[language={C++}] +(*(pointer)).data_member = 0 ; +(*(pointer)).member_function() ; +\end{lstlisting} + +\clearpage +\hypersubsection{ch270207}{thisポインター} +\index{thisぽいんた@thisポインター}\index{ぽいんた@ポインター!this} + +メンバー関数はクラスのデータメンバーにアクセスできる。このときのデータメンバーはメンバー関数が呼ばれたクラスのオブジェクトのサブオブジェクトになる。 + +\begin{lstlisting}[language={C++}] +struct C +{ + int data { } ; + + void set( int n ) + { + data = n ; + } +} ; + +int main() +{ + C a ; + C b ; + + // a.dataを変更 + a.set(1) ; + // b.dataを変更 + b.set(2) ; +} +\end{lstlisting} + +すでに説明したように、メンバー関数が自分を呼び出したクラスのオブジェクトのサブオブジェクトを参照できるのは、クラスのオブジェクトへの参照を知っているからだ。内部的には以下のような隠し引数を持つコードが生成されたかのような挙動になる。 + +\begin{lstlisting}[language={C++}] +// コンパイラーが生成するコードのたとえ +struct C +{ + int data { } ; +} ; + +// 隠し引数 +void set( C & obj, int n ) +{ + obj.data = n ; +} +\end{lstlisting} + +つまり、メンバー関数は自分を呼び出したクラスのオブジェクトへの参照を知っている。その参照にアクセスする方法が\texttt{this}キーワード\index{this@\texttt{this}}だ。 + +\texttt{this}キーワードはクラスのメンバー関数の中で使うと、メンバー関数を呼び出したクラスのオブジェクトへのポインターとして扱われる。 + +\begin{lstlisting}[language={C++}] +struct C +{ + int data { } ; + + void set( int n ) + { + // このメンバー関数を呼び出したクラスのオブジェクトへのポインター + C * pointer = this ; + this->data = n ; + } +} ; +\end{lstlisting} + +先ほど、関数\texttt{C::set}の中で\texttt{data = n ;}と書いたのは、\texttt{this->data = n ;}と書いたのと同じ意味になる。 + +\texttt{this}はリファレンスではなくてポインターだ。この理由は歴史的なものだ。本来ならばリファレンスの方がよいのだが、いまさら変更できないのでポインターになっている。わかりにくければリファレンスに束縛してもよい。 + +\begin{lstlisting}[language={C++}] +struct S +{ + void f() + { + auto & this_ref = *this ; + } +} ; +\end{lstlisting} + +\texttt{const}なメンバー関数の中では、\texttt{this}の型も\texttt{const}なクラス型へのポインターになる。 + +\begin{lstlisting}[language={C++}] +struct S +{ + void f() + { + // thisの型はS * + S * pointer = this ; + } + + void f() const + { + // thisの型はS const * + S const * pointer = this ; + } +} ; +\end{lstlisting} + +この理由は、\texttt{const}なメンバー関数はクラスのオブジェクトへの参照として\texttt{const}なリファレンスを隠し引数として持つからだ。 + +\begin{lstlisting}[language={C++}] +// コンパイラーが生成するコードのたとえ +struct S { } ; + +// 非constなメンバー関数 +void f( S & obj ) ; + +// constなメンバー関数 +void f( S const & obj ) ; +\end{lstlisting} + +\hypersubsection{ch270208}{メンバーへのポインター} +\index{ぽいんた@ポインター!めんばへの@メンバーへの〜}\index{めんば@メンバー!ぽいんた@ポインター} + +メンバーへのポインターはかなり文法的にややこしい。そもそも、通常のポインターとは概念でも実装でも異なる。 + +ここで取り扱うのはメンバーへのポインターという概念で、クラスのオブジェクトのサブオブジェクトへのポインターではない。サブオブジェクトへのポインターは通常のポインターと同じだ。 + +\begin{lstlisting}[language={C++}] +struct Object +{ + // サブオブジェクト + int subobject ; +} ; + +int main() +{ + // クラスのオブジェクト + Object object ; + + // サブオブジェクトへのポインター + int * pointer = &object.subobject ; + + *pointer = 123 ; + int read = object.subobject ; +} +\end{lstlisting} + +メンバーへのポインターとは、クラスのデータメンバーやメンバー関数を参照するもので、クラスのオブジェクトとともに使うことでそのデータメンバーやメンバー関数を参照できるものだ。 + +細かい文法の解説はあとにして例を見せよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +struct Object +{ + int data_member ; + void member_function() + { std::cout << data_member ; } +} ; + +int main() +{ + // Object::data_memberメンバーへのポインター + int Object::* int_ptr = &Object::data_member ; + // Object::member_functionメンバーへのポインター + void (Object::* func_ptr)() = &Object::member_function ; + + // クラスのオブジェクト + Object object ; + + // objectに対するメンバーポインターを介した参照 + object.*int_ptr = 123 ; + // objectに対するメンバーポインターを介した参照 + // 123 + (object.*func_ptr)() ; + + // 別のオブジェクト + Object another_object ; + another_object.data_member = 456 ; + // 456 + (another_object.*func_ptr)() ; +} +\end{lstlisting} + +細かい文法はあとで学ぶとして、肝心の機能としてはこうだ。クラスのオブジェクトからは独立したデータメンバーやメンバー関数自体へのポインターを取得する。 + +\begin{lstlisting}[language={C++}] +struct Object +{ + int data_member ; +} ; + +// メンバーへのポインター +int Object::*int_ptr = &Object::data_member ; +\end{lstlisting} + +このポインターをクラスのオブジェクトと組み合わせることで、ポインターが参照するクラスのメンバーで、かつオブジェクトのサブオブジェクトの部分を参照できる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +Object object ; + +// メンバーへのポインターをオブジェクトに適用してサブオブジェクトを参照する +object.*int_ptr = 123 ; +\end{lstlisting} + +では文法の説明に入ろう。 + +メンバーへのポインターは文法がややこしい。 + +あるクラス名\texttt{C}の型名\texttt{T}のメンバーへのポインター型は以下のようになる。 + +\begin{lstlisting}[style=grammar] +型名 クラス名::* +T C::* +\end{lstlisting} + +以下のクラスの各データメンバーへの型はそれぞれコメントのとおりになる。 + +\begin{lstlisting}[language={C++}] +struct ABC +{ + // int ABC::* + int x ; + // int ABC::* + int y ; + // double ABC::* + double d ; + // int * ABC::* + int * ptr ; +} ; + +struct DEF +{ + // ABC * DEF::* + ABC * abc ; +} ; +\end{lstlisting} + +順を追って説明していこう。まずクラス\texttt{ABC}のメンバー、 +\begin{lstlisting}[language={C++}] +// int ABC::* +int x ; +// int ABC::* +int y ; +\end{lstlisting} +このメンバーへのポインターの型はどちらも\texttt{int ABC::*}\,になる。データメンバーの型は\texttt{int}で、クラス名が\texttt{ABC}なので、\texttt{型名 クラス名::*}\,に当てはめると\texttt{int ABC::*}\,になる。 + +\begin{lstlisting}[language={C++}] +// double ABC::* +double d ; +\end{lstlisting} + +このメンバーへのポインターの型は\texttt{double ABC::*}\,になる。 + +最後のクラス\texttt{ABC}のメンバー、 +\begin{lstlisting}[language={C++}] +// int * ABC::* +int * ptr ; +\end{lstlisting} +これが\texttt{int * ABC::*}\,になる理由も、最初に説明した\texttt{型名 クラス名::*}\,のルールに従っている。型名が\texttt{int *}、クラス名が\texttt{ABC}なので、\texttt{int * ABC::*}\,だ。 + +最後の例はクラス\texttt{DEF}のメンバーとしてクラス\texttt{ABC}のポインター型のメンバーだ。\texttt{ABC DEF::*}\,になる。 + +クラス名\texttt{C}のメンバー名\texttt{M}のメンバーへのポインターを得るには以下の文法を使う。 + +\begin{lstlisting}[style=grammar] +&クラス名::メンバー名 +&C::M +\end{lstlisting} + +具体的な例を見てみよう。 + +\begin{lstlisting}[language={C++}] +struct C +{ + int x = 1 ; + int y = 2 ; +} ; + +int main() +{ + int C::* x_ptr = &C::x ; + int C::* y_ptr = &C::y ; + + C object ; + + // 1 + std::cout << object.*x_ptr ; + // 2 + std::cout << object.*y_ptr ; +} +\end{lstlisting} + +わかりづらければエイリアス宣言を使うとよい。 + +\begin{lstlisting}[language={C++}] +using type = int C::* ; +type x_ptr = &C::x ; +\end{lstlisting} + +あるいは\texttt{auto}を使うという手もある。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// int C::* +auto x_ptr = &C::x ; +\end{lstlisting} + +メンバー関数へのポインターは、メンバーへのポインターと関数へのポインターを組み合わせた複雑な文法となるので、とてもわかりづらい。 + +復習すると、\texttt{int}型の引数を1つ受け取り\texttt{int}型の戻り値を返す関数へのポインターの型は\texttt{int (*)(int)}だ。 + +\begin{lstlisting}[language={C++}] +int f(int) { return 0 ; } +int (*ptr)(int) = &f ; +\end{lstlisting} + +この関数がクラス\texttt{C}のメンバー関数の場合、以下のようになる。 + +\begin{lstlisting}[language={C++}] +struct C +{ + int f(int) { return 0 ; } +} ; +\end{lstlisting} + +ところで、メンバーへのポインターは\texttt{型名 クラス名::*}\,だった。この2つを組み合わせると、以下のように書ける。 + +\begin{lstlisting}[language={C++}] +struct C +{ + int f(int) { return 0 ; } +} ; + +int main() +{ + // メンバー関数へのポインター + int (C::*ptr)(int) = &C::f ; + // クラスのオブジェクト + C object ; + + // オブジェクトを指定したメンバー関数へのポインターを介した関数呼び出し + (object.*ptr)( 123 ) ; +} +\end{lstlisting} + +メンバー関数へのポインターは難しい。 + +関数\texttt{f}の型は\texttt{int (int)}で、そのポインターの型は\texttt{int (*)(int)}だ。するとクラス名\texttt{C}のメンバー関数\texttt{f}へのポインターの型は、\texttt{int (C::*)(int)}になる。 + +メンバー関数へのポインター型の変数を宣言してその値を\texttt{C::f}へのポインターに初期化しているのが以下の行だ。 + +\begin{lstlisting}[language={C++}] +// メンバー関数へのポインター +int (C::*ptr)(int) = &C::f ; +\end{lstlisting} + +この\texttt{ptr}を経由したメンバー関数\texttt{f}の呼び出し方だが、まずクラスのオブジェクトが必要になるので作る。 + +\begin{lstlisting}[language={C++}] +C object ; +\end{lstlisting} + +そして演算子の\texttt{operator .*}\,を使う。 +\index{{.}*@\texttt{.*}} + +\begin{lstlisting}[language={C++}] +(object.*ptr)(123) ; +\end{lstlisting} + +\texttt{object.*ptr}を括弧で囲んでいるのは、演算子の優先順位のためだ。もしこれを以下のように書くと、 +\begin{lstlisting}[language={C++}] +object.*ptr(123) +\end{lstlisting} +これは\texttt{ptr(123)}という式を評価した結果をメンバーへのポインターと解釈してクラスのオブジェクトを介して参照していることになる。例えば以下のようなコードだ。 + +\begin{lstlisting}[language={C++}] +struct C { int data { } ; } ; + +auto ptr( int ) -> int C::* +{ return &C::data ; } + +int main() +{ + C object ; + object.*ptr(123) ; +} +\end{lstlisting} + +演算子の優先順位の問題のために、\texttt{(object.*ptr)}と括弧で包んで先に評価させ、その後に関数呼び出し式である\texttt{(123)}を評価させる。 + +実は演算子\texttt{operator .*}\,のほかに、\texttt{operator ->*}\,という演算子がある。 +\index{->*@\texttt{->*}} + +\texttt{.*}\,はクラスのオブジェクトがリファレンスの場合の演算子だが、\texttt{->*}\,はクラスのオブジェクトがポインターの場合の演算子だ。 + +\begin{lstlisting}[language={C++}] +struct C{ int data { } ; } ; + +int main() +{ + auto data_ptr = &C::data ; + + C object ; + auto c_ptr = &object ; + + c_ptr->*data_ptr = 123 ; +} +\end{lstlisting} + +演算子\texttt{a->b}が\texttt{(*(a)).b}となるように、演算子\texttt{a->*b}も\texttt{(*(a)).*b}と置き換えられるシンタックスシュガーだ。 + +上の例で、 +\begin{lstlisting}[language={C++}] +c_ptr->*object = 123 ; +\end{lstlisting} +は、以下と同じだ。 + +\begin{lstlisting}[language={C++}] +(*(c_ptr)).*object = 123 ; +\end{lstlisting} + +\texttt{.*}\,や\,\texttt{->*}\,の文法を覚えるのが面倒な場合、標準ライブラリに\texttt{std::invoke( f, t1, ... )}という便利な関数が用意されている。 + +\texttt{f}がデータメンバーへのポインターで、\texttt{t1}がクラスのオブジェクトの場合、\texttt{std::invoke(f, t1)}は以下のような関数になる。 + +\begin{lstlisting}[language={C++}] +template < typename F, typename T1 > +適切な戻り値の型 std::invoke( F f, T1 t1 ) +{ + return t1.*f ; +} +\end{lstlisting} + +なので以下のように書ける。 + +\begin{lstlisting}[language={C++}] +struct C { int data { } ; } ; + +int main() +{ + auto data_ptr = &C::data ; + + C object ; + + // どちらも同じ意味 + object.*data_ptr = 123 ; + std::invoke( data_ptr, object ) = 123 ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +便利なことに\texttt{t1}がポインターの場合は、 +\begin{lstlisting}[language={C++}] +template < typename F, typename T1 > +適切な戻り値の型 std::invoke( F f, T1 t1 ) +{ + return (*(t1)).*f ; +} +\end{lstlisting} +という関数として振る舞う。そのため、リファレンスでもポインターでも気にせずに使うことができる。 + +\begin{lstlisting}[language={C++}] +C * c_ptr = &object ; + +// どちらも同じ意味 +c_ptr->*data_ptr = 123 ; +std::invoke( data_ptr, c_ptr ) = 123 ; +\end{lstlisting} + +\texttt{std::invoke}がさらにすごいことに、メンバー関数へのポインターにも対応している。 + +\texttt{std::invoke( f, t1, ... )}で、\texttt{f}がメンバー関数へのポインターで、\texttt{t1}がクラスのオブジェクトへのリファレンスで、\texttt{...}が関数呼び出しの際の引数の場合、以下のような関数として振る舞う。 + +\begin{lstlisting}[language={C++}] +template < typename F, typename T1, + // まだ知らない機能 + typename ... Ts > +適切な戻り値の型 +invoke( F f, T1 t1, +// まだ知らない機能 +Ts ... ts ) +{ + return (t1.*f)(ts...) +} +\end{lstlisting} + +厳密にはこの宣言は間違っているのだが、まだ知らない機能を使っているので気にしなくてもよい。大事なことは、\texttt{std::invoke}の第三引数以降の実引数が、関数呼び出しの実引数として使われるということだ。 + +\begin{lstlisting}[language={C++}] +struct C +{ + int f0() { return 0 ; } + int f1(int) { return 1 ; } + int f2( int, int ) { return 2 ; } +} ; + +(@\ifTombow\pagebreak\fi@) +int main() + { + C object ; + + // 同じ + (object.*&C::f0)() ; + std::invoke( &C::f0, object ) ; + // 同じ + (object.*&C::f1)(1) ; + std::invoke( &C::f1, object, 1) ; + // 同じ + (object.*&C::f2)(1,2) ; + std::invoke( &C::f2, object, 1,2) ; +} +\end{lstlisting} + +この場合も、\texttt{object}が\texttt{C}へのリファレンスではなく、\texttt{C}へのポインターでも自動で認識していいように処理してくれる。 diff --git a/TeX/030-pointer-details.tex b/TeX/030-pointer-details.tex new file mode 100644 index 0000000..b64e34f --- /dev/null +++ b/TeX/030-pointer-details.tex @@ -0,0 +1,674 @@ +\hypersection{ch2703}{ポインターの内部実装} + +ポインターの意味上と文法上の解説は終えた。ここからはポインターの内部実装\index{ぽいんた@ポインター!ないぶじつそう@内部実装}についてだ。ポインターの値とは外でもない、メモリー\index{めもり@メモリー}上のアドレス\index{あどれす@アドレス}のことだ。 + +\hypersubsection{ch270301}{キロバイトとキビバイト} + +メモリーとアドレスについて解説する前に、キロバイト(Kilo byte)とキビバイト(Kibi byte)の違いについて解説する。 +\index{きろばいと@キロバイト}\index{きびばいと@キビバイト} + +キロ(Kilo)というのはSI接頭語\index{SI@SI接頭語}で、\(1000^1\)を意味する。1キロは1000だ。SI接頭語にはほかにもメガ(Mega, \(1000^2\))、ギガ(Giga, \(1000^3\))やテラ(Tera, \(1000^4\))などの接頭語もある。 +\index{きろ@キロ}\index{めが@メガ}\index{ぎが@ギガ}\index{てら@テラ} + +長さ1キロメートルは1000メートルで、重さ1キログラムは1000グラムだ。 + +いま「このCPUのクロック周波数は1GHzだ」と言ったとき、それは\(1000^3\)Hz = \(1000000000\)Hzのことだ。 + +しかし、メモリー容量だけは慣習的に\(1000^n\)ではなく、\(1024^n\)を使う。 + +一般人が「このメモリーは1KBだ」と言ったとき、それは1024バイトのことだ。1GBのメモリーは\(1024^3 バイト = 1073741824 バイト\)だ。筆者が本書を執筆するのに使ったラップトップコンピューターは32GBのメモリーを積んでいるがこれは34359738368バイトだ。 + +メモリーの容量が10進数ではなく2進数で数えられているのは、メモリーは2進数で扱うのがハードウェア的に都合がいいからだ。そのため、慣習的にキロは\(1000^1\)ではなく\(1024^1\)を意味するようになってしまった。 + +このため、IEEE 1541\index{IEEE 1541}では10進SI接頭語と対になる2進接頭語を定義した。 + +\ifTombow\pagebreak\fi +\begin{small} +\begin{longtable}[]{@{\,\,}ll@{\,\,}} +\hline%\toprule +\textsf{接頭語} & \textsf{値}\tabularnewline +\hline%\midrule +\endhead +キビ(kibi, Ki) & \(2^{10}\)\tabularnewline +メビ(mebi, Mi) & \(2^{20}\)\tabularnewline +ギビ(gibi, Gi) & \(2^{30}\)\tabularnewline +テビ(tebi, Ti) & \(2^{40}\)\tabularnewline +ペビ(pebi, Pi) & \(2^{50}\)\tabularnewline +エクスビ(exbi, Ei) & \(2^{60}\)\tabularnewline +\hline%\bottomrule +\end{longtable} +\end{small} + +本書では1KBは1000バイトで、1KiBが1024バイトを意味する。 + +\hypersubsection{ch270302}{メモリーとアドレス} + +コンピューターにはメモリー\index{めもり@メモリー}やストレージ\index{すとれじ@ストレージ}と呼ばれる記憶領域\index{きおくりよういき@記憶領域}がある。情報の最小単位はすでに学んだようにビット\index{びつと@ビット}だが、情報をビット単位で扱うのは不便なので、慣習的に複数の連続したビットを束ねたバイト\index{ばいと@バイト}という単位で扱っている。1バイトはほとんどのアーキテクチャで8ビットだ。メモリーは複数の連続したバイト列で成り立っている。 + +この連続したバイト列の中の任意の1バイトを指し示すのがアドレスだ。メモリーのバイト列の最初の1バイトのアドレスを0とし、次の1バイトアドレスを1とし、以降、その次を前のアドレスに1加えた値にしてみよう。 + +そのようなメモリーとアドレスのコンピューターでは、1バイトの符号なし整数で表現されたアドレスは、256バイトのメモリーの中の任意の1バイトをアドレスとして参照することができる。 + +これはとても抽象化された計算機で、現実の計算機はもっと複雑な実装になっている。しかしC++の規格としては、メモリーとはフラットな連続したバイト列であって、その任意の各バイトをアドレスから参照可能だという想定になっている。 + +アドレスが1バイトの符号なし整数で表現され、そのすべてのビットが使われる場合、256バイトの連続したメモリーをアドレス可能だ。 + +アドレスが2バイトならば、64KiBのメモリーをアドレス可能だ。 + +アドレスが4バイトならば、4GiBのメモリーをアドレス可能だ。 + +アドレスが8バイトならば、16EiBのメモリーをアドレス可能だ。 + +ポインターの値というのは、このアドレスの値のことだ。 + +\hypersubsection{ch270303}{ポインターのサイズ} +\index{ぽいんた@ポインター!さいず@サイズ} + +ポインターの値というのはアドレスの値だ。ポインターの値を格納するのにもメモリーが必要だ。ではポインターのサイズは何バイトあるのだろう。 + +型\texttt{T}のサイズを調べるには\texttt{sizeof(T)}\index{sizeof(T)@\texttt{sizeof(T)}}を使う。 + +\begin{lstlisting}[language={C++}] +template +void print_size() +{ + std::cout << sizeof(T) << "\n"s ; +} + +int main() +{ + print_size() ; + print_size() ; + + // ポインターへのポインター + print_size() ; +} +\end{lstlisting} + +筆者の環境でこのプログラムを実行した結果は以下のようになった。 + +\begin{lstlisting}[style=terminal] +8 +8 +8 +\end{lstlisting} + +どうやら筆者の環境ではポインターのサイズはすべて8バイトらしい。 + +\hypersubsection{ch270304}{ポインターの値} +\index{ぽいんた@ポインター!あたい@値} + +ポインターが8バイト、つまり64ビットの値であるならば、それを8バイトの符号なし整数として解釈した値はどうなるのだろう。 + +C++にはすべてのポインターの値を格納できるサイズの符号なし整数型が用意されている。\texttt{std::uintptr\_t}\index{uintptr\_t@\texttt{uintptr\_t}}だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << sizeof( std::uintptr_t ) ; +} +\end{lstlisting} + +筆者の環境でこのプログラムを実行した結果も\texttt{8}が出力される。 + +ポインターも\texttt{std::uintptr\_t}も8バイトだ。ポインターのバイト列を\texttt{std::uintptr\_t}として強引に解釈すれば、符号なし整数としての値を出力してみよう。 + +ある値\texttt{from}のバイト列を、同じバイト数のある型\texttt{to}の値として強引に解釈するC++20で追加された標準ライブラリに、\texttt{std::bit\_cast(from)}\index{bit\_cast@\texttt{bit\_cast}}がある。 + +\begin{lstlisting}[language={C++}] +#include + +int main() +{ + int data {} ; + std::cout << std::bit_cast(&data) ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +このプログラムを何度か実行した結果、以下のような結果を得た。 + +\begin{lstlisting}[style=terminal] +$ make run +140725678382588 +$ make run +140721510940268 +$ make run +140731669632396 +\end{lstlisting} + +私の環境ではポインターの具体的な値は実行ごとに異なる。これは私の使っているOSがASLR(Address Space Layout Randomization)\index{ASLR(Address Space Layout Randomization)} を実装しているためだ。興味のある読者は調べてみるとよい。 + +この値は\texttt{int}型の変数\texttt{data}のポインターの整数としての値だ。このアドレスの場所に、\texttt{int}型のオブジェクトの最初の1バイトがあり、その次の場所に次の1バイトがある。 + +筆者の環境では\texttt{int}型は4バイトだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << sizeof(int) ; +} +\end{lstlisting} + +\texttt{int}型のオブジェクトは4バイトの連続したメモリー上に構築されている。つまり、本質的には以下のようなコードと同等になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::byte data[4] ; + std::cout << std::bit_cast(&data[0]) ; +} +\end{lstlisting} + +\texttt{std::byte}\index{byte@\texttt{byte}}というのは\texttt{sizeof(std::byte)}の結果が1になる、サイズが1バイトの符号なし整数型だ。 + +\texttt{std::byte}はC++で1バイトの生の値を表現するために使うことができる。配列は連続したバイト列なので、4バイトの\texttt{int}型は、本質的には上のようなコードになる。ただし上のコードはアライメントという概念が欠けている。これについてはあとで説明する。 + +ところで、\texttt{std::bit\_cast}は2020年に制定される国際標準規格C++20から入った。しかるに筆者がこの文章を書いているのは2018年だ。まだC++20を完全に実装したC++コンパイラーは存在しない。この本が出版されてしばらくは、読者の手元にもC++20コンパイラーは存在しないだろう。 + +\clearpage +\hypersubsection{ch270305}{std::bit\texttt{\_}castの実装} +\index{bit\_cast@\texttt{bit\_cast}} + +ないものは自分で実装すればいい。\texttt{std::bit\_cast}に近いものを実装してみよう。 + +今回実装する\texttt{bit\_cast}は以下のような関数テンプレートだ。 + +\begin{lstlisting}[language={C++}] +template < typename To, typename From > +To bit_cast( From const & from ) +{ + // 値fromのバイト列をTo型の値として解釈して返す。 +} +\end{lstlisting} + +\texttt{bit\_cast}の実装にはポインターが必要だ。\texttt{From}の値を表現するバイト列への先頭のポインターを取り、バイト単位で\texttt{To}の値を表現するバイト列にコピーすればよい。 + +標準ライブラリにはそのような処理を行ってくれる\texttt{std::memcpy(dest, src, n)}がある。ポインター\texttt{src}から\texttt{n}バイトをポインター\texttt{dest}から\texttt{n}バイトに書き込む関数だ。 + +\begin{lstlisting}[language={C++}] +template < typename To, typename From > +To bit_cast( From const & from ) +{ + To to ; + std::memcpy( &to, &from, sizeof(To) ) ; + return to ; +} +\end{lstlisting} + +これで\texttt{std::bit\_cast}の実装はできた。しかしこの実装は問題を\texttt{std::memcpy}にたらい回しにしただけだ。\texttt{std::memcpy}も実装できて初めて\texttt{std::bit\_cast}を自前で実装できたと言える。 + +\hypersubsection{ch270306}{std::memcpyの実装} +\index{memcpy@\texttt{memcpy}} + +\texttt{std::memcpy}はC++コンパイラーによって効率のよいコードに置き換えられる。そのため自分で実装した\texttt{std::memcpy}を標準ライブラリと同じ効率にすることは難しいが、機能的にはほとんど同じものを作ることができる。 + +\texttt{memcpy}の実装にはポインターの詳細な理解が必要だ。 + +\texttt{std::memcpy}関数は以下のようになっている。 + +\begin{lstlisting}[language={C++}] +void * memcpy( void * dest, void const * src, std::size_t n ) +{ + // srcの先頭バイトからnバイトを + // destの先頭バイトからのバイト列にコピーし + // destを返す +} +\end{lstlisting} + +見慣れない\texttt{void *}\,\index{void *@\texttt{void *}}という型が出てきた。まずはこれについて学ぼう。 + +\hypersubsubsection{ch27030601}{void型} +\index{void@\texttt{void}} + +\texttt{void}は特別な型だ。\texttt{void}型は何も値を持たない型という意味を持つ。例えば関数が戻り値を何も返さない場合、\texttt{void}型を返す関数として宣言される。 + +\begin{lstlisting}[language={C++}] +// 何も値を返さない関数 +void f() +{ + // 何も値を返さない + return ; +} +\end{lstlisting} + +あらゆる値は\texttt{void}型に変換することができる。変換した結果は、何も値を持たない。 + +\begin{lstlisting}[language={C++}] +void f() +{ + return static_cast(123) ; +} +\end{lstlisting} + +C++17では、\texttt{void}型の変数は作れない。 + +\begin{lstlisting}[language={C++}] +// エラー +void x ; +\end{lstlisting} + +ところで、読者が本書を読むころには、C++規格では\texttt{void}型の変数が作れるようになっているかもしれない。これは\texttt{void}型だけ変数を作れないのが面倒だから作れるようになるだけで、具体的な値のない変数になる。 + +\hypersubsubsection{ch27030602}{void *\,型} +\index{void *@\texttt{void *}} + +\texttt{void *}\,型は「\texttt{void}型へのポインター型」だ。\texttt{int *}\,が「\texttt{int}型へのポインター型」であるのと同じだ。 + +\texttt{void *}\,型の値は、ある型\texttt{T}へのポインター型から型\texttt{T}という情報が消え去ったポインターの値だ。ポインターの値というのはアドレスで、アドレスというのは単なるバイト単位のメモリーを指す整数値だということを学んだ。\texttt{void *}\,型は特定の型を意味しないポインター型だ。 + +ある型\texttt{T}へのポインター型の値は、\texttt{void *}\,型に変換できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int data { } ; + + // int *からvoid *への変換 + void * ptr = &data ; +} +\end{lstlisting} + +\texttt{void *}\,型の値\texttt{e}から元の型\texttt{T}へのポインターに変換するには\texttt{static\_cast(e)}が必要だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int data { } ; + void * void_ptr = &data ; + + int * int_ptr = static_cast(void_ptr) ; +} +\end{lstlisting} + +もし\texttt{static\_cast(e)}の\texttt{e}が\texttt{T *}\,として妥当なアドレスの値であれば、変換後も正しく動く。 + +\texttt{T const *}\,型は\texttt{void const *}\,型に変換できる。その逆変換もできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int data {} ; + int const * int_const_ptr = &data ; + void const * void_const_ptr = int_const_ptr ; + int const * original = static_cast(void_const_ptr) ; +} +\end{lstlisting} + +ポインター間の型変換で\texttt{const}を消すことはできない。 + +\texttt{memcpy}は\texttt{void *}\,を使うことで、どんなポインターの値でも取れるようにしている。C++にはテンプレートがあるので以下のように宣言してもよいのだが、 +\begin{lstlisting}[language={C++}] +template < typename Dest, typename Src > +Dest * memcpy( Dest * dest, Src const * src, std::size_t n ) ; +\end{lstlisting} +\texttt{memcpy}はC++以前からあるC言語ライブラリなので、こうなっている。 + +\hypersubsubsection{ch27030603}{std::byte型} +\index{byte@\texttt{byte}} + +\texttt{void *}\,型はアドレスだけを意味するポインター型なので、参照することができない。\texttt{memcpy}の実装にはポインターを経由して参照先を1バイトずつ読み書きする必要がある。そのための型として\texttt{std::byte}がある。 + +\texttt{std::byte}型は1バイトを表現するための型だ。\texttt{sizeof(std::byte)}の結果は\texttt{1}になる。 + +1バイトというのは10進数で\(0 \leqq n \leqq 255\)までの値を扱う。 + +\texttt{std::byte}はとても厳格に1バイトの符号なし整数として振る舞うので、普通の整数で初期化や代入をすることができない。 + +\begin{lstlisting}[language={C++}] +// エラー +std::byte a = 123 ; +std::byte b(123) ; + +// これもエラー +a = 123 ; +\end{lstlisting} + +\texttt{std::byte}に具体的な値で初期化するには\,\texttt{\{x\}}\,を使う。 + +\begin{lstlisting}[language={C++}] +std::byte a{123} ; +\end{lstlisting} + +\texttt{std::byte}に値を代入するには\texttt{std::byte\{x\}}\,を使う + +\begin{lstlisting}[language={C++}] +std::byte a ; +a = std::byte{123} ; +\end{lstlisting} + +\texttt{static\_cast(x)}や\texttt{std::byte(x)}はコンパイルできるが、使ってはならない。 + +\begin{lstlisting}[language={C++}] +// 使ってはならない +std::byte a = static_cast(123) ; +std::byte b = std::byte(123) ; +\end{lstlisting} + +なぜ使ってはならないかというと、範囲外の値を無理やり変換してしまうからだ。 + +\begin{lstlisting}[language={C++}] +std::byte a = static_cast(256) ; +std::byte b = std::byte(-1) ; +\end{lstlisting} + +\hypersubsubsection{ch27030604}{配列のメモリー上での表現} +\index{はいれつ@配列!めもりじようでのひようげん@メモリー上での表現} + +配列は要素型を表現するバイト列をメモリー上に連続して配置する。 + +例えば\texttt{int [3]}という配列があり、\texttt{sizeof(int)}が\texttt{4}の場合、全体で12バイトのメモリーが確保される。 + +\begin{lstlisting}[language={C++}] +int data[3] = {1,2,3} ; +\end{lstlisting} + +最初の4バイト(0バイト目から3バイトまで)の領域は0番目の要素である\texttt{data[0]}で、その値は\texttt{1}だ。 + +次の4バイト(4バイト目から7バイト目まで)の領域は1番目の要素である\texttt{data[1]}で、その値は\texttt{2}だ。 + +最後の4バイト(8バイト目から11バイト目まで)の領域は2番目の要素である\texttt{data[2]}で、その値は\texttt{3}だ。 + +\begin{figure}[htbp] + \centering + \includegraphics[scale=1.0]{fig/fig30-01.eps} + \label{fig:30-01} +\end{figure} + +実際にアドレスの生の値を出力して確かめてみよう。 + +\begin{lstlisting}[language={C++}] +// 生のアドレスを出力する関数 +template < typename T > +void print_raw_address( T ptr ) +{ + std::cout << std::bit_cast(ptr) << "\n"s ; +} + +int main() +{ + int data[3] = {0,1,2} ; + print_raw_address( &data[0] ) ; + print_raw_address( &data[1] ) ; + print_raw_address( &data[2] ) ; +} +\end{lstlisting} + +このプログラムを筆者の環境で実行すると以下のように出力された。 + +\begin{lstlisting}[style=terminal] +140736120015884 +140736120015888 +140736120015892 +\end{lstlisting} + +筆者の環境では\texttt{sizeof(int)}は4だ。\texttt{\&data[0]}の生のアドレスに4を足した値が\,\texttt{\&data[1]}になっていることがわかる。 + +\hypersubsubsection{ch27030605}{ポインターと整数の演算} + +ポインターと整数を加減算することができる。 +\index{ぽいんた@ポインター!かげんざん@加減算} + +ポインター\texttt{T *}\,に整数\texttt{n}を足すと、ポインターのアドレスが\texttt{sizeof(T) * n}加算される。この結果、ポインターは要素が配列のように配置された場合に\texttt{n}個先の要素を指すようになる。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void print_raw_address( T ptr ) +{ + std::cout << std::bit_cast(ptr) << "\n"s ; +} + +int main() +{ + int a[4] = {0,1,2,3} ; + + // 0個目の要素へのポインター + int * a0 = &a[0] ; + print_raw_address( a0 ) ; + + + // アドレスがsizeof(int) * 3加算される + // a3は3個目の要素へのポインター + int * a3 = a0 + 3 ; + print_raw_address( a3 ) ; + + // アドレスがsizeof(int) * 2減算される。 + // a1は1個目の要素へのポインター + int * a1 = a3 - 2 ; + print_raw_address( a1 ) ; +} +\end{lstlisting} + +これを筆者の環境で実行すると以下のように出力された。 + +\begin{lstlisting}[style=terminal] +140722117900224 +140722117900236 +140722117900228 +\end{lstlisting} + +最初の値が\texttt{a0}, 次の値が\texttt{a3}, 最後の値が\texttt{a1}だ。 + +筆者の環境では\texttt{sizeof(int)}は\texttt{4}だ。すると\texttt{a3}の値は\texttt{a0}の値より12多い値になっているはずだ。実際にそうなっている。\texttt{a1}は\texttt{a3}に対して8少ない値になっているはずだ。実際にそうなっている。 + +\hypersubsubsection{ch27030606}{いよいよmemcpyの実装} +\index{memcpy@\texttt{memcpy}} + +これまで学んできたことをすべて使い、ようやく\texttt{memcpy}が実装できる。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + \texttt{dest}を\texttt{std::byte *}\,型に変換する +\item + \texttt{src}を\texttt{std::byte const *}\,型に変換する +\item + \texttt{src}の参照先から\texttt{n}バイトを\texttt{dest}の参照先にコピーする +\item + \texttt{dest}を返す +\end{enumerate} + +\begin{lstlisting}[language={C++}] +void * memcpy( void * dest, void const * src, std::size_t n ) +{ + // destをstd::byte *型に変換 + auto d = static_cast(dest) ; + // srcをstd::byte const *型に変換する + auto s = static_cast(src) ; + + // srcからnバイトコピーするのでnバイト先のアドレスを得る + auto last = s + n ; + + // nバイトコピーする + while ( s != last ) + { + *d = *s ; + ++d ; + ++s ; + } + + // destを返す + return dest ; +} +\end{lstlisting} + +\hypersubsubsection{ch27030607}{memcpyの別の実装} + +ポインターは\texttt{operator []}に対応している。 +\index{[]@\texttt{[]}} + +ポインター\texttt{p}と整数\texttt{i}に対して\texttt{p[i]}と書いたとき、\texttt{*(p + i)}という意味になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a[5] = {0,1,2,3,4} ; + int * p = &a[0] ; + + p[0] ; // 0 + p[2] ; // 2 + + int * p2 = &p[2] ; + p2[1] ; // 3 +} +\end{lstlisting} + +\texttt{memcpy}は\texttt{operator []}を使って書くこともできる。 + +\begin{lstlisting}[language={C++}] +void * memcpy( void * dest, void const * src, std::size_t n ) +{ + auto d = static_cast(dest) ; + auto s = static_cast(src) ; + + for ( std::size_t i = 0 ; i != n ; ++i ) + { + d[i] = s[i] ; + } + + return dest ; +} +\end{lstlisting} + +\clearpage +\hypersubsection{ch270307}{データメンバーへのポインターの内部実装} +\index{でためんば@データマンバー!へのぽいんた@へのポインター} + +データメンバーへのポインターの整数としての値は少し変わっている。 + +ポインターの生の値は、メモリー上で値を表現しているバイト列の先頭アドレスだ。 + +データメンバーへのポインターは、具体的なクラスのオブジェクトへのポインターやリファレンスがあって初めて意味がある。 + +\begin{lstlisting}[language={C++}] +struct S { int x = 123 ; } ; + +int main() +{ + int data = 123 ; + int * ptr = &data ; + // ptr単体で参照できる + int read1 = *ptr ; + + S object ; + int S::* mem_ptr = &S::x ; + // objectとmem_ptrの2つで参照できる + int read2 = object.*mem_ptr ; + +} +\end{lstlisting} + +配列が要素型のバイト列を連続して配置したメモリーレイアウトをしているように、クラスもデータメンバーを連続して配置したメモリーレイアウトをしている。 + +たとえば以下のようなクラス\texttt{Object}がある場合、 +\begin{lstlisting}[language={C++}] +struct Object +{ + int x ; + int y ; + int z ; +} ; +\end{lstlisting} +このクラスのサイズは\texttt{sizeof(Object)}だ。このクラスは\texttt{int}型のサブオブジェクトを3つ持っているので、そのサイズは少なくとも\texttt{size(int)*3}はある。 + +実際に確かめてみよう。 + +\begin{lstlisting}[language={C++}] +struct Object +{ + int x ; + int y ; + int z ; +} ; + +int main() +{ + std::cout << "sizeof(int): " << sizeof(int) << "\n"s ; + + std::cout << "sizeof(Object): " << sizeof(Object) << "\n"s ; +} +\end{lstlisting} + +このプログラムを筆者の環境で実行すると以下のように出力された。 + +\begin{lstlisting}[style=terminal] +sizeof(int): 4 +sizeof(Object): 12 +\end{lstlisting} + +\texttt{int}型のサイズが\texttt{4}で、\texttt{Object}型のサイズが\texttt{12}ということは、クラス\texttt{Object}には\texttt{int}型のサブオブジェクトが3つ、\CID{1850}間なく連続して配置されているということだ。すべてのクラスがこうではないが、今回の私の環境ではそうなっている。 + +全体で12バイトということは、配列\texttt{int [3]}と同じように、最初の4バイトに\texttt{x}, \texttt{y}, \texttt{z}のどれかが、次の4バイトに残りのどちらかが、最後の4バイトに残りが配置されている。 + +データメンバーへのポインターというのは、このクラスのオブジェクトを表現するバイト列の先頭から何バイト目に配置されているかというオフセット値になっている。 + +具体的な値を見てみよう。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void print_raw_address( T ptr ) +{ + std::cout << bit_cast(ptr) << "\n"s ; +} + +struct Object +{ + int x ; + int y ; + int z ; +} ; + +int main() +{ + print_raw_address( &Object::x ) ; + print_raw_address( &Object::y ) ; + print_raw_address( &Object::z ) ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +このプログラムを筆者の環境で実行すると以下のように出力される。 + +\begin{lstlisting}[style=terminal] +0 +4 +8 +\end{lstlisting} + +筆者の環境では、\texttt{x}はクラスの先頭アドレスからオフセット0バイトに、\texttt{y}はオフセット4バイトに、\texttt{z}はオフセット8バイトに配置されているようだ。 + +確かめてみよう。 + +\begin{lstlisting}[language={C++}] +struct Object +{ + int x = 123 ; + int y = 456 ; + int z = 789 ; +} ; + +int main() +{ + + Object object ; + + // クラスのオブジェクトの先頭アドレス + std::byte * start = bit_cast(&object) ; + // オフセット0 + int * x = bit_cast(start + 0) ; + // オフセット4 + int * y = bit_cast(start + 4) ; + // オフセット8 + int * z = bit_cast(start + 8) ; + + std::cout << *x << *y << *z ; +} +\end{lstlisting} + +筆者の環境では以下のように出力される + +\begin{lstlisting}[style=terminal] +123456789 +\end{lstlisting} + +このプログラムの実行結果は環境によって変わる。読者の使っている環境でデータメンバーへのポインターが筆者の環境と同じように実装されているとは限らない。 diff --git a/TeX/031-iterator-operations.tex b/TeX/031-iterator-operations.tex new file mode 100644 index 0000000..72e13fc --- /dev/null +++ b/TeX/031-iterator-operations.tex @@ -0,0 +1,1601 @@ +\hyperchapter{ch28}{イテレーター詳細}{イテレーター詳細} + +\hypersection{ch2801}{イテレーターとポインターの関係} +\index{いてれた@イテレーター} + +\texttt{array}のイテレーターの実装を振り返ろう。前回実装したイテレーターは、リファレンスとインデックスを使うものだった。 + +\begin{lstlisting}[language={C++}] +template < Array > +struct array_iterator +{ + using reference = typename Array::reference ; + + Array & a ; + std::size_t i ; + + array_iterator( Array * a, std::size_t i ) + : a(a), i(i) { } + + reference operator *() const + { return a[i] ; } + + array_iterator & operator ++() + { + ++i ; + return *this ; + } + +(@\ifTombow\pagebreak\fi@) + reference operator [] ( std::size_t n ) + { return a[i + n] ; } +} ; +\end{lstlisting} + +このコードは単にポインターをクラスで実装しているだけではないだろうか。ならば、ポインターでイテレーターを実装することもできるのではないか。 + +\begin{lstlisting}[language={C++}] +template < typename Array > +struct array_iterator +{ + using pointer = typename Array::pointer ; + using reference = typename Array::reference ; + pointer p ; + + array_iterator( pointer p ) + : p(p) { } + + reference operator *() + { return *p ; } + + array_iterator & operator ++() + { + ++p ; + return *this ; + } + + reference operator[] ( std::size_t n ) + { return p[n] ; } +} ; +\end{lstlisting} + +このコードは本当にポインターをクラスで実装しているだけだ。ならばイテレータークラスの代わりにポインターでもいいのではないだろうか。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + + T storage[N] ; + + // ポインター + using iterator = T * ; + + iterator begin() + { return &storage[0] ; } + +(@\ifTombow\pagebreak\fi@) + iterator end() + { return begin() + N ; } +} ; +\end{lstlisting} + +これは動く。そして実際の\texttt{std::array}の実装もこうなっている。 + +実はイテレーターはポインターを参考にして作られた。インクリメントで次の要素を参照、\texttt{operator *}\,で参照先の要素にアクセスといった操作は、すべてポインターの操作をより抽象化したものだ。 + +ポインターの操作をすべてサポートしたイテレーターは、ランダムアクセスイテレーターと呼ばれる。 + +\hypersection{ch2802}{イテレーターカテゴリー} +\index{いてれた@イテレーター!かてごり@カテゴリー} + +イテレーターにはサポートしている操作に応じて以下のような種類が存在する。 + +\begin{itemize} +\item + 入力イテレーター(Input Iterator) + \index{にゆうりよくいてれた@入力イテレーター}\index{いてれた@イテレーター!にゆうりよく@入力〜} +\item + 出力イテレーター(Output Iterator) + \index{しゆつりよくいてれた@出力イテレーター}\index{いてれた@イテレーター!しゆつりよく@出力〜} +\item + 前方イテレーター(Forward Iterator) + \index{ぜんぽういてれた@前方イテレーター}\index{いてれた@イテレーター!ぜんぽう@前方〜} +\item + 双方向イテレーター(Bidirectional Iterator) + \index{そうほうこういてれた@双方向イテレーター}\index{いてれた@イテレーター!そうほうこう@双方向〜} +\item + ランダムアクセスイテレーター(Random Access Iterator) + \index{らんだむあくせすいてれた@ランダムアクセスイテレーター}\index{いてれた@イテレーター!らんだむあくせす@ランダムアクセス〜} +\end{itemize} + +イテレーターの関係は以下のようになっている。 + +\begin{figure}[htbp] + \centering + \includegraphics[scale=1.0]{fig/fig31-01.eps} + \label{fig:31-01} +\end{figure} + +矢印\texttt{A → B}は\texttt{A}が\texttt{B}であることを意味している。 + +ランダムアクセスイテレーターは双方向イテレーターのすべての操作をサポートする。故にランダムアクセスイテレーターは双方向イテレーターである。 + +同様に、双方向イテレーターは前方イテレーターである。前方イテレーターは入力イテレーター/出力イテレーターである。 + +\texttt{A}は\texttt{B}であることに加えて、追加の操作をサポートしている。 + +\hypersubsection{ch280201}{ランダムアクセスイテレーター} +\index{らんだむあくせすいてれた@ランダムアクセスイテレーター}\index{いてれた@イテレーター!らんだむあくせす@ランダムアクセス〜} + +ランダムアクセスイテレーターは名前のとおりランダムアクセス\index{らんだむあくせす@ランダムアクセス}ができる。イテレーターが\texttt{n}番目の要素を指すとき、\texttt{n+m}番目の要素を指すことができる。\texttt{m}は負数でもよい。 + +\begin{lstlisting}[language={C++}] +template < typename RandomAccessIterator > +void f( RandomAccessIterator i, int n ) +{ + i + n ; + i - n ; + n + i ; // i+nと同じ + n - i ; // n-iと同じ + + i + (-n) ; // i - nと同じ + + // i = i + n ; と同じ + i += n ; + // i = i - n ; と同じ + i -= n ; +} +\end{lstlisting} +と書ける。\texttt{n}の型が符号付き整数型でよい。\texttt{i + (-5)}は\texttt{i-5}と同じ意味だ。 + +イテレーター間の距離を計算したいときはイテレーター同士を引き算する。 +\index{いてれた@イテレーター!きより@距離} + +\begin{lstlisting}[language={C++}] +template < typename RandomAccessIterator > +void f( RandomAccessIterator a, RandomAccessIterator b ) +{ + b - a ; // aからbまでの距離 + a - b ; // bからaまでの距離 +} +\end{lstlisting} + +イテレーター間の距離は負数にもなる。 + +\begin{lstlisting}[language={C++}] +template < typename RandomAccessIterator > +void f( RandomAccessIterator a ) +{ + auto b = a ; + // bはaより3進んでいる + ++b ; ++b ; ++b ; + b - a ; // 3 + a - b ; // -3 +} +\end{lstlisting} + +イテレーター\texttt{b}は\texttt{a}より3進んでいるので、\texttt{a}から\texttt{b}までの距離である\texttt{b - a}は3になる。では\texttt{b}から\texttt{a}までの距離である\texttt{a - b}はどうなるかというと、\(-3\)になる。\texttt{b}にとって\texttt{a}は3戻っているからだ。 + +イテレーター\texttt{i}の\texttt{n}個先の要素を参照したい場合は、 +\begin{lstlisting}[language={C++}] +template < typename RandomAccessIterator > +void f( RandomAccessIterator i, std::size_t n ) +{ + // *(i + n) ; と同じ + i[n] ; +} +\end{lstlisting} +と書ける。 + +ランダムアクセスイテレーターは大小比較ができる。 +\index{らんだむあくせすいてれた@ランダムアクセスイテレーター!だいしようひかく@大小比較}\index{いてれた@イテレーター!だいしようひかく@大小比較} + +\begin{lstlisting}[language={C++}] +template < typename RandomAccessIterator > +void f( RandomAccessIterator i, RandomAccessIterator j ) +{ + i < j ; + i > j ; + i <= j ; + i >= j ; +} +\end{lstlisting} + +イテレーターの比較は、イテレーターが参照する要素の値の比較ではない。イテレーターが参照する要素の順番の比較だ。 + +\texttt{n}番目の要素を参照するイテレーターは、\texttt{n+1}番目の要素を参照するイテレーターより小さい。\texttt{n-1}番目を参照するイテレーターより大きい。 + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void f( Iterator i ) +{ + // jはn+1番目を指す + auto j = i + 1 ; + + i < j ; // true + i > j ; // false +} +\end{lstlisting} + +ここまでの操作はランダムアクセスイテレーターにしかできない。 + +双方向イテレーター以下のイテレーターができる比較は同値比較だけだ。 +\index{いてれた@イテレーター!どうちひかく@同値比較} + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void f( Iterator i, Iterator j ) +{ + i == j ; + i != j ; +} +\end{lstlisting} + +イテレーターは同じ\texttt{n}番目の要素を指しているときに等しいと比較される。 + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void f( Iterator i ) +{ + auto j = i ; + i == j ; // true + ++j ; + i = j ; // false +} +\end{lstlisting} + +\hypersubsection{ch280202}{双方向イテレーター} +\index{そうほうこういてれた@双方向イテレーター}\index{いてれた@イテレーター!そうほうこう@双方向〜} + +双方向イテレーターは名前のとおり双方向のイテレーターの移動ができる。双方向というのはイテレーターが参照している\texttt{n}番目の要素の\texttt{n-1}番目の要素と\texttt{n+1}番目の要素だ。 + +\begin{lstlisting}[language={C++}] +template < typename BidirectionalIterator > +void f( BidirectionalIterator i ) +{ + ++i ; // i+1 + --i ; // i-1 + + // r1, r2は変更する前のiの値 + auto r1 = i++ ; + auto r2 = i-- ; +} +\end{lstlisting} +と書ける。この操作は前方イテレーターにはできない。 + +1個ずつ移動できるのであれば、イテレーターを\texttt{n}個進めることもできそうなものだ。実際、双方向イテレーターを以下のようにして\texttt{n}個進めることができる。 + +\begin{lstlisting}[language={C++}] +template < typename BidirectionalIterator > +BidirectionalIterator +nth_next( BidirectionalIterator iter, std::size_t n ) +{ + for ( std::size_t i = 0 ; i != n ; ++i ) + ++iter ; + return iter ; +} +\end{lstlisting} + +確かにこれはできる。できるが、効率的ではない。双方向イテレーターが提供される場合というのは、ランダムアクセスが技術的に可能ではあるが非効率的な場合だ。具体的なデータ構造を出すと、例えばリンクリストがある。リンクリストに対するランダムアクセスは技術的に可能であるが非効率的だ。 + +\hypersubsection{ch280203}{前方イテレーター} +\index{ぜんぽういてれた@前方イテレーター}\index{いてれた@イテレーター!ぜんぽう@前方〜} + +前方イテレーターは前方にしか移動できない。イテレーターが0番目の要素を指しているならば1番目、1番目の要素を指しているならば2番目に移動できる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +template < typename ForwardIterator > +void f( ForwardIterator i ) +{ + ++i ; +} +\end{lstlisting} + +前方イテレーターにはマルチパス保証がある。イテレーターの指す要素を動かす前のイテレーターの値を保持しておき、保持した値を動かしたとき、2つのイテレーターは同一になるという保証だ。 + +\begin{lstlisting}[language={C++}] +template < typename ForwardIterator > +void f( ForwardIterator i ) +{ + // 動かす前の値を保持 + auto prev = i ; + // 次の要素を指す + ++i ; + // 動かす前の値も次の要素を指すようにする + ++prev ; + + // true + bool b = ( i == prev ) ; + + // r1, r2は同じ要素を指す + auto & r1 = *i ; + auto & r2 = *prev ; +} +\end{lstlisting} + +入力イテレーター、出力イテレーターにはこの保証がない。 + +\hypersubsection{ch280204}{入力イテレーター} +\index{にゆうりよくいてれた@入力イテレーター}\index{いてれた@イテレーター!にゆうりよく@入力〜} + +入力イテレーターはイテレーターの比較、イテレーターの参照、イテレーターのインクリメントができる。 + +\begin{lstlisting}[language={C++}] +template < typename InputIterator > +void f( InputIterator i, InputIterator j ) +{ + // 比較 + bool b1 = (i == j) ; + bool b2 = (i != j) ; + + // 参照 + *i ; + // (*i).m と同じ + i->m ; + + // インクリメント + ++i ; + i++ ; +} +\end{lstlisting} + +入力イテレーターの参照は、読み込みことしか保証されていない。 + +\begin{lstlisting}[language={C++}] +template < typename InputIterator > +void f( InputIterator i ) +{ + // OK + auto value = *i ; + // エラー + *i = value ; +} +\end{lstlisting} + +書き込みは出力イテレーターの仕事だ。 + +\hypersubsection{ch280205}{出力イテレーター} +\index{しゆつりよくいてれた@出力イテレーター}\index{いてれた@イテレーター!しゆつりよく@出力〜} + +出力イテレーターはイテレーターのインクリメントと、イテレーターの参照への代入ができる。 + +\begin{lstlisting}[language={C++}] +template < typename OutputIterator > +void f( OutputIterator i, typename OutputIterator::value_type v ) +{ + // 参照への代入 + *i = v ; + + // インクリメント + ++i ; + i++ ; +} +\end{lstlisting} + +出力イテレーターを参照した結果は定められていない。\texttt{void}かもしれない。したがって出力イテレーターの値を読むのは意味がない。 + +\begin{lstlisting}[language={C++}] +template < typename OutputIterator > +void f( OutputIterator i ) +{ + // 意味がない + auto value = *i ; +} +\end{lstlisting} + +\hypersection{ch2803}{iterator\texttt{\_}traits} +\index{iterator\_traits@\texttt{iterator\_traits}} + +イテレーターカテゴリーやイテレーターの参照する値を見分けるためのライブラリとして、\texttt{iterator\_traits}\,がある。これは以下のようになっている。 + +\begin{lstlisting}[language={C++}] +namespace std { +template < typename T > +struct iterator +{ + using difference_type = ... ; + using value_type = ... ; + using pointer = ... ; + using reference = ... ; + using iterator_category = ... ; + +} ; + +} +\end{lstlisting} + +\texttt{difference\_type}はイテレーター同士の距離を指す数値だ。 +\index{difference\_type@\texttt{difference\_type}} + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void f( Iterator i, Iterator j ) +{ + // イテレーター同士の距離 + typename std::iterator_traits::difference_type diff = j - i ; +} +\end{lstlisting} + +\texttt{value\_type}はイテレーターの参照する値の型、\texttt{pointer}はそのポインター型、\texttt{reference}はそのリファレンス型だ。 +\index{value\_type@\texttt{value\_type}} + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void f( Iterator i ) +{ + // 値型 + typename std::iterator_traits::value_type v = *i ; + // ポインター型 + typename std::iterator_traits::pointer p = &v ; + // リファレンス型 + typename std::iterator_traits::reference r = v ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +\texttt{iterator\_category}はイテレーターカテゴリーを示す型で、以下のようになっている。 +\index{iterator\_category@\texttt{iterator\_category}} + +\begin{lstlisting}[language={C++}] +namespace std { +struct input_iterator_tag { }; +struct output_iterator_tag { }; +struct forward_iterator_tag: public input_iterator_tag { }; +struct bidirectional_iterator_tag: public forward_iterator_tag { }; +struct random_access_iterator_tag: public bidirectional_iterator_tag { }; +} +\end{lstlisting} + +\texttt{forward\_iterator\_tag}以降のコロン文字のあとに続くコードについては、いまは気にしなくてもよい。これは派生というまだ説明していないクラスの機能だ。 + +あるイテレーターがあるイテレーターカテゴリーを満たすかどうかを調べるには以下のようにする。 + +\begin{lstlisting}[language={C++}] +template < typename tag, typename Iterator > +constexpr bool is_category_of( ) +{ + using iter_tag = typename std::iterator_traits::iterator_category ; + return std::is_base_of_v< tag, iter_tag> ; +} + +int main() +{ + using iterator = std::vector::iterator ; + bool b = is_category_of< std::forward_iterator_tag, iterator >() ; + // vectorのイテレーターはランダムアクセスイテレーターなので前方イテレーターでもある + std::cout << b ; +} +\end{lstlisting} + +このコードはまだ学んでいないC++の機能をふんだんに使っているので、現時点で理解するのは難しい。 + +\clearpage +\hypersection{ch2804}{イテレーターカテゴリーの実例} + +イテレーターカテゴリーについて学んだので、イテレーターカテゴリーの実例について見ていこう。 + +\hypersubsection{ch280401}{出力イテレーター} +\index{しゆつりよくいてれた@出力イテレーター}\index{いてれた@イテレーター!しゆつりよく@出力〜} + +前方イテレーター以上のイテレーターカテゴリーを満たすイテレーターはすべて、出力イテレーターとして使える。例えば\texttt{std::array}の内容を\texttt{std::vector}にコピーしたければ以下のように書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a = {1,2,3,4,5} ; + std::vector v(5) ; + + std::copy( std::begin(a), std::end(a), std::begin(v) ) ; +} +\end{lstlisting} + +\texttt{std::vector}のイテレーターは出力イテレーターとして振る舞う。 + +出力イテレーターの要件しか満たさないイテレーターは、例えば以下のようなものだ。 + +\begin{lstlisting}[language={C++}] +struct cout_iterator +{ +// --- ボイラープレートコード + // 出力イテレーターでは使わないのでvoidでいい + using difference_type = void ; + using value_type = void ; + using reference = void ; + using pointer = void ; + // イテレーターカテゴリーは出力イテレーター + using iterator_category = std::output_iterator_tag ; + // 何もしない + // 自分自身を返すだけ + cout_iterator & operator *() { return *this ; } + cout_iterator & operator ++() { return *this ; } + cout_iterator & operator ++(int) { return *this ; } +// --- ボイラープレートコード + + // ここが肝心 + template < typename T > + cout_iterator & operator =( T const & x ) + { + std::cout << x ; + return *this ; + } +} ; + + +int main() +{ + std::vector v = {1,2,3,4,5} ; + cout_iterator out ; + + std::copy( std::begin(v), std::end(v), out ) ; +} +\end{lstlisting} + +\texttt{cout\_iterator}\index{cout\_iterator@\texttt{cout\_iterator}}は\,\texttt{*i = x;}\,と書いたときに、値\texttt{x}を\texttt{std::cout}で出力する。 + +\texttt{cout\_iterator}は出力イテレーターの要件を満たすので\texttt{std::copy}に渡せる。\texttt{std::copy}はイテレーターを順番に\,\texttt{*out = *i;}\,のように実行するので、結果として値がすべて\texttt{std::cout}で出力される。 + +\texttt{cout\_iterator}はとても便利なので、標準ライブラリには\texttt{std::ostream\_iterator}\,\index{ostream\_iterator@\texttt{ostream\_iterator}}がある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + std::ostream_iterator out(std::cout) ; + std::copy( std::begin(v), std::end(v), out ) ; +} +\end{lstlisting} + +\texttt{ostream\_iterator}\index{ostream\_iterator@\texttt{ostream\_iterator}}は出力ストリーム(\texttt{ostream})に対するイテレーターだ。コンストラクターに出力先の出力ストリームを渡すことで値を出力先に出力してくれる。今回は\texttt{std::cout}\index{cout@\texttt{cout}}だ。 + +上のような出力イテレーターが\texttt{operator =}\,で以下のようなことをしていたらどうだろう。 + +\begin{lstlisting}[language={C++}] +template < typename Container > +struct back_inserter ; +{ + back_inserter( Container & c ) + : c(c) { } + + // その他のボイラープレートコード + + back_inserter & operator =( const typename Container::value_type & value ) + { + c.push_back(value) ; + } + + Container & c ; +} ; + +(@\ifTombow\pagebreak\fi@) +template < typename Container > +void f( Container const & c ) +{ + // cの全要素をコピーしたい + std:vector< typename Container::value_type > temp ; + auto out = back_inserter(temp) ; + std::copy( std::begin(c), std::end(c), out ) ; +} +\end{lstlisting} + +このコードが何をするかわかるだろうか。コンテナー\texttt{c}の全要素を出力イテレーターで出力する。出力イテレーターは渡された値\texttt{value}を\texttt{temp.push\_back(value);}する。その結果、\texttt{temp}は\texttt{c}のすべての要素を保持していることになる。 + +C++の標準ライブラリには\texttt{std::back\_inserter}\index{back\_inserter@\texttt{back\_inserter}}がある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + std::vector temp ; + auto out = std::back_inserter(temp) ; + + std::copy( std::begin(v), std::end(v), out ) ; +} +\end{lstlisting} + +\texttt{std::back\_inserter(c)}はコンテナー\texttt{c}に出力イテレーターとして渡された値を\texttt{puch\_back}する。 + +ただし、\texttt{std::back\_inserter}は古いライブラリなので、ここで示した方法とは少し違う実装がされている。 + +\begin{lstlisting}[language={C++}] +// 出力イテレーター +template < typename Container > +struct back_insert_iterator +{ + back_insert_iterator( Container & c ) + : c(&c) { } + Container * c ; + + // その他のコード +} ; + +// 出力イテレーターを返す関数 +template < typename Container > +back_insert_iterator back_inserter( Container & c ) +{ + return back_insert_iterator(c) ; +} +\end{lstlisting} + +この理由は、C++17以前のC++ではクラスのコンストラクターからテンプレート実引数の推定ができなかったためだ。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void f( T ) { } + +template < typename T > +struct S +{ + S( T ) { } +} ; + +int main() +{ + // fと推定 + f(0) ; + + // Sと推定 + S s(0) ; +} +\end{lstlisting} + +C++17以前のC++では関数の実引数からテンプレート仮引数\texttt{T}の型を推定することはできたが、クラスのコンストラクターから推定することはできなかった。C++17以降は可能だ。 + +\texttt{std::cout}に出力したり、コンテナーに\texttt{push\_back}する実装のイテレーターは、マルチパス保証を満たさない。実装を見ればわかるように、イテレーターをコピーして別々にインクリメントした結果のイテレーターのオブジェクトに対する操作は同一ではないからだ。 + +\hypersubsection{ch280402}{入力イテレーター} +\index{にゆうりよくいてれた@入力イテレーター}\index{いてれた@イテレーター!にゆうりよく@入力〜} + +入力イテレーターの実例はどうか。 + +\texttt{std::cin}\index{cin@\texttt{cin}}から\texttt{T}型を読み込む入力イテレーターの実装は以下のようになる。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct cin_iterator +{ +// --- ボイラープレートコード + using difference_type = std::ptrdiff_t ; + using value_type = T ; + using reference = T & ; + using pointer = T * ; + // イテレーターカテゴリーは入力イテレーター + using iterator_category = std::input_iterator_tag ; +// --- ボイラープレートコード + +(@\ifTombow\pagebreak\fi@) + // コンストラクター + cin_iterator( bool fail = false ) + : fail(fail) + { ++*this ; } + + // キャッシュした値を返す + const reference operator *() const + { return value ; } + + // 新しい値をキャッシュする + cin_iterator & operator ++() + { + if ( !fail ) + { + std::cin >> value ; + fail = std::cin.fail() ; + } + return *this ; + } + + // 後置インクリメント + cin_iterator operator ++(int) + { + auto old = *this ; + ++*this ; + return old ; + } + + // イテレーターの等価比較の状態 + bool fail ; + // 値のキャッシュ + value_type value ; +} ; + +// 比較演算子 +template < typename T > +bool operator ==( cin_iterator const & l, cin_iterator const & r ) +{ return l.fail == r.fail ; } + +template < typename T > +bool operator !=( cin_iterator const & l, cin_iterator const & r ) +{ return !(l == r) ; } +\end{lstlisting} + +\ifTombow\pagebreak\fi +以下のように使える。 + +\begin{lstlisting}[language={C++}] +int main() +{ + cin_iterator input, fail(true) ; + std::vector buffer ; + + std::copy( input, fail, std::back_inserter(buffer) ) ; +} +\end{lstlisting} + +実装としては、まずボイラープレートコード +\begin{lstlisting}[language={C++}] +using difference_type = std::ptrdiff_t ; +using value_type = T ; +using reference = T & ; +using pointer = T * ; +// イテレーターカテゴリーは入力イテレーター +using iterator_category = std::input_iterator_tag ; +\end{lstlisting} +\texttt{difference\_type}\index{difference\_type@\texttt{difference\_type}}はイテレーターの距離を表現する型で、通常は\texttt{std::ptrdiff\_t}\index{ptrdiff\_t@\texttt{ptrdiff\_t}}という型が使われる。これはポインターの距離を表現する型だ。 + +\texttt{value\_type}\index{value\_type@\texttt{value\_type}}は参照している要素の型、\texttt{reference}\index{reference@\texttt{reference}}と\texttt{pointer}\index{pointer@\texttt{pointer}}は要素に対するリファレンスとポインターだ。 + +\texttt{iterator\_category}\index{iterator\_category@\texttt{iterator\_category}}は今回は入力イテレーターなので\texttt{std::input\_iterator\_tag}になる。 + +データメンバーが2つ。 + +\begin{lstlisting}[language={C++}] +bool fail ; +value_type value ; +\end{lstlisting} + +\texttt{fail}\index{fail@\texttt{fail}}は\texttt{std::cin}が失敗状態のときに\texttt{true}になる。通常は\texttt{false}だ。\texttt{std::cin}が失敗状態かどうかは、メンバー関数\texttt{fail}で確かめることができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + bool b = std::cin.fail() ; +} +\end{lstlisting} + +\texttt{std:cin}が失敗状態になる理由はいくつかあるが、EOF\index{EOF}が入力された場合や、指定した型の値を読み込めなかった場合、例えば\texttt{int}型を読み込むのに入力が\,\texttt{"abcd"}\,のような文字列だった場合に\texttt{true}になる。 + +\texttt{value}は\texttt{std::cin}から読み込んだ値だ。 + +イテレーターから値を読み込むのは\texttt{operator *}\,\index{\protect{*}@\texttt{\protect{*}}}の仕事だ。これは単に\texttt{value}を返す。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +const reference operator *() const +{ return value ; } +\end{lstlisting} + +入力イテレーターでは値の読み込みのみをサポートしている。書き込みはサポートしない。イテレーター \texttt{i}に対して\,\texttt{*i}\,は書けるが、\texttt{*i = x}とは書けない。 + +実際に\texttt{std::cin}から値を読み込むのは\texttt{operator ++}\index{\protect{++}@\texttt{\protect{++}}}で行われる。 + +\begin{lstlisting}[language={C++}] +cin_iterator & operator ++() +{ + // 失敗状態でなければ + if ( !fail ) + { + // 値を読み込む + std::cin >> value ; + // 失敗状態かどうかも調べる + fail = std::cin.fail() ; + } + return *this ; +} +\end{lstlisting} + +まず\texttt{std::cin}が失敗状態でないかどうかを確認する。失敗状態となった\texttt{std::cin}からは読み込めないからだ。失敗状態でなければ値を読み込み、失敗状態かどうかを確認する。結果の値は\texttt{value}に、失敗状態かどうかは\texttt{fail}に保持される。 + +後置インクリメントは前置インクリメントを呼び出すだけの汎用的な実装だ。 + +\begin{lstlisting}[language={C++}] +cin_iterator operator ++(int) +{ + // 元の値をコピーし + auto old = *this ; + // 次の値を読み込み + ++*this ; + // 元の値を返す + return old ; +} +\end{lstlisting} + +コンストラクターに\texttt{true}を渡すと、イテレーターを最初から失敗状態にしておく + +\begin{lstlisting}[language={C++}] +cin_iterator( bool fail = false ) + : fail(fail) +{ ++*this ; } +\end{lstlisting} + +コンストラクターは最初の値を読み込むために自分自身にインクリメントを呼び出す。 + +\ifTombow\pagebreak\fi +入力イテレーターは同値比較ができる。 +\index{にゆうりよくいてれた@入力イテレーター!どうちひかく@同値比較} + +\begin{lstlisting}[language={C++}] +template < typename T > +bool operator ==( cin_iterator const & l, cin_iterator const & r ) +{ return l.fail == r.fail ; } + +template < typename T > +bool operator !=( cin_iterator const & l, cin_iterator const & r ) +{ return !(l == r) ; } +\end{lstlisting} + +イテレーターが同値比較できると、イテレーターが終了条件に達したかどうかの判定ができる。 +\index{にゆうりよくいてれた@入力イテレーター!しゆうりようじようけん@終了条件} + +\begin{lstlisting}[language={C++}] +template < typename InputIterator > +void print( InputIterator iter, InputIterator end_iter ) +{ + // 終了条件に達するまで + while ( iter != end_iter ) + { // 値を標準出力する + std::cout << *iter ; + ++iter ; + } +} +\end{lstlisting} + +このような関数\texttt{print}に、\texttt{vector}の\texttt{begin}/\texttt{end}を渡すと、\texttt{vector}の要素をすべて標準出力する。 +\index{vector@\texttt{vector}!ひようじゆんしゆつりよく@標準出力} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + print( std::begin(v), std::end(v) ) ; +} +\end{lstlisting} + +\texttt{cin\_iterator}\index{cin\_iterator@\texttt{cin\_iterator}}を渡した場合、失敗状態になるまで標準出力する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + cin_iterator iter, fail(true) ; + print( iter, fail ) +} +\end{lstlisting} + +\texttt{cin\_iterator}が比較するのは\texttt{std::cin}の失敗状態の有無だ。この比較によって、\texttt{cin\_iterator}で標準入力から失敗するまで値を読み込み続けることができる。 + +このようなイテレーターは標準に\texttt{std::istream\_iterator}\,として存在する。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::istream_iterator iter( std::cin ), end_iter ; + std::vector v ; + + std::copy( iter, end_iter, std::back_inserter(v) ) ; +} +\end{lstlisting} + +標準ライブラリは読み込むストリームをコンストラクターで取る。何も指定しない場合は失敗状態になる。 + +\hypersubsection{ch280403}{前方イテレーター} +\index{ぜんぽういてれた@前方イテレーター}\index{いてれた@イテレーター!ぜんぽう@前方〜} + +前方イテレーター以上のイテレーターの例として、\texttt{iota\_iterator}\,\index{iota\_iterator@\texttt{iota\_iterator}}を実装してみよう。 + +このイテレーターは\texttt{T}型の整数を保持し、\texttt{operator *}\,でリファレンスを返し、\texttt{operator ++}でインクリメントする。 + +以下のように使える。 + +\begin{lstlisting}[language={C++}] +int main() +{ + iota_iterator iter(0) ; + *iter ; // 0 + *++iter ; // 1 + *++iter ; // 2 + + iota_iterator first(0), last(10) ; + + // 0123456789と出力される + std::for_each( first, last, + [](auto i){ std::cout << i ;} + ) ; + + std::vector v ; + std::copy( first, last, std::back_inserter(v) ) ; + // vは{0,1,2,3,4,5,6,7,8,9} +} +\end{lstlisting} + +さっそく実装してみよう。まずはネストされた型名と初期化から。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct iota_iterator +{ + // イテレーター同士の距離を表現する型 + using difference_type = std::ptrdiff_t ; +(@\ifTombow\pagebreak\fi@) + // 要素の型 + using value_type = T ; + using reference = T & ; + using pointer = T * ; + // イテレーターカテゴリーは前方イテレーター + using iterator_category = std::forward_iterator_tag ; + + // 値を保持する + T value ; + + // コンストラクター + iota_iterator( T value = 0 ) + : value(value) + { } + + // 残りのコード +} ; +\end{lstlisting} + +これでイテレーターとしてオブジェクトを作ることができるようになる。コピーは自動的に生成されるので書く必要はない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // i(0) + iota_iterator i ; + // iota_iterator + iota_iterator first(0), last(10) ; + + // lastをiにコピー + i = last ; +} +\end{lstlisting} + +残りのコードも書いていこう。\texttt{operator *}\,\index{\protect{*}@\texttt{\protect{*}}}は単に\texttt{value}を返すだけだ。 + +\begin{lstlisting}[language={C++}] +// 非const版 +reference operator *() noexcept +{ return value ; } +// const版 +const reference operator *() const noexcept +{ return value ; } +\end{lstlisting} + +非\texttt{const}版と\texttt{const}版があるのは、\texttt{const}な\texttt{iota\_iterator}のオブジェクトからも使えるようにするためだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + // 非constなオブジェクト + iota_iterator non_const(0) ; + // 非const版のoperator *を呼び出す + int value = *non_const ; + // 変更できる + *non_const = 1 ; + + // constなオブジェクト + iota_iterator immutable(0) ; + // const版のoperator *を呼び出す + int const_value = *immutable ; + // 変更はできない +} +\end{lstlisting} + +\texttt{noexcept}はこの関数は例外を外に投げないという宣言だ。今回、例外を投げる処理は使わないので、\texttt{noexcept}を指定できる。 + +\texttt{operator ++}\index{\protect{++}@\texttt{\protect{++}}}を実装しよう。 + +\begin{lstlisting}[language={C++}] +// 前置 +iota_iterator & operator ++() noexcept +{ + ++value ; + return *this ; +} +// 後置 +iota_iterator operator ++(int) noexcept +{ + auto temp = *this ; + ++*this ; + return temp ; +} +\end{lstlisting} + +すでに説明したようにインクリメント演算子には前置後置の2種類が存在する。 + +\begin{lstlisting}[language={C++}] +++i ; // 前置 +i++ ; // 後置 +\end{lstlisting} + +前置インクリメント演算子は引数を取らず、後置インクリメント演算子は区別のためだけに特に意味のない\texttt{int}型の引数を取る。 + +インクリメント演算子も例外を投げないので\texttt{noexcept}を指定する。 + +インクリメント演算子はデータメンバーを変更するので\texttt{const}は指定しない。 + +最後は比較演算子だ。 + +\begin{lstlisting}[language={C++}] +bool operator == ( iota_iterator const & i ) const noexcept +{ + return value == i.value ; +} +bool operator != ( iota_iterator const & i ) const noexcept +{ + return !(*this == i) ; +} +\end{lstlisting} + +前方イテレーターがサポートする比較演算子は2つ、\texttt{operator ==}\,\index{==@\texttt{==}}と\texttt{operator !=}\,\index{\protect{!}=@\texttt{\protect{!}=}}だ。\texttt{!=}\,は\,\texttt{==}\,で実装してしまうとして、\texttt{==}\,は単に\texttt{value}を比較する。通常、イテレーターの比較は要素の値の比較ではなく、同じ要素を参照するイテレーターかどうかの比較になるが、\texttt{iota\_iterator}の場合、\texttt{vector}や\texttt{array}のようなメモリー上に構築された要素は存在しないので、\texttt{value}の比較でよい。 + +前方イテレーターが提供される実例としては、前方リンクリストがある。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct forward_link_list +{ + T value ; + forward_link_list * next ; +} ; + +int main() +{ + forward_link_list list3{ 3, nullptr } ; + forward_link_list list2{ 2, &list3 } ; + forward_link_list list1{ 1, &list2 } ; + forward_link_list list0{ 0, &list1 } ; +} +\end{lstlisting} + +この\texttt{forward\_link\_list}\,\index{forward\_link\_list@\texttt{forward\_link\_list}}というクラスは\texttt{T}型の値を保持する\texttt{value}と、次のクラスのオブジェクトを参照するポインター\texttt{next}を持っている。このクラス\texttt{list}の次の要素は\,\texttt{*(list.next)}で、\texttt{list}の2つ次の要素は\,\texttt{*(*list.next).next)}だ。 + +このような\texttt{forward\_link\_list}\,へのイテレーターの骨子は以下のように書ける。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct iterator +{ + forward_link_list * ptr ; + + T & operator *() noexcept + { + return ptr->value ; + } + + iterator & operator ++() noexcept + { + ptr = ptr->next ; + return *this ; + } +// 省略 +} ; +\end{lstlisting} + +前方リンクリストは\texttt{vector}や\texttt{array}のように要素の線形の集合を表現できる。\texttt{n}番目の要素から\texttt{n+1}番目の要素を返すことはできる。 + +\begin{lstlisting}[language={C++}] +// n+1番目の要素を返す関数 +template < typename T > +forward_link_list & next( forward_link_list & list ) noexcept +{ + // 次の要素 + return *list.next ; +} +\end{lstlisting} + +ただし\texttt{n-1}番目の要素を返すことはできない。その方法がないからだ。 + +前方イテレーターが入力/出力イテレーターと違う点は、マルチパス保証\index{まるちぱすほしよう@マルチパス保証}があることだ。イテレーターのコピーを使い回して複数回同じ要素をたどることができる。 + +\begin{lstlisting}[language={C++}] +template < typename ForwardIterator > +void f( ForwardIterator first, ForwardIterator last ) +{ + using vector_type = std::vector< typename ForwardIterator::value_type > ; + + // 全要素の値をv1にコピー + vector_type v1 ; + for ( auto iter = first ; iter != last ; ++iter ) + v1.push_back( *iter ) ; + + // 全要素の値をv2にコピー + // イテレーターがもう一度使われる + vector_type v2 ; + for ( auto iter = first ; iter != last ; ++iter ) + v2.push_back( *iter ) ; + + // マルチパス保証があれば常にtrue + bool b = v1 == v2 ; +} +\end{lstlisting} + +前方イテレーター以上のイテレーターにはこのマルチパス保証がある。 + +\hypersubsection{ch280404}{双方向イテレーター} +\index{そうほうこういてれた@双方向イテレーター}\index{いてれた@イテレーター!そうほうこう@双方向〜} + +双方向イテレーターは\texttt{n}番目の要素を指すイテレーターから\texttt{n-1}番目を指すイテレーターを得られるイテレーターだ。\texttt{n-1}番目を指すには\texttt{operator {-}{-}}\,\index{{-}{-}@\texttt{{-}{-}}}を使う。 + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void f( Iterator i ) +{ + ++i ; // n+1番目 + --i ; // n-1番目 +} +\end{lstlisting} + +\texttt{iota\_iterator}\index{iota\_iterator@\texttt{iota\_iterator}}を双方向イテレーターにするのは簡単だ。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct iota_iterator +{ + // イテレーターカテゴリー + using iterator_category = std::bidirectional_iterator_tag ; + + iota_iterator & operator --() noexcept + { + --value ; + return *this ; + } + iota_iterator operator --(int) noexcept + { + auto temp = *this ; + --*this ; + return temp ; + } + + // 省略 +} ; +\end{lstlisting} + +イテレーターカテゴリーは双方向イテレーターを表現する\texttt{std::bidirectional\_iterator\_tag}\index{bidirectional\_iterator\_tag@\texttt{bidirectional\_iterator\_tag}}を指定する。 + +\texttt{operator {-}{-}}\,の実装は\texttt{operator ++}の実装と要領は同じだ。 + +これで\texttt{iota\_iterator}が双方向イテレーターになった。 + +双方向イテレーターが提供される実例としては、双方向リンクリストがある。前方リンクリストが前方の要素への参照を持つのに対し、双方向リンクリストは後方の要素への参照も持つ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +template < typename T > +struct bidirectional_link_list +{ + T value ; + + bidirectional_link_list * next ; + bidirectional_link_list * prev ; +} ; +\end{lstlisting} + +双方向リンクリストに対するイテレーター操作の骨子は以下のようになる。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct iterator +{ + // 前方 n+1 + iterator & operator ++() noexcept + { + ptr = ptr->next ; + return *this ; + } + // 後方 n-1 + iterator & operator --() noexcept + { + ptr = ptr->prev ; + return *this ; + } +} ; +\end{lstlisting} + +\hypersubsection{ch280405}{ランダムアクセスイテレーター} +\index{らんだむあくせすいてれた@ランダムアクセスイテレーター}\index{いてれた@イテレーター!らんだむあくせす@ランダムアクセス〜} + +ランダムアクセスイテレーターにできることは多い。すでにランダムアクセスイテレーターでできることは解説したので、\texttt{iota\_iterator}\index{iota\_iterator@\texttt{iota\_iterator}}を対応させていこう。 + +イテレーターの参照する要素の移動の部分。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct iota_iterator +{ + iota_iterator & operator += ( difference_type n ) + { + value += n ; + return *this ; + } +(@\ifTombow\pagebreak\fi@) + iota_iterator operator + ( difference_type n ) const + { + auto temp = *this ; + temp += n ; + return *this ; + } + iota_iterator & operator -= ( difference_type n ) + { + value -= n ; + return *this ; + } + iota_iterator operator - ( difference_type n ) const + { + auto temp = *this ; + temp -= n ; + return *this ; + } + // 省略 +} ; + +// difference_type + iota_iteratorの場合 +template < typename T > +iota_iterator operator + +( + typename iota_iterator::difference_type n, + iota_iterator const & i +) +{ return i + n ; } + + +template < typename T > +iota_iterator operator - +( + typename iota_iterator::difference_type n, + iota_iterator const & i +) +{ return i - n ; } +\end{lstlisting} + +ランダムアクセスイテレーター \texttt{i}と\texttt{difference\_type n}があるとき、\texttt{i + n}と\texttt{n + i}は同じ意味だ。\texttt{i + n}はイテレーターのメンバー関数としても、クラス外のフリー関数としても実装できる。どちらでも好きな方法で実装してよい。 + +\ifTombow\pagebreak\fi +参考に、クラス外のフリー関数として実装する場合は以下のようになる。 + +\begin{lstlisting}[language={C++}] +template < typename T > +iota_iterator operator + +( + iota_iterator i, + typename iota_iterator::difference_type n +) +{ return i + n ; } + + +template < typename T > +iota_iterator operator - +( + iota_iterator i, + typename iota_iterator::difference_type n +) +{ return i - n ; } +\end{lstlisting} + +\texttt{n + i}は必ずクラス外のフリー関数として実装しなければならない。クラスのメンバー関数として演算子のオーバーロードをする場合はオペランドが\texttt{this}になるからだ。 + +イテレーターの距離の実装は\texttt{iota\_iterator}の場合、単に\texttt{value}の差だ。 + +メンバー関数として実装する場合は以下のとおり。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct iota_iterator +{ + difference_type operator - ( iota_iterator const & i ) + { + return value - i.value ; + } +} ; +\end{lstlisting} + +クラス外のフリー関数として実装する場合は以下のとおり。 + +\begin{lstlisting}[language={C++}] +template < typename T > +typename iota_iterator::difference_type +( iota_iterator const & a, iota_iterator const & b ) +{ + return a.value - b.value ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +大小比較の実装も\texttt{value}を比較するだけだ。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct iota_iterator +{ + bool operator < ( iota_iterator const & i ) const noexcept + { return value < i.value ; } + bool operator <= ( iota_iterator const & i ) const noexcept + { return value <= i.value ; } + bool operator > ( iota_iterator const & i ) const noexcept + { return value > i.value ; } + bool operator >= ( iota_iterator const & i ) const noexcept + { return value >= i.value ; } + // 省略 +} ; +\end{lstlisting} + +ランダムアクセスイテレーターの実例としては、連続したメモリー上に構築された要素の集合に対するイテレーターがある。標準ライブラリでは、\texttt{vector}や\texttt{array}が該当する。 + +\texttt{vector}や\texttt{array}の中身は連続したメモリー上に確保された要素で、要素の参照にはポインターか、ポインターとインデックスが用いられる。 + +\begin{lstlisting}[language={C++}] +// arrayやvectorのイテレーター +template < typename T > +struct iterator +{ + T * ptr ; + + T & operator * () { return *ptr ; } + iterator & operator ++ () noexcept + { ++ptr ; return *this ; } + // その他のメンバー +} ; +\end{lstlisting} + +\texttt{vector}や\texttt{array}のイテレーターの実装は、ポインターとほぼ同じ処理をしている。その実装は上にあるように、単にポインターに処理をデリゲートするだけだ。 + +そこで、C++標準ライブラリの実装によっては、\texttt{vector}や\texttt{array}の実装は単に生のポインターを返す。 + +\begin{lstlisting}[language={C++}] +template < typename T, std::size_t N > +struct array +{ + T storage[N] ; + + T * begin() noexcept + { return storage ; } + T * end() noexcept + { return storage + N ; } +} ; +\end{lstlisting} + +イテレーターはクラスであり、そのネストされた型名に\texttt{value\_type}や\texttt{difference\_type}や\texttt{iterator\_category}などの型がある。 + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +// ネストされた型名を使う +typename Iterator::reference_type +get_value( Iterator i ) +{ + return *i ; +} +\end{lstlisting} + +\texttt{vector}や\texttt{array}のイテレーターが単に生のポインターを返す実装の場合、上のコードは動かない。 + +こういうときのために、\texttt{iterator\_traits}\,がある。もし\texttt{T}がポインターの場合は、ネストされた型名を都合のいいように宣言してくれる。 + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +// ポインターにも対応 +typename std::iterator_traits::reference_type +get_value( Iterator i ) +{ + return *i ; +} +\end{lstlisting} + +そのため、イテレーターのネストされた型名を使うときには、直接使うのではなく、一度\texttt{iterator\_traits}を経由して使うとよい。 + +\clearpage +\hypersection{ch2805}{イテレーター操作} +\index{いてれた@イテレーター!そうさ@操作} + +イテレーターはそのまま使うこともできるが、一部の操作を簡単に行うための標準ライブラリがある。 + +\hypersubsection{ch280501}{advance( i, n ): n移動する} +\index{いてれた@イテレーター!advance@\texttt{advance}}\index{いてれた@イテレーター!いどう@移動} + +イテレーター \texttt{i}を\texttt{n}回移動したいとする。ランダムアクセスイテレーターならば以下のようにする。 + +\begin{lstlisting}[language={C++}] +i += n ; +\end{lstlisting} + +しかし前方イテレーターの場合、\texttt{operator +=}\,は使えない。\texttt{n}回\texttt{operator ++}を呼び出す必要がある。 + +\begin{lstlisting}[language={C++}] +for ( auto count = 0 ; count != n ; ++count ) + ++i ; +\end{lstlisting} + +双方向イテレーターの場合、\texttt{n}は負数の場合がある。\texttt{n}が負数の場合、\texttt{n}回\texttt{operator {-}{-}}\,を呼び出すことになる。 + +\begin{lstlisting}[language={C++}] +if ( n > 0 ) + for ( auto count = 0 ; count != n ; ++count ) + ++i ; +else + for ( auto count = 0 count != n ; --count ) + --i ; +\end{lstlisting} + +双方向イテレーター用のコードはランダムアクセスイテレーターでも動くが非効率的だ。 + +いま使っているイテレーターの種類を把握して適切な方法を選ぶコードを書くのは面倒だ。そこで標準ライブラリには、イテレーター \texttt{i}を\texttt{n}回移動してくれる\texttt{advance(i, n)}がある。 + +\begin{lstlisting}[language={C++}] +// iを1前方に移動 +std::advance(i, 1) ; +// iを5前方に移動 +std::advance(i, 5) ; +// iを5後方に移動 +std::advance(i, -5) ; +// iは移動しない +std::advance(i, 0) ; +\end{lstlisting} + +\texttt{n}が正数の場合は前方(\texttt{i+1}の方向)に、\texttt{n}が負数の場合は後方(\texttt{i-1}の方向)に、それぞれ\texttt{n}回移動させる。 + +\texttt{advance(i,n)}は\texttt{i}自体が書き換わる。 + +\begin{lstlisting}[language={C++}] +i ; // n番目を指す +std::advance( i, 1 ) ; +i ; // n+1番目を指す +\end{lstlisting} + +\hypersubsection{ch280502}{distance(first, last): firstからlastまでの距離} +\index{いてれた@イテレーター!distance@\texttt{distance}}\index{いてれた@イテレーター!きより@距離} + +イテレーター\texttt{first}から\texttt{last}までの距離を求めたいとする。 + +ランダムアクセスイテレーターならば以下のようにする。 + +\begin{lstlisting}[language={C++}] +auto dist = last - first ; +\end{lstlisting} + +それ以外のイテレーターならば、\texttt{first}が\texttt{last}と等しくなるまで\texttt{operator ++}を呼び出す。 + +\begin{lstlisting}[language={C++}] +std::size_t dist = 0 ; +for ( auto iter = first ; iter != last ; ++iter ) + ++dist ; +\end{lstlisting} + +これをやるのも面倒なので標準ライブラリがある。 + +\texttt{distance( first, last )}は\texttt{first}から\texttt{last}までの距離を返す。 +\index{distance@\texttt{distance}} + +\begin{lstlisting}[language={C++}] +// iからjまでの距離を返す +auto dist = std::distance( i, j ) ; +\end{lstlisting} + +ランダムアクセスイテレーターならば\texttt{j - i}と同じで、そうでなければ\texttt{i}が\texttt{j}と等しくなるまで\texttt{operator ++}を呼び出す。 + +\texttt{distance}に渡したイテレーターは変更されない。 + +\hypersubsection{ch280503}{next/prev : 移動したイテレーターを返す} +\index{いてれた@イテレーター!next@\texttt{next}}\index{いてれた@イテレーター!prev@\texttt{prev}} + +\texttt{advance(i, n)}はイテレーター\texttt{i}を変更してしまう。イテレーターを変更させずに移動後のイテレーターもほしい場合、以下のように書かなければならない。 + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void f( Iterator i ) +{ + auto j = i ; + std::advance( j, 3 ) ; + // jはiより3前方に移動している +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +標準ライブラリの\texttt{next}/\texttt{prev}は、引数に渡したイテレーターを変更せず、移動後のイテレーターを返してくれる。 + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void f( Iterator i ) +{ + auto j = std::next( i, 3 ) ; + // jはiより3前方に移動している +} +\end{lstlisting} + +\texttt{prev}はその逆だ。 + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void f( Iterator i ) +{ + auto j = std::prev( i, 3 ) ; + // jはiより3後方に移動している + // jはstd::advance(i, 3)したあとのiと同じ値 +} +\end{lstlisting} + +\texttt{next}/\texttt{prev}に第二引数を渡さない場合、前後に1だけ移動する。 + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void f( Iterator i ) +{ + auto j = std::next(i) ; + // jは++iしたのと同じ値 + auto k = std::prev(i) ; + // kは--iしたのと同じ値 +} +\end{lstlisting} + +\clearpage +\hypersection{ch2806}{リバースイテレーター} +\index{りばすいてれた@リバースイテレーター}\index{いてれた@イテレーター!りばす@リバース〜} + +イテレーターは要素を順番どおりにたどる。例えば以下は要素を順番に出力する関数テンプレート\texttt{print}だ。 + +\begin{lstlisting}[language={C++}] +template < typename Iterator > +void print( Iterator first, Iterator last ) +{ + for ( auto iter = first ; iter != last ; ++iter ) + std::cout << *iter ; +} +\end{lstlisting} + +逆順に出力するにはどうすればいいのだろうか。 +\index{いてれた@イテレーター!ぎやくじゆん@逆順} + +双方向イテレーター以上ならば逆順にたどることはできる。すると逆順に出力する関数テンプレート\texttt{reverse\_print}は以下のように書ける。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void reverse_print( Iterator first, Iterator last ) +{ + for ( auto iter = std::prev(last) ; iter != first ; --iter ) + { + std::cout << *iter ; + } + // 最初の要素の出力 + std::cout << *iter ; +} +\end{lstlisting} + +しかしイテレーターを正順にたどるか逆順にたどるかという違いだけで、本質的に同じアルゴリズム、同じコードを2度も書きたくはない。そういうときに役立つのがリバースイテレーターだ。 + +\texttt{std::reverse\_iterator}\,\index{reverse\_iterator@\texttt{reverse\_iterator}}はイテレーター\texttt{Iterator}に対するリバースイテレーターを提供する。リバースイテレーターはイテレーターのペア \texttt{[first,last)}を受け取り、\texttt{last}の1つ前の要素が先頭で\texttt{first}の要素が末尾になるような順番のイテレーターにしてくれる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // std::reverse_iterator< std::vector::iterator > + std::reverse_iterator first{ std::end(v) } ; + std::reverse_iterator last{ std::begin(v) } ; + +(@\ifTombow\pagebreak\fi@) + // 54321 + std::for_each( first, last, + [](auto x ){ std::cout << x ; } ) ; +} +\end{lstlisting} + +これで、\texttt{print}と\texttt{reverse\_print}のような本質的に同じコードを重複して書かずに済む。 + +リバースイテレーターはとても便利なので、\texttt{std::vector}のような標準ライブラリのコンテナーには最初からネストされた型名としてリバースイテレーター\,\texttt{::reverse\_iterator}がある。リバースイテレーターを返す\texttt{rbegin}/\texttt{rend}もある。 +\index{rbegin@\texttt{rbegin}}\index{rend@\texttt{rend}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // std::vector::reverse_iterator + auto first = std::rbegin(v) ; + auto last = std::rend(v) ; + + std::for_each( first, last, + [](auto x ){ std::cout << x ; } ) ; +} +\end{lstlisting} + diff --git a/TeX/032-memory-allocation.tex b/TeX/032-memory-allocation.tex new file mode 100644 index 0000000..be42593 --- /dev/null +++ b/TeX/032-memory-allocation.tex @@ -0,0 +1,443 @@ +\hyperchapter{ch29}{動的メモリー確保}{動的メモリー確保} + +\hypersection{ch2901}{概要} + +動的メモリー確保\index{どうてきめもりかくほ@動的メモリー確保}\index{めもり@メモリー!どうてきかくほ@動的〜確保}は任意のサイズのメモリーを確保できる機能だ。 + +例えば\texttt{std::vector}は任意個の要素を保持できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int input { } ; + std::vector v ; + while ( std::cin >> input ) + { + v.push_back( input ) ; + } +} +\end{lstlisting} + +このプログラムは任意個の\texttt{int}型の値を保持する。いくつ保持するかはコンパイル時にはわからないし、実行途中にもわからない。プログラムが終了するまで、実際にいくつ値を保持したのかはわからない。 + +このような事前にいくつの値を保持するかわからない状況では、動的メモリー確保を使う。 + +\clearpage +\hypersection{ch2902}{malloc/free} +\index{malloc@\texttt{malloc}}\index{free@\texttt{free}} + +\texttt{malloc}/\texttt{free}はC言語から受け継いだ素朴な動的メモリー確保のライブラリだ。 + +\begin{lstlisting}[language={C++}] +namespace std { + void * malloc ( std::size_t size ) ; + void free ( void * ptr ) ; +} +\end{lstlisting} + +\texttt{malloc(n)}は\texttt{n}バイトの生のメモリーを確保\index{めもり@メモリー!かくほ@確保}して、その先頭バイトへのポインターを返す。 + +\begin{lstlisting}[language={C++}] +// 5バイトのメモリーを確保 +void * ptr = std::malloc( 5 ) ; +\end{lstlisting} + +これによって確保されるメモリーは、1バイトごとのメモリーが配列のように連続したメモリーだ。型で書くと、\texttt{std::byte [5]}のようなものだ。 + +確保したメモリーは\texttt{free}で解放\index{めもり@メモリー!かいほう@解放}するまで有効だ。\texttt{free(ptr)}は\texttt{malloc}が返したポインター\texttt{ptr}を解放する。その結果、メモリーはまた再び\texttt{malloc}によって再利用できるようになる。 + +\begin{lstlisting}[language={C++}] +// 5バイトの生のメモリーを確保 +void * ptr = std::malloc( 5 ) ; +// 解放 +std::free( ptr ) ; +// これ以降、 ptrの値は無効 +\end{lstlisting} + +\hypersection{ch2903}{operator new/operator delete} +\index{new@\texttt{new}}\index{delete@\texttt{delete}} + +C++の追加した生のメモリーを確保する方法が、\texttt{operator new}と\texttt{operator delete}だ。 + +\begin{lstlisting}[language={C++}] +// グローバル名前空間 +void * operator new ( std::size_t size ); +void operator delete ( void * ptr ) ; +\end{lstlisting} + +使い方は\texttt{malloc}とほぼ同じだ。\texttt{"operator new"}\,までが名前なので少し混乱するが、通常の関数呼び出しと同じだ。 + +\begin{lstlisting}[language={C++}] +void * ptr = ::operator new( 5 ) ; +\end{lstlisting} + +グローバル名前空間であることを明示するために\,\texttt{::}\,を使っている。 + +\texttt{operator new}で確保したメモリーは、\texttt{operator delete}で解放するまで有効だ。 + +\begin{lstlisting}[language={C++}] +void * ptr = ::operator new( 5 ) ; +::operator delete ( ptr ) ; +\end{lstlisting} + +\hypersection{ch2904}{生のバイト列を基本的な型の値として使う方法} + +\texttt{int}や\texttt{double}のような基本的な型は、生のバイト列のポインターを型変換\index{ぽいんた@ポインター!かたへんかん@型変換}するだけで使える。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 生のメモリーを確保 +\item + ポインターを型変換 +\item + 値を代入 +\end{enumerate} + +\vskip -0.5zw +\begin{lstlisting}[language={C++}] +int main() +{ + // 確保 + void * void_ptr = ::operator new( sizeof(int) ) ; + // 型変換 + int * int_ptr = static_cast( void_ptr ) ; + // 代入 + *int_ptr = 0 ; + // 解放 + ::operator delete ( void_ptr ) ; +} +\end{lstlisting} + +\texttt{int}型のサイズは\texttt{sizeof(int)}バイトなので、\texttt{sizeof(int)}バイトのメモリーを確保する。\texttt{void *}\,型から\texttt{int *}\,型に型変換する。あとはポインターを経由して使うだけだ。 +\index{sizeof@\texttt{sizeof}} + +ポインターの文法がわかりにくい場合、リファレンスを使うこともできる。 + +\begin{lstlisting}[language={C++}] +int & int_ref = *int_ptr ; +\end{lstlisting} + +\texttt{malloc}や\texttt{operator new}が返すメモリーの値は不定だ。なので、確保した生のメモリーへのポインターを、実際に使う型のポインターに型変換して、その値を参照しようとすると、結果は未定義だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // ここまでは定義された挙動 + int * ptr = static_cast( ::operator new(sizeof(int)) ) ; + // 未定義の挙動 + std::cout << *ptr ; +} +\end{lstlisting} + +このプログラムを実行した結果、何が起こるかはわからない。 + +\hypersection{ch2905}{メモリー確保の失敗} +\index{めもり@メモリー!かくほしつぱい@確保失敗} + +メモリー確保は失敗する可能性がある。現実のコンピューターは有限のリソースしか持たないために、メモリーも当然有限のリソースだ。 + +\texttt{malloc}が失敗\index{malloc@\texttt{malloc}!しつぱい@失敗}すると、\texttt{nullptr}\index{nullptr@\texttt{nullptr}}が返される。\texttt{malloc}が失敗したかどうかを調べるには、戻り値を\texttt{nullptr}と比較すればよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + void * ptr = std::malloc( 1 ) ; + + if ( ptr == nullptr ) { + // メモリー確保失敗 + } else { + // メモリー確保成功 + } +} +\end{lstlisting} + +\texttt{operator new}が失敗すると、\texttt{std::bad\_alloc}\index{bad\_alloc@\texttt{bad\_alloc}}が投げられる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + try { + void * ptr = ::operator new( 1 ) ; + // メモリー確保成功 + } catch ( std::bad_alloc e ) + { + // メモリー確保失敗 + } +} +\end{lstlisting} + +たいていの環境ではメモリー確保が失敗したときにできることは少ない。そのままプログラムを終了するのが最も適切な処理だ。というのも、ほとんどの処理にはメモリー確保が必要だからだ。 + +例外の場合、\texttt{catch}しなければプログラムは終了する。\texttt{malloc}の場合、自分でメモリー確保が失敗したかどうかを調べてプログラムを終了しなければならない。プログラムを途中で強制的に終了するには、\texttt{std::abort}\index{abort@\texttt{abort}}が使える。 + +\begin{lstlisting}[language={C++}] +void f() +{ + void * ptr = malloc(1) ; + +(@\ifTombow\pagebreak\fi@) + // 失敗判定 + if ( ptr == nullptr ) + std::abort() ; + + // 成功 +} +\end{lstlisting} + +\hypersection{ch2906}{クラス型の値の構築} +\index{くらす@クラス!あたいのこうちく@値の構築} + +動的に確保したメモリーを\texttt{int}や\texttt{double}のような基本的な型の値として使うには以下のように書けばよいことはすでに学んだ。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + その型のサイズ分のメモリーを確保 +\item + ポインターを型変換 +\item + 適切な値を代入 +\end{enumerate} + +より汎用的にテンプレートを使って書くと以下のようになる。 + +\begin{lstlisting}[language={C++}] +// 動的確保したメモリーをT型の値として使う +template < typename T > +void dynamic_allocate() +{ + // 1. その型のサイズ分のメモリーを確保 + void * ptr = ::operator new( sizeof(T) ) ; + // 2. ポインターを型変換 + T * T_ptr = static_cast( ptr ) ; + // 3. 適切な値を代入 + *T_ptr = T{} ; + ::operator delete( ptr ) ; +} + +int main() +{ + dynamic_allocate() ; + dynamic_allocate() ; +} +\end{lstlisting} + +この方法は、ほとんどのクラスには使えない。例えば\texttt{std::vector}\,には使えない。 + +\begin{lstlisting}[language={C++}] +// エラー +dynamic_allocate< std::vector >() ; +\end{lstlisting} + +\ifTombow\pagebreak\fi +「ほとんどのクラス」と書いたからには、使えるクラスもあるということだ。例えば以下のようなクラスでは使える。 + +\begin{lstlisting}[language={C++}] +struct Simple +{ + int i ; + double d ; +} ; + +int main() +{ + // 使える + dynamic_allocate() ; +} +\end{lstlisting} + +なぜ\texttt{Simple}のようなクラスでは使えるのだろうか。\texttt{std::vector}\,とはどう違うのか。この違いを厳密に解説するためには、とても長くて厳密なC++の標準規格の理解が必要だ。とても難しいため、本書では解説しない。 + +クラスの値を使うためには、メモリー上にクラスのオブジェクトを構築する必要がある。クラスの構築にはコンストラクター呼び出し以外にも、そのメモリーをクラスのオブジェクトとして使うのに必要な何らかの初期化が含まれる。 + +\begin{lstlisting}[language={C++}] +// sizeof(std::vector)バイトのメモリーを確保し +// そのメモリー上にクラスのオブジェクトを構築 +std::vector v ; +\end{lstlisting} + +生のメモリー上にクラスのような複雑な型を構築するには、\texttt{newプレイスメント}\index{new@\texttt{new}!ぷれいすめんと@プレイスメント}を使う。 + +\begin{lstlisting}[style=grammar] +new ( 生のポインター ) 型 new初期化子 +\end{lstlisting} + +\texttt{new初期化子}\index{new@\texttt{new}!しよきかし@初期化子}というのは\texttt{()}か\,\texttt{\{\}}\,で囲んだコンストラクターへの引数だ。引数がない場合は省略もできる。 + +例えば\texttt{std::vector}\,型を構築するには以下のようにする。 + +\begin{lstlisting}[language={C++}] +// 生のメモリーを動的確保 +void * ptr = ::operator new ( sizeof( std::vector ) ) ; +// 生のメモリー上に型を構築 +std::vector * vector_ptr = new (ptr) std::vector{} ; +\end{lstlisting} + +こうすればクラスが適切にメモリー上に構築され、コンストラクターも呼ばれる。コンストラクターが呼ばれることを確かめてみよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +struct Logger +{ + std::string name ; + Logger( std::string name ) + : name( name ) + { std::cout << name << " is constructed.\n"s ; } + ~Logger() + { std::cout << name << " is destructed.\n"s ; } +} ; + +int main() +{ + void * ptr = ::operator new ( sizeof( Logger ) ) ; + Logger * logger_ptr = new (ptr) Logger{"Alice"s} ; +} +\end{lstlisting} + +このプログラムを実行すると、\texttt{"Alice is constructed."}\,と出力される。 + +クラスのオブジェクトを適切に破棄するためには、デストラクターを呼ばなければならない。通常の変数ならば、変数が寿命を迎えたときに自動的にデストラクターが呼ばれてくれる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + Logger Alice("Alice"s) ; + { + Logger Bob("Bob"s) ; + // Bobの寿命はここまで + } + // Aliceの寿命はここまで +} +\end{lstlisting} + +このプログラムを実行すると、以下のように出力される。 + +\begin{lstlisting}[style=terminal] +Alice is constructed. +Bob is constructed. +Bob is destructed. +Alice is destructed. +\end{lstlisting} + +動的に確保されるメモリー上に構築されたオブジェクトは自動的に破棄されてくれない。クラスのオブジェクトの場合デストラクターを呼び出さなければならないが、動的メモリー確保したメモリー上に構築したクラスのオブジェクトの場合は、明示的に呼び出さなければならない。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// 動的メモリー確保 +void * raw_ptr = ::operator new( sizeof(Logger) ) ; +// 構築 +Logger * logger_ptr = new(raw_ptr) Logger{ "Alice"s } ; +// デストラクター呼び出し +logger_ptr->~Logger() ; +// 破棄 +::operator delete( raw_ptr ) ; +\end{lstlisting} + +このようにすれば、コンストラクター、デストラクターが適切に呼ばれる。また確保したメモリーも解放される。 + +\hypersection{ch2907}{new/delete} + +クラスのオブジェクトを動的確保するのに、生のメモリーの確保/解放と、クラスのオブジェクトの構築/破棄をすべて自前で行うのは面倒だ。幸い、確保と構築、破棄と解放を同時にやってくれる機能がある。\texttt{new式}と\texttt{delete式}だ。 +\index{new@\texttt{new}!しき@式}\index{delete@\texttt{delete}!しき@式} + +\begin{lstlisting}[style=grammar] +new 型 new初期化子 +delete ポインター +\end{lstlisting} + +\texttt{new式}は生のメモリーを確保し、型のオブジェクトを構築し、そのオブジェクトへのポインターを返す。 + +\begin{lstlisting}[language={C++}] +int * int_ptr = new int{123} ; +std::vector * vector_ptr = new std::vector{} ; +\end{lstlisting} + +\texttt{delete式}は\texttt{new式}で返されたポインターの指し示すオブジェクトを破棄し、生のメモリーを解放する。 + +\begin{lstlisting}[language={C++}] +delete int_ptr ; +delete vector_ptr ; +\end{lstlisting} + +\texttt{new式}がメモリーの確保に失敗すると、\texttt{std::bad\_alloc}\index{bad\_alloc@\texttt{bad\_alloc}}例外を投げる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + try { + new int{0} ; + // 確保成功 + } catch( std::bad_alloc e ) + { +(@\ifTombow\pagebreak\fi@) + // 確保失敗 + } +} +\end{lstlisting} + +\hypersection{ch2908}{配列版new/delete} +\index{new@\texttt{new}!しき@式}\index{delete@\texttt{delete}!しき@式} + +\texttt{new式}は配列型を動的確保することもできる。 +\index{はいれつ@配列!どうてきかくほ@動的確保} + +\begin{lstlisting}[language={C++}] +int * int_array_ptr = new int[5]{1,2,3,4,5} ; +\end{lstlisting} + +配列型を\texttt{new式}で動的確保した場合、\texttt{delete式}は通常の\texttt{delete}ではなく、\texttt{delete[]}\index{delete[]@\texttt{delete[]}}を使わなければならない。 + +\begin{lstlisting}[language={C++}] +delete [] int_array_ptr ; +\end{lstlisting} + +\hypersection{ch2909}{スマートポインター} +\index{すまとぽいんた@スマートポインター}\index{ぽいんた@ポインター!すまと@スマート〜} + +クラスのオブジェクトの動的確保は、解放を明示的にしなければならないので間違いをしやすい。この問題はクラスを使って解決できる。 + +クラスのコンストラクターで動的確保し、デストラクターで解放すればよいのだ。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct smart_ptr +{ + T * ptr ; + // コンストラクターで構築 + smart_ptr() + : ptr( new T{} ) + { } + // デストラクターで破棄 + ~smart_ptr() + { delete ptr ; } + + T & operator *() const noexcept + { return *ptr ; } +} ; + +int main() +{ + smart_ptr ptr ; + *ptr = 123 ; +(@\ifTombow\pagebreak\fi@) + // 自動的に破棄される +} +\end{lstlisting} + +このクラスはさまざまな点で実用的ではない。例えばこのクラスはコピーできてしまう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + smart_ptr p1 ; + // コピーされる + auto p2 = p1 ; + // p2の寿命 + // エラー、 p1の寿命 +} +\end{lstlisting} + +このコードの何がまずいかというと、\texttt{smart\_ptr::ptr}がコピーされてしまうということだ。\texttt{p2}が破棄されると、\texttt{delete ptr}が実行される。そのあとに\texttt{p1}が破棄されるのだが、もう一度\texttt{delete ptr}が実行されてしまうのだ。一度\texttt{delete}を呼び出したポインターはもう無効になっているので、それ以上\texttt{delete}を呼び出すことはできない。よってエラーになる。 + +この問題を解決するには、まだ学んでいないC++の機能がたくさん必要になる。この問題は必要な機能をすべて学び終えたあとの章で、もう一度挑戦することにしよう。 diff --git a/TeX/033-vector-implementation.tex b/TeX/033-vector-implementation.tex new file mode 100644 index 0000000..5b81a89 --- /dev/null +++ b/TeX/033-vector-implementation.tex @@ -0,0 +1,1082 @@ +\hyperchapter{ch30}{vectorの実装:基礎}{vectorの実装:基礎} +\index{vector@\texttt{vector}} + +クラス、ポインター、メモリー確保を学んだので、とうとうコンテナーの中でも一番有名な\texttt{std::vector}を実装する用意ができた。しかしその前に、アロケーター\index{あろけた@アロケーター}について学ぶ必要がある。 + +\texttt{std::vector}は\texttt{std::vector}\,のように要素の型\texttt{T}を指定して使うので、以下のようになっていると思う読者もいるだろう。 + +\begin{lstlisting}[language={C++}] +namespace std { + template < typename T > + struct vector ; +} +\end{lstlisting} + +実際には以下のようになっている。 + +\begin{lstlisting}[language={C++}] +namespace std { + template < typename T, typename allocator = allocator > + struct vector ; +} +\end{lstlisting} + +\texttt{std::allocator}\,\index{allocator@\texttt{allocator}}というのは標準ライブラリのアロケーターだ。アロケーターは生のメモリーの確保と解放をするライブラリだ。デフォルトで\texttt{std::allocator}\,が渡されるので、普段ユーザーはアロケーターを意識することはない。 + +\texttt{std::vector}は\texttt{malloc}や\texttt{operator new}を直接使わずアロケーターを使ってメモリー確保を行う。 + +アロケーターはテンプレートパラメーターで指定できる。何らかの理由で独自のメモリー確保を行いたい場合、独自のアロケーターを実装してコンテナーに渡すことができる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// 独自のアロケーター +template < typename T > +struct custom_allocator +{ + // ... +} ; + +template < typename T > +using custom_vector = std::vector< T, custom_allocator > ; + +int main() +{ + custom_vector v ; + // 独自のアロケーターを使ったメモリー確保 + v.push_back(0) ; +} +\end{lstlisting} + +\hypersection{ch3001}{std::allocator\texttt{<}T\texttt{>}\,の概要} + +\texttt{std::allocator}\,は\texttt{T}型を構築できる生のメモリーを確保するための以下のようになっている。 + +\begin{lstlisting}[language={C++}] +namespace std { +template class allocator { + // ネストされた型名の宣言 + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using propagate_on_container_move_assignment = true_type; + using is_always_equal = true_type; + + // コンストラクター + // constexprはまだ学んでいない + constexpr allocator() noexcept; + constexpr allocator(const allocator&) noexcept; + template constexpr allocator(const allocator&) noexcept; + ~allocator(); + // コピー代入演算子 + allocator& operator=(const allocator&) = default; + + // ここが重要 + [[nodiscard]] T* allocate(size_t n); + void deallocate(T* p, size_t n); +}; +} +\end{lstlisting} + +\texttt{constexpr}というキーワードがあるが、ここでは気にする必要はない。あとで学ぶ。 + +重要なのはメモリー確保をする\texttt{allocate}と、メモリー解放をする\texttt{deallocate}だ。 + +\hypersection{ch3002}{std::allocator\texttt{<}T\texttt{>}\,の使い方} + +標準ライブラリのアロケーター、\texttt{std::allocator}\,\index{allocator@\texttt{allocator}}は、\texttt{T}型を構築できる生のメモリーの確保と解放をするライブラリだ。重要なメンバーは以下のとおり。 + +\begin{lstlisting}[language={C++}] +// メモリー確保 +[[nodiscard]] T* allocate(size_t n); +// メモリー解放 +void deallocate(T* p, size_t n); +\end{lstlisting} + +\texttt{allocate(n)}\index{allocate@\texttt{allocate}}は\texttt{T}型の\texttt{n}個の配列を構築できるだけの生のメモリーを確保\index{めもり@メモリー!かくほ@確保}してその先頭へのポインターを返す。 + +\texttt{deallocate(p, n)}\index{deallocate@\texttt{deallocate}}は\texttt{allocate(n)}で確保されたメモリーを解放\index{めもり@メモリー!かいほう@解放}する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::allocator a ; + // 生のメモリー確保 + // std::string [1]分のメモリーサイズ + std::string * p = a.allocate(1) ; + // メモリー解放 + a.deallocate( p, 1 ) ; +} +\end{lstlisting} + +\texttt{allocate}には\texttt{[[nodiscard]]}\index{[[nodiscard]]@\texttt{[[nodiscard]]}}という属性が付いている。これにより戻り値を無視すると警告が出る。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::allocator a ; + // 警告、 戻り値が無視されている + a.allocate(1) ; + + // OK + int * p = a.allocate(1) ; +} +\end{lstlisting} + +確保されるのが生のメモリーだということに注意したい。実際に\texttt{T}型の値として使うには、\texttt{new}による構築が必要だ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::allocator a ; + // 生のメモリー確保 + // std::string [1]分のメモリーサイズ + std::string * p = a.allocate(1) ; + // 構築 + std::string * s = new(p) std::string("hello") ; + // 明示的なデストラクター呼び出し + s->~basic_string() ; + // メモリー解放 + a.deallocate( p, 1 ) ; +} +\end{lstlisting} + +このように書くのはとても面倒だ。特に\texttt{std::string}の明示的なデストラクター呼び出し\texttt{s->{\textasciitilde}basic\_string}が面倒だ。なぜ\texttt{s->{\textasciitilde}string}ではだめなのか。 + +実は\texttt{std::string}\index{string@\texttt{string}}は以下のようなクラステンプレートになっている。 + +\begin{lstlisting}[language={C++}] +namespace std { +template < + typename charT, + typename traits = char_traits, + typename Allocator = allocator> +class basic_string +{ + // デストラクター + ~basic_string() ; +} ; + +} +\end{lstlisting} + +本当のクラス名は\texttt{basic\_string}\index{basic\_string@\texttt{basic\_string}}なのだ。 + +普段は使っている\texttt{std::string}というのは、以下のようなエイリアスだ。 + +\begin{lstlisting}[language={C++}] +namespace std { +using string = basic_string ; +} +\end{lstlisting} + +明示的なデストラクター呼び出しにエイリアスは使えないので、本当のクラス名である\texttt{basic\_string}を直接指定しなければならない。 + +\ifTombow\pagebreak\fi +この問題はテンプレートで解決できる。 + +\begin{lstlisting}[language={C++}] +// 明示的なデストラクター呼び出しをする関数テンプレート +template < typename T > +void destroy_at( T * location ) +{ + location->~T() ; +} +\end{lstlisting} + +このようにテンプレートで書くことによって、クラス名を意識せずに破棄ができる。 + +\begin{lstlisting}[language={C++}] +// 破棄 +destroy_at( s ) ; +\end{lstlisting} + +このようなコードを書くのは面倒なので、標準ライブラリには\texttt{std::destroy\_at}\index{destroy\_at@\texttt{destroy\_at}}がある。また、これらをひっくるめたアロケーターを使うためのライブラリである\texttt{allocator\_traits}がある。 + +\hypersection{ch3003}{std::allocator\texttt{\_}traits\texttt{<}Alloc\texttt{>}} +\index{allocator\_traits@\texttt{allocator\_traits}} + +\texttt{std::allocator\_traits}\,はアロケーター\texttt{Alloc}を簡単に使うためのライブラリだ。 + +\texttt{allocator\_traits}\,はアロケーターの型\texttt{Alloc}を指定して使う。 + +\begin{lstlisting}[language={C++}] +std::allocator a ; +int * p = a.allocate(1) ; +\end{lstlisting} +と書く代わりに、 +\begin{lstlisting}[language={C++}] +std::allocator a ; +int * p = std::allocator_traits< std::allocator >::allocate( a, 1 ) ; +\end{lstlisting} +と書く。 + +これはとても使いづらいので、\texttt{allocator\_traits}のエイリアスを書くとよい。 + +\begin{lstlisting}[language={C++}] +std::allocator a ; +// エイリアス +using traits = std::allocator_traits< std::allocator > ; +int * p = traits::allocate( a, 1 ) ; +\end{lstlisting} + +これもまだ書きにくいので、\texttt{decltype}\index{decltype@\texttt{decltype}}を使う。\texttt{decltype(expr)}は式\texttt{expr}の型として使える機能だ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// int型 +decltype(0) a ; +// double型 +decltype(0.0) b ; +// int型 +decltype( 1 + 1 ) c ; +// std::string型 +decltype( "hello"s ) c ; +\end{lstlisting} + +\texttt{decltype}を使うと以下のように書ける。 + +\begin{lstlisting}[language={C++}] +std::allocator a ; +// エイリアス +using traits = std::allocator_traits< decltype(a) > ; +int * p = traits::allocate( a, 1 ) ; +\end{lstlisting} + +\texttt{allocator\_traits}はアロケーターを使った生のメモリーの確保、解放と、そのメモリー上にオブジェクトを構築、破棄する機能を提供している。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::allocator a ; + // allocator_traits型 + using traits = std::allocator_traits ; + + // 生のメモリー確保 + std::string * p = traits::allocate( a, 1 ) ; + // 構築 + std::string * s = traits::construct( a, p, "hello") ; + // 破棄 + traits::destroy( a, s ) ; + // メモリー解放 + traits::deallocate( a, p, 1 ) ; +} +\end{lstlisting} + +\texttt{T}型の\texttt{N}個の配列を構築\index{はいれつ@配列!こうちく@構築}するには、まず\texttt{N}個の生のメモリーを確保し、 +\begin{lstlisting}[language={C++}] +std::allocator a ; +using traits = std::allocator_traits ; +std::string * p = traits::allocate( a, N ) ; +\end{lstlisting} +\texttt{N}回の構築を行う。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +for ( auto i = p, last = p + N ; i != last ; ++i ) +{ + traits::construct( a, i, "hello" ) ; +} +\end{lstlisting} + +破棄\index{はいれつ@配列!はき@破棄}も\texttt{N}回行う。 + +\begin{lstlisting}[language={C++}] +for ( auto i = p + N, first = p ; i != first ; --i ) +{ + traits::destroy( a, i ) ; +} +\end{lstlisting} + +生のメモリーを破棄する。 + +\begin{lstlisting}[language={C++}] +traits::deallocate( a, p, N ) ; +\end{lstlisting} + +\hypersection{ch3004}{簡易vectorの概要} +\index{かんいvector@簡易\texttt{vector}}\index{vector\texttt{vector}!かんい@簡易〜} + +準備はできた。簡易的な\texttt{vector}を実装していこう。以下が本書で実装する簡易\texttt{vector}だ。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[language={C++}] +template < typename T, typename Allocator = std::allocator > +class vector +{ +private : + // データメンバー +public : + // value_typeなどネストされた型名 + using value_type = T ; + // コンストラクター + vector( std::size_t n = 0, Allocator a = Allocator() ) ; + // デストラクター + ~vector() ; + // コピー + vector( const vector & x ) ; + vector & operator =( const vector & x ) ; + + // 要素アクセス + void push_back( const T & x ) ; + T & operator []( std::size_t i ) noexcept ; + + // イテレーターアクセス + iterator begin() noexcept ; + iterator end() noexcept ; +} ; +\end{lstlisting} + +これだけの簡易\texttt{vector}でもかなり便利に使える。 + +例えば要素数を定めて配列のようにアクセスできる。 + +\begin{lstlisting}[language={C++}] +vector v(100) ; +for ( auto i = 0 ; i != 100 ; ++i ) + v[i] = i ; +\end{lstlisting} + +イテレーターも使える。 + +\begin{lstlisting}[language={C++}] +std::for_each( std::begin(v), std::end(v), + []( auto x ) { std::cout << x ; } ) ; +\end{lstlisting} + +要素を際限なく追加できる。 + +\begin{lstlisting}[language={C++}] +std::copy( + std::istream_iterator(std::cin), std::istream_iterator(), + std::back_inserter(v) ) ; +\end{lstlisting} + +\hypersection{ch3005}{classとアクセス指定} + +簡易\texttt{vector}の概要では、まだ学んでいない機能が使われていた。\texttt{class}\index{class@\texttt{class}}と\texttt{public}\index{public@\texttt{public}}と\texttt{private}\index{private@\texttt{private}}だ。 + +C++のクラス\index{くらす@クラス}にはアクセス指定\index{あくせすしてい@アクセス指定}\index{くらす@クラス!あくせすしてい@アクセス指定}がある。\texttt{public:}と\texttt{private:}だ。アクセス指定が書かれたあと、別のアクセス指定が現れるまでの間のメンバーは、アクセス指定の影響を受ける。 + +\begin{lstlisting}[language={C++}] +struct C +{ +public : + // publicなメンバー + int public_member1 ; + int public_member2 ; +private : + // privateなメンバー + int private_member1 ; + int private_member2 ; +public : + // 再びpublicなメンバー + int public_member3 ; +} ; +\end{lstlisting} + +\ifTombow\pagebreak\fi +\texttt{public}メンバーはクラスの外から使うことができる。 + +\begin{lstlisting}[language={C++}] +struct C +{ +public : + int data_member ; + void member_function() { } +} ; + +int main() +{ + C c; + // クラスの外から使う + c.data_member = 0 ; + c.member_function() ; +} +\end{lstlisting} + +\texttt{private}メンバーはクラスの外から使うことができない。 + +\begin{lstlisting}[language={C++}] +struct C +{ +private : + int data_member ; + void member_function() ; +} ; + +int main() +{ + C c ; + // エラー + c.data_member = 0 ; + // エラー + c.member_function() ; +} +\end{lstlisting} + +コンストラクターもアクセス指定の対象になる。 + +\begin{lstlisting}[language={C++}] +struct C +{ +public : + C(int) { } +private : + C(double) { } +} ; + +(@\ifTombow\pagebreak\fi@) +int main() +{ + // OK + C pub(0) ; + // エラー + C pri(0.0) ; +} +\end{lstlisting} + +この例では、\texttt{C::C(int)}は\texttt{public}メンバーなのでクラスの外から使えるが、\texttt{C::C(double)}は\texttt{private}メンバーなのでクラスの外からは使えない。 + +\texttt{private}メンバーはクラスの中から使うことができる。クラスの中であればどのアクセス指定のメンバーからでも使える。 + +\begin{lstlisting}[language={C++}] +struct C +{ +public : + void f() + { + // ここはクラスの中 + data_member = 0 ; + member_function() ; + } + +private : + int data_member ; + void member_function() { } +} ; +\end{lstlisting} + +\texttt{private}メンバーの目的はクラスの外から使ってほしくないメンバーを守ることだ。例えば以下のようにコンストラクターで\texttt{new}してデストラクターで\texttt{delete}するようなクラスがあるとする。 + +\begin{lstlisting}[language={C++}] +class dynamic_int +{ +private : + int * ptr ; +public : + dynamic_int( int value = 0 ) + : ptr( new int(value) ) + { } + ~dynamic_int() + { + delete ptr ; + } +} ; +\end{lstlisting} + +もし\texttt{dynamic\_int::ptr}が\texttt{public}メンバーだった場合、以下のようなコードのコンパイルが通ってしまう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + dynamic_int i ; + delete i.ptr ; + int obj{} ; + i.ptr = &obj ; +} +\end{lstlisting} + +このプログラムが\texttt{dynamic\_int}のデストラクターを呼ぶと、\texttt{main}関数のローカル変数のポインターに対して\texttt{delete}を呼び出してしまう。これは未定義の挙動となる。 + +外部から使われては困るメンバーを\texttt{private}メンバーにすることでこの問題はコンパイル時にエラーにでき、未然に回避できる。 + +クラスを定義\index{くらす@クラス!ていぎ@定義}するにはキーワードとして\texttt{struct}\index{struct@\texttt{struct}}もしくは\texttt{class}\index{class@\texttt{class}}を使う。 + +\begin{lstlisting}[language={C++}] +struct foo { } ; +class bar { } ; +\end{lstlisting} + +違いはデフォルトのアクセス指定だ。 + +\texttt{struct}はデフォルトで\texttt{public}となる。 + +\begin{lstlisting}[language={C++}] +struct foo +{ + // publicメンバー + int member ; +} ; +\end{lstlisting} + +\texttt{class}はデフォルトで\texttt{private}となる。 + +\begin{lstlisting}[language={C++}] +class bar +{ + // privateメンバー + int member ; +} ; +\end{lstlisting} + +\texttt{struct}と\texttt{class}の違いはデフォルトのアクセス指定だけだ。アクセス指定を明示的に書く場合、違いはなくなる。 + +\clearpage +\hypersection{ch3006}{ネストされた型名} +\index{かためい@型名!ねすとされた@ネストされた〜} + +\texttt{std::vector}にはさまざまなネストされた型名がある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + using vec = std::vector ; + vec v = {1,2,3} ; + + vec::value_type val = v[0] ; + vec::iterator i = v.begin() ; +} +\end{lstlisting} + +自作の簡易\texttt{vector}で\texttt{std::vector}と同じようにネストされた型名を書いていこう。 + +要素型に関係するネストされた型名。 + +\begin{lstlisting}[language={C++}] +template < typename T, typename Allocator = std::allocator > +class vector +{ +public : + using value_type = T ; + using pointer = T *; + using const_pointer = const pointer; + using reference = value_type & ; + using const_reference = const value_type & ; +} ; +\end{lstlisting} + +本物の\texttt{std::vector}とは少し異なるが、ほぼ同じだ。要素型が\texttt{value\_type}で、あとは要素型のポインター、\texttt{const}ポインター、リファレンス、\texttt{const}リファレンスがそれぞれエイリアス宣言される。 + +アロケーター型も\texttt{allocator\_type}\index{allocator\_type@\texttt{allocator\_type}}としてエイリアス宣言される。 + +\begin{lstlisting}[language={C++}] +template < typename T, typename Allocator = std::allocator > +class vector +{ +public : + using allocator_type = Allocator ; +} ; +\end{lstlisting} + +\texttt{size\_type}\index{size\_type@\texttt{size\_type}}は要素数を表現する型だ。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[language={C++}] +void f( std::vector & v ) +{ + std::vector::size_type s = v.size() ; +} +\end{lstlisting} + +通常\texttt{std::size\_t}が使われる。 + +\begin{lstlisting}[language={C++}] +size_type = std::size_t ; +\end{lstlisting} + +\texttt{difference\_type}\index{difference\_type@\texttt{difference\_type}}はイテレーターの\texttt{difference\_type}と同じだ。これはイテレーター間の距離を表現する型だ。 + +\begin{lstlisting}[language={C++}] +void f( std::vector & v ) +{ + auto i = v.begin() ; + auto j = i + 3 ; + + // iとjの距離 + std::vector::difference_type d = j - i ; +} +\end{lstlisting} + +通常\texttt{std::ptrdiff\_t}\index{ptrdiff\_t@\texttt{ptrdiff\_t}}が使われる。 + +\begin{lstlisting}[language={C++}] +difference_type = std::ptrdiff_t ; +\end{lstlisting} + +イテレーターのエイリアス。 + +\begin{lstlisting}[language={C++}] +using iterator = pointer ; +using const_iterator = const_pointer ; +using reverse_iterator = std::reverse_iterator ; +using const_reverse_iterator = std::reverse_iterator ; +\end{lstlisting} + +今回実装する簡易\texttt{vector}では、ポインター型をイテレーター型として使う。\texttt{std::vector}の実装がこのようになっている保証はない。 + +\texttt{reverse\_iterator}と\texttt{const\_reverse\_iterator}はリバースイテレーターだ。 + +\hypersection{ch3007}{簡易vectorのデータメンバー} +\index{かんいvector@簡易\texttt{vector}!でためんば@データメンバー} + +簡易\texttt{vector}にはどのようなデータメンバーがあればいいのだろうか。以下の4つの情報を保持する必要がある。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 動的確保したストレージへのポインター +\item + 現在有効な要素数 +\item + 動的確保したストレージのサイズ +\item + アロケーター +\end{enumerate} + +これを素直に考えると、ポインター1つ、整数2つ、アロケーター1つの4つのデータメンバーになる。 + +\begin{lstlisting}[language={C++}] +template < typename T, typename Allocator = std::allocator > +class vector +{ +private : + // 動的確保したストレージへのポインター + pointer first = nullptr ; + // 現在有効な要素数 + size_type valid_size = nullptr ; + // 動的確保したストレージのサイズ + size_type allocated_size = nullptr ; + // アロケーターの値 + allocator_type alloc ; +} ; +\end{lstlisting} + +確かに\texttt{std::vector}はこのようなデータメンバーでも実装できる。しかし多くの実装では以下のようなポインター3つとアロケーター1つになっている。 + +\begin{lstlisting}[language={C++}] +template < typename T, typename Allocator = std::allocator > +class vector +{ +private : + // 先頭の要素へのポインター + pointer first ; + // 最後の要素の1つ前方のポインター + pointer last ; + // 確保したストレージの終端 + pointer reserved_last ; + // アロケーターの値 + allocator_type alloc ; +} ; +\end{lstlisting} + +このように実装すると、現在有効な要素数は\texttt{last - first}で得られる。確保したストレージのサイズは\texttt{reserved\_last - first}だ。ポインターで持つことによってポインターが必要な場面でポインターと整数の演算を必要としない。 + +効率的な実装はC++が実行される環境によっても異なるので、すべての環境に最適な実装はない。 + +\clearpage +\hypersection{ch3008}{簡単なメンバー関数の実装} +\index{かんいvector@簡易\texttt{vector}!めんばかんすう@メンバー関数} + +簡易\texttt{vector}の簡単なメンバー関数を実装していく。ここでのサンプルコードはすべて簡易\texttt{vector}のクラス定義の中に書いたかのように扱う。例えば +\begin{lstlisting}[language={C++}] +void f() { } +\end{lstlisting} +とある場合、これは、 +\begin{lstlisting}[language={C++}] +template < typename T, typename Allocator = std::allocator > +class vector +{ + // その他のメンバーすべて +public : + void f() {} +} ; +\end{lstlisting} +のように書いたものとして考えよう。 + +\hypersubsection{ch300801}{イテレーター} +\index{かんいvector@簡易\texttt{vector}!いてれた@イテレーター} + +簡易\texttt{vector}は要素の集合を配列のように連続したストレージ上に構築された要素として保持する。したがってイテレーターは単にポインターを返すだけでよい。 + +まず通常のイテレーター +\begin{lstlisting}[language={C++}] +iterator begin() noexcept +{ return first ; } +iterator end() noexcept +{ return last ; } +\end{lstlisting} +これは簡単だ。\texttt{iterator}型は実際には\texttt{T *}\,型へのエイリアスだ。このメンバー関数は例外を投げないので\texttt{noexcept}を指定する。 + +\texttt{vector}のオブジェクトが\texttt{const}の場合、\texttt{begin/end}は\texttt{const\_iterator}が返る。 +\index{かんいvector@簡易\texttt{vector}!begin@\texttt{begin}}\index{かんいvector@簡易\texttt{vector}!end@\texttt{end}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v(1) ; + // std::vector::iterator + auto i = v.begin() ; + // OK、代入可能 + *i = 0 ; + // constなvectorへのリファレンス + auto const & cv = v ; + // std::vector::const_iterator + auto ci = cv.begin() ; + // エラー + // const_iteratorを参照した先には代入できない + *ci = 0 ; +} +\end{lstlisting} + +これを実現するには、メンバー関数を\texttt{const}修飾する。 + +\begin{lstlisting}[language={C++}] +struct Foo +{ + // 非const版 + void f() {} + // const版 + void f() const { } +} ; + +int main() +{ + // aは非constなオブジェクト + Foo a ; + // 非const版が呼ばれる + a.f() ; + // constなリファレンス + const Foo & cref = a ; + // const版が呼ばれる + cref.f() ; +} +\end{lstlisting} + +すでに学んだように\texttt{const}修飾は\texttt{this}ポインターを修飾する。オブジェクトの\texttt{const}性によって、適切な方のメンバー関数が呼ばれてくれる。 + +簡易\texttt{vector}での実装は単に\texttt{const}修飾するだけだ。 + +\begin{lstlisting}[language={C++}] +iterator begin() const noexcept +{ return first ; } +iterator end() const noexcept +{ return last ; } +\end{lstlisting} + +\texttt{const}ではない\texttt{vector}のオブジェクトから\texttt{const\_iterator}がほしいときに、わざわざ\texttt{const}なリファレンスに変換するのは面倒なので、\texttt{const\_reference}を返す\texttt{cbegin}/\texttt{cend}もある。 +\index{かんいvector@簡易\texttt{vector}!cbegin@\texttt{cbegin}}\index{かんいvector@簡易\texttt{vector}!cend@\texttt{cend}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v(1) ; + // std::vector::const_iterator + auto i = v.cbegin() ; +} +\end{lstlisting} + +この実装はメンバー関数名以外同じだ。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[language={C++}] +const_iterator cbegin() const noexcept +{ return first ; } +const_iterator cend() const noexcept +{ return last ; } +\end{lstlisting} + +\texttt{std::vector}にはリバースイテレーターを返すメンバー関数\texttt{rbegin}/\texttt{rend}と\texttt{crbegin}/\texttt{crend}がある。 +\index{かんいvector@簡易\texttt{vector}!rbegin@\texttt{rbegin}}\index{かんいvector@簡易\texttt{vector}!rend@\texttt{rend}}\index{かんいvector@簡易\texttt{vector}!crbegin@\texttt{crbegin}}\index{かんいvector@簡易\texttt{vector}!crend@\texttt{crend}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + + // イテレーター + auto i = v.begin() ; + *i ; // 1 + + // リバースイテレーター + auto r = v.rbegin() ; + *r ; // 5 +} +\end{lstlisting} + +\texttt{begin}に対する\texttt{rbegin}/\texttt{rend}の実装は以下のようになる。\texttt{crbegin}/\texttt{crend}は自分で実装してみよう。 + +\begin{lstlisting}[language={C++}] +reverse_iterator rbegin() noexcept +{ return reverse_iterator{ last } ; } +reverse_iterator rend() noexcept +{ return reverse_iterator{ first } ; } +\end{lstlisting} + +\texttt{return}文で\texttt{T\{e\}}という形の明示的な型変換を使っている。これには理由がある。 + +C++では引数が1つしかないコンストラクターを\texttt{変換コンストラクター}として特別に扱う。 +\index{へんかんこんすとらくた@変換コンストラクター}\index{こんすとらくた@コンストラクター!へんかん@変換〜} + +例えば以下は数値のように振る舞う\texttt{Number}クラスの例だ。 + +\begin{lstlisting}[language={C++}] +class Number +{ + Number( int i ) ; + Number( double d ) ; + Number( std::string s ) ; +} ; +\end{lstlisting} + +この\texttt{Number}は初期値をコンストラクターで取る。そのとき、\texttt{int}型、\texttt{double}型、はては文字列で数値を表現した\texttt{std::string}型まで取る。この3つのコンストラクターは引数が1つしかないため変換コンストラクターだ。 + +クラスは変換コンストラクターの引数の型から暗黙に型変換できる。 + +例えば\texttt{Number}クラスを引数に取る関数があると、 +\begin{lstlisting}[language={C++}] +void print_number( Number n ) ; +\end{lstlisting} +変換コンストラクターの型の値を渡せる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // int型から変換 + print_number( 123 ) ; + // double型から変換 + print_number( 3.14 ) ; + // std::string型から変換 + print_number( "3.14"s ) ; +} +\end{lstlisting} + +\texttt{int}や\texttt{double}や\texttt{std::string}は\texttt{Number}ではないが、変換コンストラクターによって暗黙に型変換される。 + +戻り値として返すときにも変換できる。 + +\begin{lstlisting}[language={C++}] +// Number型のゼロを返す +Number zero() +{ + // int型から変換 + return 0 ; +} +\end{lstlisting} + +しかし、場合によってはこのような暗黙の型変換\index{あんもくのかたへんかん@暗黙の型変換}を行いたくないこともある。そういう場合、コンストラクターに\texttt{explicit}キーワード\index{explicit@\texttt{explicit}}を付けると、暗黙の型変換を禁止させることができる。 + +\begin{lstlisting}[language={C++}] +class Number +{ + explicit Number( int i ) ; + explicit Number( double d ) ; + explicit Number( std::string s ) ; +} ; +\end{lstlisting} + +実は\texttt{std::reverse\_iterator}\,のコンストラクターにも\texttt{explicit}キーワードが付いている。 + +\begin{lstlisting}[language={C++}] +namespace std { +template< typename Iterator > +class reverse_iterator +{ + constexpr explicit reverse_iterator(Iterator x); + // ... +} ; +} +\end{lstlisting} + +\texttt{explicit}キーワード付きの変換コンストラクターを持つクラスは、暗黙の型変換ができないので、明示的に型変換しなければならない。 + +\hypersubsection{ch300802}{容量確認} +\index{vector@\texttt{vector}!ようりようかくにん@容量確認} + +\texttt{std::vector}には容量を確認するメンバー関数がある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + // true、要素数0 + bool a = v.empty() ; + v.push_back(0) ; + // false、要素数非ゼロ + bool b = v.empty() ; + // 1、 現在の要素数 + auto s = v.size() ; + // 実装依存、 追加の動的メモリー確保をせずに格納できる要素の最大数 + auto c = v.capacity() ; +} +\end{lstlisting} + +さっそく実装していこう。 + +\texttt{size}\index{size@\texttt{size}}\index{vector@\texttt{vector}!size@\texttt{size}}は要素数を返す。イテレーターの距離を求めればよい。 + +\begin{lstlisting}[language={C++}] +size_type size() const noexcept +{ + return end() - begin() ; +} +\end{lstlisting} + +イテレーターライブラリを使ってもよい。本物の\texttt{std::vector}では以下のように実装されている。 + +\begin{lstlisting}[language={C++}] +size_type size() const noexcept +{ + return std::distance( begin(), end() ) ; +} +\end{lstlisting} + +\texttt{empty}\index{empty@\texttt{empty}}\index{vector@\texttt{vector}!empty@\texttt{empty}}は空であれば\texttt{true}、そうでなければ\texttt{false}を返す。「空」というのは要素数がゼロという意味だ。 + +\begin{lstlisting}[language={C++}] +bool empty() const noexcept +{ + return size() == 0 ; +} +\end{lstlisting} + +しかし\texttt{size() == 0}というのは、\texttt{begin() == end()}ということだ。なぜならば要素数が0であれば、イテレーターのペアはどちらも終端のイテレーターを差しているからだ。本物の\texttt{std::vector}では以下のように実装されている。 + +\begin{lstlisting}[language={C++}] +bool empty() const noexcept +{ + return begin() == end() ; +} +\end{lstlisting} + +\texttt{capacity}\index{capacity@\texttt{capacity}}\index{vector@\texttt{vector}!capacity@\texttt{capacity}}は、追加の動的メモリー確保をせずに追加できる要素の最大数を返す。これを計算するには、動的確保したストレージの末尾の1つ次のポインターであるデータメンバーである\texttt{reserved\_last}\index{reserved\_last@\texttt{reserved\_last}}\index{vector@\texttt{vector}!reserved\_last@\texttt{reserved\_last}}を使う。最初の要素へのポインターである\texttt{first}から\texttt{reserved\_last}までの距離が答えだ。ポインターの距離はイテレーターと同じく引き算する。 + +\begin{lstlisting}[language={C++}] +size_type capacity() const noexcept +{ + return reserved_last - first ; +} +\end{lstlisting} + +\hypersubsection{ch300803}{要素アクセス} +\index{かんいべくた@簡易ベクター!ようそあくせす@要素アクセス} + +\hypersubsubsection{ch30080301}{operator \texttt{[]}} +\index{[]@\texttt{[]}}\index{かんいべくた@簡易ベクター![]@\texttt{[]}} + +\texttt{std::vector}の\texttt{operator []}相当のものを簡易\texttt{vector}にも実装しよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + v[1] ; // 2 + v[3] ; // 4 +} +\end{lstlisting} + +\texttt{operator []}は非\texttt{const}版と\texttt{const}版の2種類がある。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +reference operator []( size_type i ) +{ return first[i] ; } +const_reference operator []( size_type i ) const +{ return first[i] ; } +\end{lstlisting} + +\vskip -1.0zw +\hypersubsubsection{ch30080302}{at} +\index{at@\texttt{at}}\index{かんいべくた@簡易ベクター!at@\texttt{at}} + +メンバー関数\texttt{at(i)}は\texttt{operator [](i)}と同じだが、範囲外のインデックスを指定した場合、\texttt{std::out\_of\_range}が例外として投げられる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + try { + // 有効なインデックスはv[0]からv[4]まで + std::vector v = {1,2,3,4,5} ; + v[0] = 0 ; // OK + v[3] = 0 ; // OK + v[5] = 0 ; // エラー + } catch( std::out_of_range e ) + { + std::cout << e.what() ; + } +} +\end{lstlisting} + +実装はインデックスを\texttt{size()}と比較して、範囲外であれば\texttt{std::out\_of\_range}を\texttt{throw}する。\texttt{operator []}と同じく、非\texttt{const}版と\texttt{const}版がある。 + +\begin{lstlisting}[language={C++}] +reference at( size_type i ) +{ + if ( i >= size() ) + throw std::out_of_range( "index is out of range." ) ; + + return first[i] ; +} +const_reference at( size_type i ) const +{ + if ( i >= size() ) + throw std::out_of_range( "index is out of range." ) ; + + return first[i] ; +} +\end{lstlisting} + +\ifTombow\pagebreak\else{\vskip -1.0zw}\fi +\hypersubsubsection{ch30080303}{front/back} +\index{front@\texttt{front}}\index{かんいべくた@簡易ベクター!front@\texttt{front}}\index{back@\texttt{back}}\index{かんいべくた@簡易ベクター!back@\texttt{back}} + +\texttt{front()}は先頭要素へのリファレンスを返す。 + +\texttt{back()}は末尾の要素へのリファレンスを返す + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + v.front() ; // 1 + v.back() ; // 5 +} +\end{lstlisting} + +これにも\texttt{const}版と非\texttt{const}版がある。\texttt{vector}の\texttt{last}が最後の要素の次のポインターを指していることに注意。 + +\begin{lstlisting}[language={C++}] +reference front() +{ return first ; } +const_reference front() const +{ return first ; } +reference back() +{ return last - 1 ; } +const_reference back() const +{ return last - 1 ; } +\end{lstlisting} + +\vskip -1.0zw +\hypersubsubsection{ch30080304}{data} +\index{data@\texttt{data}}\index{かんいべくた@簡易ベクター!data@\texttt{data}} + +\texttt{data()}は先頭の要素へのポインターを返す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3} ; + int * ptr = v.data() ; + *ptr ; // 1 +} +\end{lstlisting} + +実装は\texttt{first}を返すだけだ。 + +\begin{lstlisting}[language={C++}] +pointer data() noexcept +{ return first ; } +const_pointer data() const noexcept +{ return first ; } +\end{lstlisting} + diff --git a/TeX/034-vector-memory-allocation.tex b/TeX/034-vector-memory-allocation.tex new file mode 100644 index 0000000..1efb5b2 --- /dev/null +++ b/TeX/034-vector-memory-allocation.tex @@ -0,0 +1,946 @@ +\hyperchapter{ch31}{vectorの実装:メモリー確保}{vectorの実装:メモリー確保} +\index{vector@\texttt{vector}!めもりかくほ@メモリー確保} + +\hypersection{ch3101}{メモリー確保と解放の起こるタイミング} + +\texttt{std::vector}はどこでメモリーを確保と解放しているのだろうか。 + +デフォルト構築すると空になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + v.empty() ; // true +} +\end{lstlisting} + +コンストラクターに要素数を渡すことができる。 +\index{vector@\texttt{vector}!ようそすう@要素数} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v(100) ; + v.size() ; // 100 +} +\end{lstlisting} + +すると\texttt{std::vector}は指定した要素数の有効な要素を持つ。 + +コンストラクターに要素数と初期値を渡すことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v(100, 123) ; + v[0] ; // 123 + v[12] ; // 123 + v[68] ; // 123 +} +\end{lstlisting} + +すると、指定した要素数で、要素の値はすべて初期値になる。 + +\texttt{vector}のオブジェクトを構築したあとでも、メンバー関数\texttt{resize(size)}で要素数を\texttt{size}個にできる。 +\index{resize@\texttt{resize}}\index{vector@\texttt{vector}!resize@\texttt{resize}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + v.resize(10) ; + v.size() ; // 10 + // 減らす + v.resize(5) ; + v.size() ; // 5 +} +\end{lstlisting} + +\texttt{resize}で要素数が増える場合、増えた要素の初期値も指定できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + v.resize(3, 123) ; + // vは{123,123,123} +} +\end{lstlisting} + +\texttt{resize}で要素数が減る場合、末尾が削られる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + v.resize(3) ; + // vは{1,2,3} +} +\end{lstlisting} + +メンバー関数\texttt{push\_back(value)}を呼び出すと要素数が1増え、要素の末尾の要素が値\texttt{value}になる。 +\index{push\_back@\texttt{push\_back}}\index{vector@\texttt{vector}!push\_back@\texttt{push\_back}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + // vは{} + v.push_back(1) ; +(@\ifTombow\pagebreak\fi@) + // vは{1} + v.push_back(2) ; + // vは[1,2} + v.push_back(3) ; + // vは{1,2,3} +} +\end{lstlisting} + +\texttt{reserve(size)}は少なくとも\texttt{size}個の要素が追加の動的メモリー確保なしで追加できるようにメモリーを予約する。 +\index{reserve@\texttt{reserve}}\index{vector@\texttt{vector}!reserve@\texttt{reserve}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + // 少なくとも3個の要素を追加できるように動的メモリー確保 + v.reserve(3) ; + v.size() ; // 0 + v.capacity() ; // 3以上 + + // 動的メモリー確保は発生しない + v.push_back(1) ; + v.push_back(2) ; + v.push_back(3) ; + // 動的メモリー確保が発生する可能性がある。 + v.push_back(3) ; +} +\end{lstlisting} + +\texttt{clear()}は要素数を0にする。 +\index{clear@\texttt{clear}}\index{vector@\texttt{vector}!clear@\texttt{clear}} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3} ; + v.clear() ; + v.size() ; // 0 +} +\end{lstlisting} + +この章ではここまでの実装をする。 + +\clearpage +\hypersection{ch3102}{デフォルトコンストラクター} + +簡易\texttt{vector}のデフォルトコンストラクターは何もしない。 + +\begin{lstlisting}[language={C++}] +vector( ) { } +\end{lstlisting} + +何もしなくてもポインターはすべて\texttt{nullptr}で初期化され、アロケーターもデフォルト構築されるからだ。 + +これで簡易\texttt{vector}の変数を作れるようになった。ただしまだ何もできない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + vector v ; + // まだ何もできない。 +} +\end{lstlisting} + +\hypersection{ch3103}{アロケーターを取るコンストラクター} +\index{こんすとらくた@コンストラクター}\index{vector@\texttt{vector}!こんすとらくた@コンストラクター} + +\texttt{std::vector}のコンストラクターは最後の引数にアロケーターを取れる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::allocator alloc ; + // 空 + std::vector v1(alloc) ; + // 要素数5 + std::vector v2(5, alloc) ; + // 要素数5で初期値123 + std::vector v3(5, 123, alloc) ; +} +\end{lstlisting} + +これを実装するには、アロケーターを取ってデータメンバーにコピーするコンストラクターを書く。 + +\begin{lstlisting}[language={C++}] +vector( const allocator_type & alloc ) noexcept + : alloc( alloc ) +{ } +\end{lstlisting} + +ほかのコンストラクターはこのコンストラクターにまずデリゲートすればよい。 + +\begin{lstlisting}[language={C++}] +vector() + : vector( allocator_type() ) +{ } + +vector( size_type size, const allocator_type & alloc = allocator_type() ) + : vector( alloc ) +{ /* 実装 */ } +vector( size_type size, const_reference value, const allocator_type & alloc = allocator_type() ) + : vector( alloc ) +{ /* 実装 */ } +\end{lstlisting} + +\hypersection{ch3104}{要素数と初期値を取るコンストラクターの実装} +\index{resize@\texttt{resize}}\index{vector@\texttt{vector}!resize@\texttt{resize}} + +要素数と初期値を取るコンストラクターは\texttt{resize}を使えば簡単に実装できる。 + +\begin{lstlisting}[language={C++}] +vector( size_type size, const allocator_type & alloc ) + : vector( alloc ) +{ + resize( size ) ; +} +vector( size_type size, const_reference value, const allocator_type & alloc ) + : vector( alloc ) +{ + resize( size, value ) ; +} +\end{lstlisting} + +しかしこれは実装を\texttt{resize}に丸投げしただけだ。\texttt{resize}の実装をする前に、実装を楽にするヘルパー関数を実装する。 + +\hypersection{ch3105}{ヘルパー関数} +\index{へるぱかんすう@ヘルパー関数}\index{vector@\texttt{vector}!へるぱかんすう@ヘルパー関数} + +ここでは\texttt{vector}の実装を楽にするためのヘルパー関数をいくつか実装する。このヘルパー関数はユーザーから使うことは想定しないので、\texttt{private}メンバーにする。 + +\begin{lstlisting}[language={C++}] +// 例 +struct vector +{ +private : + // ユーザーからは使えないヘルパー関数 + void helper_function() ; +public : + // ユーザーが使える関数 + void func() + { + // ヘルパー関数を使って実装 + helper_function() ; + } +} ; +\end{lstlisting} + +\hypersubsection{ch310501}{ネストされた型名traits} +\index{あろけた@アロケーター}\index{allocator\_traits@\texttt{allocator\_traits}}\index{vector@\texttt{vector}!allocator\_traits@\texttt{allocator\_traits}} + +アロケーターは\texttt{allocator\_traits}を経由して使う。実際のコードはとても冗長になる。 + +\begin{lstlisting}[language={C++}] +template < typename Allocator > +void f( Allocator & alloc ) +{ + std::allocator_traits::allocate( alloc, 1 ) ; +} +\end{lstlisting} + +この問題はエイリアス名を使えば解決できる。 + +\begin{lstlisting}[language={C++}] +private : + using traits = std::allocator_traits ; + + template < typename Allocator > + void f( Allocator & alloc ) + { + traits::allocate( alloc, 1 ) ; + } +\end{lstlisting} + +\hypersubsection{ch310502}{allocate/deallocate} +\index{allocate@\texttt{allocate}}\index{vector@\texttt{vector}!allocate@\texttt{allocate}}\index{deallocate@\texttt{deallocate}}\index{vector@\texttt{vector}!deallocate@\texttt{deallocate}} + +\texttt{allocate(n)}はアロケーターから\texttt{n}個の要素を格納できる生のメモリーの動的確保をして先頭要素へのポインターを返す。 + +\texttt{deallocate(ptr)}はポインター\texttt{ptr}を解放する。 + +\begin{lstlisting}[language={C++}] +private: + pointer allocate( size_type n ) + { return traits::allocate( alloc, n ) ; } + void deallocate( ) + { traits::deallocate( alloc, first, capacity() ) ; } +\end{lstlisting} + +\hypersubsection{ch310503}{construct/destroy} +\index{construct@\texttt{construct}}\index{vector@\texttt{vector}!construct@\texttt{construct}}\index{destroy@\texttt{destroy}}\index{vector@\texttt{vector}!destroy@\texttt{destroy}} + +\texttt{construct(ptr)}は生のメモリーへのポインター\texttt{ptr}に\texttt{vector}の\texttt{value\_type}型の値をデフォルト構築する。 + +\texttt{construct(ptr, value)}は生のメモリーへのポインター\texttt{ptr}に値\texttt{value}のオブジェクトを構築する。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] + void construct( pointer ptr ) + { traits::construct( alloc, ptr ) ; } + void construct( pointer ptr, const_reference value ) + { traits::construct( alloc, ptr, value ) ; } + // ムーブ用 + void construct( pointer ptr, value_type && value ) + { traits::construct( alloc, ptr, std::move(value) ) ; } +\end{lstlisting} + +ムーブ用の\texttt{construct}についてはまだ気にする必要はない。この理解には、まずムーブセマンティクスを学ぶ必要がある。 + +\texttt{destroy(ptr)}は\texttt{ptr}の指すオブジェクトを破棄する。 + +\begin{lstlisting}[language={C++}] +private : + void destroy( pointer ptr ) + { traits::destroy( alloc, ptr ) ; } +\end{lstlisting} + +\hypersubsection{ch310504}{destroy\texttt{\_}until} +\index{destroy\_until@\texttt{destroy\_until}}\index{vector@\texttt{vector}!destroy\_until@\texttt{destroy\_until}} + +\texttt{destroy\_until(rend)}は、\texttt{vector}が保持する\texttt{rbegin()}からリバースイテレーター\texttt{rend}までの要素を破棄する。リバースイテレーターを使うので、要素の末尾から先頭に向けて順番に破棄される。なぜ末尾から先頭に向けて要素を破棄するかというと、C++では値の破棄は構築の逆順で行われるという原則があるからだ。 + +\begin{lstlisting}[language={C++}] +private : + void destroy_until( reverse_iterator rend ) + { + for ( auto riter = rbegin() ; riter != rend ; ++riter, --last ) + { + destroy( &*riter ) ; + } + } +\end{lstlisting} + +\texttt{\&*riter}はやや泥臭い方法だ。簡易\texttt{vector}\,の\texttt{iterator}は単なる\texttt{T *}\,だが、\texttt{riter}はリバースイテレーターなのでポインターではない。ポインターを取るために\,\texttt{*riter}でまず\texttt{T \&}を得て、そこに\,\texttt{\&}\,を適用することで\texttt{T *}\,を得ている。 + +破棄できたら有効な要素数を減らすために\,\texttt{{-}{-}last}する。 + +\clearpage +\hypersection{ch3106}{clear} +\index{clear@\texttt{clear}}\index{vector@\texttt{vector}!clear@\texttt{clear}} + +\texttt{clear()}はすべての要素を破棄する。 + +\begin{lstlisting}[language={C++}] +void clear() noexcept +{ + destroy_until( rend() ) ; +} +\end{lstlisting} + +先ほど実装した\texttt{destroy\_until(rend)}にリバースイテレーターの終端を渡せばすべての要素が破棄される。 + +\hypersection{ch3107}{デストラクター} +\index{ですとらくた@デストラクター}\index{vector@\texttt{vector}!ですとらくた@デストラクター} + +ヘルパー関数を組み合わせることでデストラクターが実装できるようになった。 + +\texttt{std::vector}のデストラクターは、 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 要素を末尾から先頭に向かう順番で破棄 +\item + 生のメモリーを解放する +\end{enumerate} + +この2つの処理はすでに実装した。デストラクターの実装は単にヘルパー関数を並べて呼び出すだけでよい。 + +\begin{lstlisting}[language={C++}] +~vector() +{ + // 1. 要素を末尾から先頭に向かう順番で破棄 + clear() ; + // 2. 生のメモリーを解放する + deallocate() ; +} +\end{lstlisting} + +\hypersection{ch3108}{reserveの実装} +\index{reserve@\texttt{reserve}}\index{vector@\texttt{vector}!reserve@\texttt{reserve}} + +\texttt{reserve}の実装は生の動的メモリーを確保してデータメンバーを適切に設定する。 + +ただし、いろいろと考慮すべきことが多い。 + +現在の\texttt{capacity}より小さい要素数が\texttt{reserve}された場合、無視してよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 要素数5 + std::vector v = {1,2,3,4,5} ; +(@\ifTombow\pagebreak\fi@) + // 3個の要素を保持できるよう予約 + v.reserve( 3 ) ; + // 無視する +} +\end{lstlisting} + +すでに指定された要素数以上に予約されているからだ。 + +動的メモリー確保が行われていない場合、単に動的メモリー確保をすればよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + // おそらく動的メモリー確保 + v.reserve( 10000 ) ; +} +\end{lstlisting} + +「おそらく」というのは、C++の規格は\texttt{vector}のデフォルトコンストラクターが予約するストレージについて何も言及していないからだ。すでに要素数10000を超えるストレージが予約されている実装も規格準拠だ。本書で実装している\texttt{vector}は、デフォルトコンストラクターでは動的メモリー確保をしない実装になっている。 + +有効な要素が存在する場合、その要素の値は引き継がなければならない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 要素数3 + std::vector v = {1,2,3} ; + // 1万個の要素を保持できるだけのメモリーを予約 + v.reserve( 10000 ) ; + // vは{1,2,3} +} +\end{lstlisting} + +つまり動的メモリー確保をしたあとに、既存の要素を新しいストレージにコピーしなければならないということだ。 + +まとめよう。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + すでに指定された要素数以上に予約されているなら何もしない +\item + まだ動的メモリー確保が行われていなければ動的メモリー確保をする +\item + 有効な要素がある場合は新しいストレージにコピーする +\end{enumerate} + +古いストレージから新しいストレージに要素をコピーするとき、古いストレージと新しいストレージが一時的に同時に存在しなければならない。 + +疑似コード風に記述すると以下のようになる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +template < typename T > +void f() +{ + // すでに動的確保した古いストレージ + auto old_ptr = new T ; + + // いま構築した新しいストレージ + auto new_ptr = new T ; + // 古いストレージから新しいストレージにコピー + // *new_ptr = *old_ptr ; + // 古いストレージを解放 + delete old_value ; +} +\end{lstlisting} + +このとき、\texttt{T}型がコピーの最中に例外を投げると、後続の\texttt{delete}が実行されなくなる。この問題に対処して例外安全にするために、C++20に入る見込みの標準ライブラリ、\texttt{std::scope\_exit}\index{scope\_exit@\texttt{scope\_exit}}を使う。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void f() +{ + // すでに動的確保した古いストレージ + auto old_ptr = new T ; + + // いま構築した新しいストレージ + auto new_ptr = new T ; + + // 関数fを抜けるときに古いストレージを解放する。 + std::scope_exit e( [&]{ delete old_ptr ; } ) ; + + // 古いストレージから新しいストレージにコピー + // *new_ptr = *old_ptr ; + + // 変数eの破棄に伴って古いストレージが解放される +} +\end{lstlisting} + +これを踏まえて\texttt{reserve}を実装する。 + +\begin{lstlisting}[language={C++}] +void reserve( size_type sz ) +{ + // すでに指定された要素数以上に予約されているなら何もしない + if ( sz <= capacity() ) + return ; + + // 動的メモリー確保をする + auto ptr = allocate( sz ) ; + + // 古いストレージの情報を保存 + auto old_first = first ; + auto old_last = last ; + auto old_capacity = capacity() ; + + // 新しいストレージに差し替え + first = ptr ; + last = first ; + reserved_last = first + sz ; + + // 例外安全のため + // 関数を抜けるときに古いストレージを破棄する + std::scope_exit e( [&]{ + traits::deallocate( alloc, old_first, old_capacity ) ; + } ) ; + + // 古いストレージから新しいストレージに要素をコピー構築 + // 実際にはムーブ構築 + for ( auto old_iter = old_first ; old_iter != old_last ; ++old_iter, ++last ) + { + // このコピーの理解にはムーブセマンティクスの理解が必要 + construct( last, std::move(*old_iter) ) ; + } + + // 新しいストレージにコピーし終えたので + // 古いストレージの値は破棄 + for ( auto riter = reverse_iterator(old_last), rend = reverse_iterator(old_first) ; + riter != rend ; ++riter ) + { + destroy( &*riter ) ; + } + // scope_exitによって自動的にストレージが破棄される +} +\end{lstlisting} + +ここではまだ学んでいないムーブの概念が出てくる。これはムーブセマンティクスの章で詳しく学ぶ。 + +\clearpage +\hypersection{ch3109}{resize} +\index{resize@\texttt{resize}}\index{vector@\texttt{vector}!resize@\texttt{resize}} + +\texttt{resize(sz)}は要素数を\texttt{sz}個にする。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 要素数0 + std::vector v ; + // 要素数10 + v.resize(10) ; + // 要素数5 + v.resize(5) + // 要素数変わらず + v.resize(5) +} +\end{lstlisting} + +\texttt{resize}は呼び出し前より要素数を増やすことも減らすこともある。また変わらないこともある。 + +要素数が増える場合、増えた要素数の値はデフォルト構築された値になる。 + +\begin{lstlisting}[language={C++}] +struct X +{ + X() { std::cout << "default constructed.\n" ; } +} ; + +int main() +{ + std::vector v ; + v.resize(5) ; +} +\end{lstlisting} + +このプログラムを実行すると、\texttt{"default constructed.{\textbackslash}n"}\,は5回標準出力される。 + +\texttt{resize(sz, value)}は\texttt{resize}を呼び出した結果要素が増える場合、その要素を\texttt{value}で初期化する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3} ; + v.resize(5, 4) ; + // vは{1,2,3,4,4} +} +\end{lstlisting} + +要素数が減る場合、要素は末尾から順番に破棄されていく。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +struct X +{ + ~X() + { std::cout << "destructed.\n"s ; } +} ; + +int main() +{ + std::vector v(5) ; + v.resize(2) ; + std::cout << "resized.\n"s ; +} +\end{lstlisting} + +このプログラムを実行すると、以下のように出力される。 + +\begin{lstlisting}[style=terminal] +destructed. +destructed. +destructed. +resized. +destructed. +destructed. +\end{lstlisting} + +最初の\texttt{v.resize(2)}で、\texttt{v[4], v[3], v[2]}が書いた順番で破棄されていく。\texttt{main}関数を抜けるときに残りの\texttt{v[1], v[0]}が破棄される。 + +\texttt{resize(sz)}を呼び出したときに\texttt{sz}が現在の要素数と等しい場合は何もしない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 要素数5 + std::vector v(5) ; + v.resize(5) ; // 何もしない +} +\end{lstlisting} + +まとめると\texttt{resize}は以下のように動作する。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 現在の要素数より少なくリサイズする場合、末尾から要素を破棄する +\item + 現在の要素数より大きくリサイズする場合、末尾に要素を追加する +\item + 現在の要素数と等しくリサイズする場合、何もしない +\end{enumerate} + +\ifTombow\pagebreak\fi +実装しよう。 + +\begin{lstlisting}[language={C++}] + void resize( size_type sz ) + { + // 現在の要素数より少ない + if ( sz < size() ) + { + auto diff = size() - sz ; + destroy_until( rbegin() + diff ) ; + last = first + sz ; + } + // 現在の要素数より大きい + else if ( sz > size() ) + { + reserve( sz ) ; + for ( ; last != reserved_last ; ++last ) + { + construct( last ) ; + } + } + } +\end{lstlisting} + +要素を破棄する場合、破棄する要素数だけ末尾から順番に破棄する。 + +要素を増やす場合、\texttt{reserve}を呼び出してメモリーを予約してから、追加の要素を構築する。 + +\texttt{sz == size()}の場合は、どちらの\texttt{if}文の条件にも引っかからないので、何もしない。 + +\texttt{size(sz, value)}は、追加の引数を取るほか、\texttt{construct(iter)}の部分が\texttt{constrcut(iter, value)}に変わるだけだ。 + +\begin{lstlisting}[language={C++}] +void resize( size_type sz, const_reference value ) +{ + // ... + construct( iter, value ) ; + // ... +} +\end{lstlisting} + +これで自作の\texttt{vector}はある程度使えるようになった。コンストラクターで要素数を指定できるし、リサイズもできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + vector v(10, 1) ; + v[2] = 99 ; + v.resize(5) ; + // vは{1,1,99,1,1} +} +\end{lstlisting} + +\hypersection{ch3110}{push\texttt{\_}back} +\index{push\_back@\texttt{push\_back}}\index{vector@\texttt{vector}!push\_back@\texttt{push\_back}} + +\texttt{push\_back}は\texttt{vector}の末尾に要素を追加する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + // vは{} + v.push_back(1) ; + // vは{1} + v.push_back(2) ; + // vは{1,2} +} +\end{lstlisting} + +\texttt{push\_back}の実装は、末尾の予約された未使用のストレージに値を構築する。もし予約された未使用のストレージがない場合は、新しく動的メモリー確保する。 + +追加の動的メモリー確保なしで保持できる要素の個数はすでに実装した\texttt{capacity()}で取得できる。\texttt{push\_back}は要素を1つ追加するので、\texttt{size() + 1 <= capacity()}ならば追加の動的メモリー確保はいらない。逆に、\texttt{size() + 1 > capacity()}ならば追加の動的メモリー確保をしなければならない。追加の動的メモリー確保はすでに実装した\texttt{reserve}を使えばよい。 + +\begin{lstlisting}[language={C++}] +void push_back( const_reference value ) +{ + // 予約メモリーが足りなければ拡張 + if ( size() + 1 > capacity() ) + { + // 1つだけ増やす + reserve( size() + 1 ) ; + } + + // 要素を末尾に追加 + construct( last, value ) ; + // 有効な要素数を更新 + ++last ; +} +\end{lstlisting} + +これは動く。ただし、効率的ではない。自作の\texttt{vector}を使った以下のような例を見てみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 要素数10000 + vector v(1000) ; +(@\ifTombow\pagebreak\fi@) + // 10001個分のメモリーを確保する + // 10000個の既存の要素をコピーする + v.push_back(0) ; + // 10002個分のメモリーを確保する + // 10001個の既存の要素をコピーする + v.push_back(0) ; +} +\end{lstlisting} + +たった1つの要素を追加するのに、毎回動的メモリー確保と既存の全要素のコピーをしている。これは無駄だ。 + +\texttt{std::vector}は\texttt{push\_back}で動的メモリー確保が必要な場合、\texttt{size()+1}よりも多くメモリーを確保する。こうすると、\texttt{push\_back}を呼び出すたびに毎回動的メモリー確保と全要素のコピーを行う必要がなくなるので、効率的になる。 + +ではどのくらい増やせばいいのか。10個ずつ増やす戦略は以下のようになる。 + +\begin{lstlisting}[language={C++}] +void push_back( const_reference value ) +{ + // 予約メモリーが足りなければ拡張 + if ( size() + 1 > capacity() ) + { + // 10個増やす + reserve( capacity() + 10 ) ; + } + construct( last, value ) ; + ++last ; +} +\end{lstlisting} + +しかしこの場合、以下のようなコードで効率が悪い。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + for ( auto i = 0 ; i != 10000 ; ++i ) + { + v.push_back(i) ; + } +} +\end{lstlisting} + +10個ずつ増やす戦略では、この場合に1000回の動的メモリー確保と全要素のコピーが発生する。 + +上のような場合、\texttt{vector}の利用者が事前に\texttt{v.reserve(10000)}とすれば効率的になる。しかし、コンパイル時に要素数がわからない場合、その手も使えない。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector inputs ; + // 要素数は実行時にしかわからない + // 10万個の入力が行われるかもしれない + std::copy( + std::ostream_iterator(std::cin), + std::ostream_iterator(), + std::back_inserter(inputs) ) ; +} +\end{lstlisting} + +よくある実装は、現在のストレージサイズの2倍のストレージを確保する戦略だ。 + +\begin{lstlisting}[language={C++}] +void push_back( const_reference value ) +{ + // 予約メモリーが足りなければ拡張 + if ( size() + 1 > capacity() ) + { + // 現在のストレージサイズ + auto c = size() ; + // 0の場合は1に + if ( c == 0 ) + c = 1 ; + else + // それ以外の場合は2倍する + c *= 2 ; + + reserve( c ) ; + } + construct( last, value ) ; + ++last ; +} +\end{lstlisting} + +\texttt{size()}は\texttt{0}を返す場合もあるということに注意。単に\texttt{reserve(size()*2)}としたのでは\texttt{size() == 0}のときに動かない。 + +\hypersubsection{ch311001}{shrink\texttt{\_}to\texttt{\_}fit} +\index{shrink\_to\_fit@\texttt{shrink\_to\_fit}}\index{vector@\texttt{vector}!shrink\_to\_fit@\texttt{shrink\_to\_fit}} + +\texttt{shrink\_to\_fit()}は\texttt{vector}が予約しているメモリーのサイズを実サイズに近づけるメンバー関数だ。 + +本書で実装してきた自作の\texttt{vector}は、\texttt{push\_back}時に予約しているメモリーがなければ、現在の要素数の2倍のメモリーを予約する実装だった。すると以下のようなコードで、 +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + vector v ; + std::copy( std::istream_iterator(std::cin), std::istream_iterator(), + std::back_inserter(v) ) ; +} +\end{lstlisting} +ユーザーが4万個の\texttt{int}型の値を入力した場合、65536個の\texttt{int}型の値を保持できるだけのメモリーが確保されてしまい、差し引き\texttt{sizeof(int) * 25536}バイトのメモリーが未使用のまま確保され続けてしまう。 + +メモリー要件の厳しい環境ではこのようなメモリーの浪費を避けたい。しかし、実行時にユーザーから任意の個数の入力を受けるプログラムを書く場合には、\texttt{push\_back}を使いたい。 + +こういうとき、\texttt{shrink\_to\_fit}は\texttt{vector}が予約するメモリーを切り詰めて実サイズに近くする、かもしれない。「かもしれない」というのは、C++の標準規格は\texttt{shrink\_to\_fit}が必ずメモリーの予約サイズを切り詰めるよう規定してはいないからだ。 + +自作の\texttt{vector}では必ず切り詰める実装にしてみよう。 + +まず予約するメモリーを切り詰めるとはどういうことか。現在予約しているメモリーで保持できる最大の要素数は\texttt{capacity()}で得られる。実際に保持している要素数を返すのは\texttt{size()}だ。すると\texttt{size() == capacity()}になるようにすればいい。 + +\begin{lstlisting}[language={C++}] +vector v ; +// ... +v.shrink_to_fit() ; +v.size() == v.capacity() ; // trueにする +\end{lstlisting} + +\texttt{shrink\_to\_fit()}を呼んだとき、すでに\texttt{size() == capacity()}が\texttt{true}である場合は、何もしなくてもよい。 + +それ以外の場合は、現在の有効な要素数分の新しいストレージを確保し、現在の値を新しいストレージにコピーし、古いメモリーは破棄する。 + +\begin{lstlisting}[language={C++}] +void shrink_to_fit() +{ + // 何もする必要がない + if ( size() == capacity() ) + return ; + + // 新しいストレージを確保 + auto ptr = allocate( size() ) ; + // コピー + auto current_size = size() ; + for ( auto raw_ptr = ptr, iter = begin(), iter_end = end() ; + iter != iter_end ; ++iter, ++raw_ptr ) + { + construct( raw_ptr, *iter ) ; + } + // 破棄 + clear() ; + deallocate() ; + // 新しいストレージを使う + first = ptr ; + last = ptr + current_size ; + reserved_last = last ; +} +\end{lstlisting} + +この実装は\texttt{reserve}と似ている。 + +\hypersection{ch3111}{vectorのその他のコンストラクター} + +\hypersubsection{ch311101}{イテレーターのペア} + +\texttt{std::vector}はイテレーターのペアを取り、その参照する値で要素を初期化できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array a {1,2,3,4,5} ; + std::vector v( std::begin(a), std::end(a) ) ; + // vは{1,2,3,4,5} +} +\end{lstlisting} + +これはすでに実装したメンバー関数を使えば簡単に実装できる。 + +\begin{lstlisting}[language={C++}] +template < typename InputIterator > +vector( InputIterator first, InputIterator last, const Allocator & = Allocator() ) +{ + reserve( std::distance( first, last ) ; + for ( auto i = first ; i != last ; ++i ) + { + push_back( *i ) ; + } +} +\end{lstlisting} + +\clearpage +\hypersubsection{ch311102}{初期化リスト} + +\texttt{std::vector}は配列のように初期化できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3} ; +} +\end{lstlisting} + +このような初期化を\textsf{リスト初期化}と呼ぶ。 + +リスト初期化に対応するためには、\texttt{std::initializer\_list}\,を引数に取るコンストラクターを追加する。 + + \ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[language={C++}] +template < typename T, Allocator = std::allocator > +{ +// コンストラクター +vector( std::initializer_list init, const Allocator & = Allocator() ) ; + // 省略... +} ; +\end{lstlisting} + +\texttt{std::initializer\_list}\,は\texttt{T}型の要素を格納する標準ライブラリで、\texttt{\{a,b,c,...\}}\,のようなリスト初期化で構築することができる。 + +\begin{lstlisting}[language={C++}] +std::initializer_list init = {1,2,3,4,5} ; +\end{lstlisting} + +\texttt{std::initializer\_list}は\texttt{begin}/\texttt{end}によるイテレーターを提供しているので、すでに実装したコンストラクターにデリゲートすればよい。 + +\begin{lstlisting}[language={C++}] +vector( std::initializer_list init, const Allocator & alloc = Allocator() ) ; + : vector( std::begin(init), std::end(init), alloc ) +{ } +\end{lstlisting} diff --git a/TeX/035-copy.tex b/TeX/035-copy.tex new file mode 100644 index 0000000..dcfc518 --- /dev/null +++ b/TeX/035-copy.tex @@ -0,0 +1,776 @@ +\hyperchapter{ch32}{コピー}{コピー} +\index{こぴ@コピー} + +クラスにコピー\index{くらす@クラス!こぴ@コピー}を正しく実装するためには、まずコピーが何であるかを理解しなければならない。 + +\hypersection{ch3201}{普通のコピー} + +C++を書くユーザーは、クラス型のオブジェクトを使うとき、クラスが普通の型(regular type)\index{ふつうのかた@普通の型}\index{くらす@クラス!ふつうのかた@普通の型}のように振る舞うことを期待している。この普通にはさまざまな意味がある。 + +\texttt{int}型の変数をコピーするとき、コピー先\index{こぴ@コピー!さき@〜先}の変数はコピー元\index{こぴ@コピー!もと@〜元}の変数と等しくなる。 + +\begin{lstlisting}[language={C++}] +int source = 42 ; +int destination = source ; +\end{lstlisting} + +この例では変数\texttt{destination}は変数\texttt{source}と等しくなる。\texttt{source == destination}は\texttt{true}となり、\texttt{destination}の値は\texttt{42}になる。 + +コピーの結果、コピー先の変数は値が書き換えられる。コピー元の変数は変わらない。上の例で、変数\texttt{source}が勝手に別の値になることは「普通」はない。 + +我々が普通にコピーと認識しているものは、C++の文法的にはコピー構築\index{こぴ@コピー!こうちく@構築}とコピー代入\index{こぴ@コピー!だいにゆう@代入}に分けることができる。 + +\begin{lstlisting}[language={C++}] +T source ; +// コピー構築 +T a = source ; +T b(source) ; +T c{source} + +(@\ifTombow\pagebreak\fi@) +T d ; +// コピー代入 +d = source ; +\end{lstlisting} + +ユーザーは普通、コピー構築とコピー代入のコピーが両方とも同じ挙動をすると期待している。コピー構築とコピー代入のどちらか片方が使えるならば、もう片方も使えるべきで、そのコピーの挙動は同じであるべきだ。 + +コピー代入にはコピーの普通に加えて、さらにユーザーが代入に期待する普通がある。 + +代入式を評価した結果は、代入されるオブジェクトへの\texttt{lvalue}リファレンスになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x, y, z ; + // x, y, zに0を代入 + x = y = z = 0 ; +} +\end{lstlisting} + +これはまず\texttt{z = 0}が評価される。変数\texttt{z}の値は\texttt{0}になり、式を評価した結果の値は\texttt{z}への\texttt{lvalue}リファレンスだ。なので、\texttt{y = z = 0}というのは、\texttt{y = (z=0)}となる。\texttt{z=0}については\texttt{z}であるので、\texttt{y = z}となる。ここでの\texttt{z}は\texttt{0}を代入されたあとの\texttt{z}なので、値は\texttt{0}だ。その結果変数\texttt{y}の値は\texttt{0}になる。変数\texttt{x}の場合も同様だ。 + +以下のような例も見てみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x ; + (x = 0) = 1 ; +} +\end{lstlisting} + +これは\texttt{(x = 0)}の結果に\texttt{1}を代入している。\texttt{x=0}の結果は\texttt{x}なので、\texttt{x}には\texttt{0}が代入されたあとに\texttt{1}が代入される。結果として\texttt{x}の値は\texttt{1}になる。 + +\clearpage +\hypersection{ch3202}{コピーコンストラクター} +\index{こぴこんすとらくた@コピーコンストラクター}\index{くらす@クラス!こぴこんすとらくた@コピーコンストラクター}\index{こんすとらくた@コンストラクター!こぴ@コピー〜} + +コピー構築の場合、コピーコンストラクターが呼ばれる。 + +\begin{lstlisting}[language={C++}] +struct Value +{ + // コピーコンストラクター + X( const X & source ) + { } +} ; + +int main() +{ + Value source ; + // コピーコンストラクターを呼ぶ + Value b = source ; + Value c(source) ; + Value d{source} ; +} +\end{lstlisting} + +コピーコンストラクターは\texttt{クラス型へのlvalueリファレンス型}\index{lvalueりふあれんすかた@\texttt{lvalue}リファレンス型}を引数に取る\texttt{コンストラクター}だ。 + +\begin{lstlisting}[language={C++}] +struct X +{ + X( const X & source ) { } +} ; +\end{lstlisting} + +引数は通常は\texttt{constなlvalueリファレンス型}だが、\texttt{非constなlvalueリファレンス型}を引数に取るコンストラクターも\texttt{コピーコンストラクター}となる。 + +\begin{lstlisting}[language={C++}] +struct X +{ + X( X & source ) { } +} ; +\end{lstlisting} + +ただし、非\texttt{const}な\texttt{lvalue}リファレンス型を引数に取るコピーコンストラクターは通常は使わない。なぜならば、コピーの結果、コピー元が書き換えられるような挙動は不自然だからだ。 + +\begin{lstlisting}[language={C++}] +struct funny_number +{ + int n ; + funny_number( int n = 0 ) + : n(n) { } +(@\ifTombow\pagebreak\fi@) + funny_number( funny_number & source ) + : n( source.n ) + { + source.n = 0 ; + } +} ; + +int main() +{ + funny_number a = 1 ; + // コピー + funny_number b = a ; + // a == 0 + // b == 1 +} +\end{lstlisting} + +このおかしな\texttt{funny\_number}のコピーコンストラクターはコピー元を0に書き換えてしまう。このコードは完全に合法なC++のコードだが、このようにコピーコンストラクターを実装するのはおすすめできない。なぜならば、ユーザーはコピーについて上で示したような意味を普通だと想定しているため、普通から外れるような型はユーザーのあてが外れてしまうからだ。 + +\hypersection{ch3203}{コピー代入演算子} +\index{こぴだいにゆうえんざんし@コピー代入演算子} + +コピー代入演算子は\texttt{クラス型へのlvalueリファレンス型}を引数に取る\texttt{operator =}\,\index{=@\texttt{=}}のオーバーロードだ。 + +\begin{lstlisting}[language={C++}] +struct X +{ + X & operator = ( const X & source ) + { + return *this ; + } +} ; +\end{lstlisting} + +コピーコンストラクターと同じく、コピー代入演算子の引数は非\texttt{const}な\texttt{lvalue}リファレンスでもよい。ただし、ユーザーの期待する普通にはそぐわない結果になる。 + +コピー代入演算子の戻り値の型はクラス型への非\texttt{const}な\texttt{lvalue}リファレンスでなくてもよい。ただし、その場合もユーザーの期待にそぐわないことになる。 + +\begin{lstlisting}[language={C++}] +struct X +{ + void operator = ( const X & source ) { } +} ; + +int main() +{ + X a, b, c ; + // OK + a = b ; + // エラー + a = b = c ; +} +\end{lstlisting} + +\texttt{a = b = c}は、クラス\texttt{X}のコピー代入演算子の戻り値の型が\texttt{void}なので動かない。ユーザーは普通、これが動くことを期待している。ユーザーの普通の期待に答えるためにはクラスへの非\texttt{const}な\texttt{lvalue}リファレンスを返さなければならない。 + +\hypersection{ch3204}{コピーの挙動} + +クラスのコピーは何をすればいいのだろうか。クラスにコピーコンストラクターとコピー代入演算子を書かない場合、デフォルトのコピーコンストラクター、コピー代入演算子が生成される。 + +デフォルトのコピー\index{こぴ@コピー!でふおるとの@デフォルトの〜}は、クラスのデータメンバーをそれぞれコピーする。 + +\begin{lstlisting}[language={C++}] +struct Point +{ + int x ; + int y ; + int z ; +} ; + +int main() +{ + Point a{1,2,3} ; + Point b = a ; + Point c ; + c = a ; +} +\end{lstlisting} + +上記のコードは、以下のように書いたのと同じだ。 + +\begin{lstlisting}[language={C++}] +Point b{ a.x, a.y, a.z } ; +Point c ; +c.x = a.x ; +c.y = a.y ; +c.z = a.z ; +\end{lstlisting} + +つまり、以下のようなコピーコンストラクターとコピー代入演算子を書いたのと同じだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +struct Point +{ + int x ; + int y ; + int z ; + + Point( const Point & r ) + : x(r.x), y(r.y), z(r.z) + { } + + Point & operator = ( const Point & r ) + { + x = r.x ; + y = r.y ; + z = r.z ; + } +} ; +\end{lstlisting} + +では\texttt{std::vector}のコピーはどうなるだろうか。 +\index{vector@\texttt{vector}}\index{こぴ@コピー!vevtorの@\texttt{vector}の〜} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + std::vector w = v ; + // wは{1,2,3,4,5} +} +\end{lstlisting} + +\texttt{std::vector}をコピーした場合、その値がコピーされる。 + +自作の\texttt{vector}のコピーはどのように実装すればいいだろうか。デフォルトのコピーに任せてもいいのだろうか。デフォルトのコピーを使う場合、コピーコンストラクターは以下のように書いたものと同じだ。 + +\begin{lstlisting}[language={C++}] +template < typename T, typename Allocator = std::allocator > +class vector +{ + // ... その他のメンバー +private : + pointer first = nullptr ; + pointer last = nullptr ; + pointer reserved_last = nullptr ; + allocator_type alloc ; + +(@\ifTombow\pagebreak\fi@) +public : + // コピーコンストラクター + vector( const vector & r ) + : first( r.first ), last( r.last ), + , reserved_last( r.reserved_last ), + , alloc( r.alloc ) + { } +} ; +\end{lstlisting} + +これは問題だ。以下のコードを考える。 + +\begin{lstlisting}[language={C++}] +int main() +{ + vector v{1} ; + vector w = v ; + // wのデストラクターが呼ばれる + // vのデストラクターが呼ばれる +} +\end{lstlisting} + +\texttt{w = v}で、\texttt{v}のデータメンバーの値がそれぞれ\texttt{w}のデータメンバーにコピーされる。 + +\texttt{main}関数を抜けるので、構築の逆順に変数が破棄される。この場合\texttt{w}が先に破棄される。破棄にあたっては\texttt{w}のデストラクターが呼ばれる。 + +この場合、\texttt{w}のデストラクターは、 +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + ポインター\texttt{first}が指すオブジェクトのデストラクターを呼び出す +\item + ポインター\texttt{first}の指す生のストレージを解放 +\end{enumerate} +する。 + +次に\texttt{v}が破棄される。\texttt{v}のデストラクターは\texttt{w}のデストラクターとまったく同じことをする。ただし、ポインター\texttt{first}の指すオブジェクトはすでにデストラクターが呼び出されているし、ポインター\texttt{first}の指す生のストレージも解放されている。 + +すでにデストラクターを呼び出したオブジェクトに対してもう一度デストラクターを呼び出した場合の挙動は未定義だ。すでに解放したストレージを指すポインターに対してもう一度ストレージの解放した場合の挙動は未定義だ。したがって、このプログラムの挙動は未定義となる。 + +コピー代入も同じ問題を抱えている。しかも別の問題まである。例えば以下の例を見てみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + vector v = {1,2,3} ; + vector w = {4,5,6} ; + w = v ; +} +\end{lstlisting} + +変数\texttt{w}はまず要素を保持するためのメモリーを動的確保する。その後、\texttt{w}に\texttt{v}が代入されるわけだが、このとき\texttt{w}が動的確保したメモリーを指すポインターの値が上書きされてしまう。\texttt{w}が破棄されるとき、\texttt{w}がもともと持っていた要素は破棄されなくなり、ストレージも解放されなくなる。 + +\hypersection{ch3205}{所有するクラス} + +この問題は「所有」\index{しよゆう@所有}\index{くらす@クラス!しよゆう@所有}という考え方を使うと解決できる。 + +問題を簡単にするために、以下のようなクラスを考えよう。 + +\begin{lstlisting}[language={C++}] +template < typename T > +class own +{ +private : + T * ptr ; +public : + own( ) + : ptr( new T ) + { } + ~own() + { delete ptr ; } + + T * get() const { return ptr ; } + +} ; +\end{lstlisting} + +このクラスはコンストラクターでテンプレートパラメーター\texttt{T}型のオブジェクトを動的メモリー確保をし、デストラクターでメモリーの解放をする。 + +コピーコンストラクターとコピー代入演算子は定義していないので、デフォルトのコピーが使われる。 + +デフォルトのコピーを使うことを明示する方法もある。\texttt{= default}\index{= default@\texttt{= default}}を使うのだ。 + +\begin{lstlisting}[language={C++}] +class own +{ + // その他のメンバー +public : + own( const own & ) = default ; + own & operator ==( const own & ) = default ; +} +\end{lstlisting} + +コピーコンストラクター、コピー代入演算子となる宣言に\,\texttt{= default}を使うと、デフォルトのコピー実装を使うということを明示的に宣言したことになる。この文法はややわかりにくいが、こういうものだと思って覚えておこう。 + +このようなクラスを「デフォルトのコピー」でコピーしたとき、コピーされるのはポインターの値だ。ポインターが参照する先は同じだ。 + +この場合、クラスはポインターの参照するオブジェクトを所有していると考えることができる。ポインターの値をコピーするということは、所有権を共有するということだ。所有権を共有していることを考慮しないまま、クラスのオブジェクトが破棄されたときにポインターの参照先まで破棄してしまうと、所有したつもりになっているクラスのオブジェクトが出来上がってしまう。 + +普通の型のように振る舞うコピーを実装するには、コピーの際に所有権を共有しない実装をする。具体的には、コピーのときに新しく動的メモリー確保し、値をコピーするのだ。 + +コピーコンストラクターは以下のようになる。 + +\begin{lstlisting}[language={C++}] +own( const own & r ) + : ptr( new T( *r.ptr ) ) +{ } +\end{lstlisting} + +今回の場合、コピー代入演算子で動的メモリー確保をする必要はない。なぜならば、コピー代入演算子が呼ばれたということは、いずれかのコンストラクターがすでに呼ばれていて、動的メモリー確保はされているからだ。 + +\begin{lstlisting}[language={C++}] +own & operator = ( const own & r ) +{ + *ptr = *r.ptr ; + return *this ; +} +\end{lstlisting} + +このコードには少し問題がある。変数は自分自身に代入ができるのだ。 + +\begin{lstlisting}[language={C++}] +// 1GBもの巨大なサイズのクラス +struct one_giga_byte { std::byte storage[(@\textcolor{black}{\texttt{1'000'000'000}}@)] ; } + +int main() +{ + own x ; + // 1GBのコピーが発生 + x = x ; +} +\end{lstlisting} + +自分自身に代入というのは少し奇妙だが、これはC++では普通のことだ。クラス型はできるだけ普通に振る舞うべきだ。 + +普通のクラスは、自分自身への代入で特に何かをする必要はない。したがって、単に自分自身への代入が行われたことを判定したならば、コピーを行わないという処理でいい。 + +自分自身への代入を判定するには、コピー代入演算子の引数のリファレンスが指すオブジェクトのポインターが\texttt{this}ポインターと等しいかどうかを調べればよい。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +own & operator = ( const own & r ) +{ + // 自分自身への代入でなければ + if ( this != &r ) + { + // コピー処理 + *ptr = *r.ptr ; + } + return *this ; +} +\end{lstlisting} + +\hypersection{ch3206}{own\texttt{<}U\texttt{>}\,からown\texttt{<}T\texttt{>}\,への変換} + +C++では、\texttt{int}型から\texttt{long}型に、変換することができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int a = 123 ; + // 変換してコピー + long b = a ; +} +\end{lstlisting} + +これは厳密には変換であってコピーではないのだが、コピーによく似ている。 + +これと同じことを、\texttt{own}\,でやるにはどうすればいいのだろうか。つまり\texttt{own}\,から\texttt{own}\,への変換だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + own a ; + *a.get() = 123 ; + own b = a ; + *b.get() ; // long型の123 +} +\end{lstlisting} + +単に\texttt{own}\,からの変換だけであれば、\texttt{own}\,型から変換するコンストラクターを書けばよい。 + +\begin{lstlisting}[language={C++}] +template < typename T > +class own +{ +private : + T * ptr ; +(@\ifTombow\pagebreak\fi@) +public : + // 変換コンストラクター + own( const own & r ) + : ptr( new T(*r.get()) ) + { } + // ... +} +\end{lstlisting} + +このような自分自身以外の型の引数を1つだけ取るコンストラクターのことを、\texttt{変換コンストラクター}\index{へんかんこんすとらくた@変換コンストラクター}\index{こんすとらくた@コンストラクター!へんかん@変換〜}という。 + +しかしこれでは\texttt{own}\,からの変換にしか対応できない。しかも\texttt{int}型から変換できない型を使うとエラーとなる。 + +\begin{lstlisting}[language={C++}] +// int型から変換できない型 +struct I_hate_int +{ + // デフォルトのデフォルトコンストラクター + I_hate_int() = default ; + // intからの変換コンストラクター + I_hate_int(int) = delete ; +} + +int main() +{ + // エラー + own a ; +} +\end{lstlisting} + +関数の宣言に\,\texttt{= delete}\index{= delete@\texttt{= delete}}を書くと、その関数を消すことができる。「消す」というのは、その関数を使った時点でプログラムがコンパイルエラーになるという意味だ。 + +この問題を解決するにはテンプレートを使う。 + +\begin{lstlisting}[language={C++}] +template < typename T > +class own +{ +private : + T * ptr ; +public : + template < typename U > + own( const own & r ) + : ptr( new T(*r.get()) ) + { } + // ... +} +\end{lstlisting} + +こうすると任意の型\texttt{T, U}について、\texttt{U}型から\texttt{T}型に変換構築できるのであれば、\texttt{own}\,から\texttt{own}\,への変換構築ができる。 + +しかし、上のクラス\texttt{I\_hate\_int}型は任意の型から変換できないので、この変換コンストラクターテンプレートの存在は問題にならならないのだろうか。心配御無用。テンプレートは具体的なテンプレート実引数が与えられて初めてコードが生成される。実際に使わない限りは問題にならない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 問題なし + own a ; + // 問題なし + own b ; + + // エラー + // 実際に使われた + a = b ; +} +\end{lstlisting} + +\hypersection{ch3207}{もう少し複雑な所有するクラス} + +同じく所有するクラスだが、もう少し複雑な例を考えよう。 + +\begin{lstlisting}[language={C++}] +template < typename T > +class dynamic_array +{ +private : + T * first ; + T * last ; +public : + dynamic_array( std::size_t size = 0 ) + : first( new T[size]), last( first + size ) + { } + ~dynamic_array() + { delete[] first ; } + + T & operator [] ( std::size_t i ) const noexcept + { return first[i] ; } + std::size_t size() const noexcept + { return last - first ; } + T * begin() const noexcept + { return first ; } + T * end() const noexcept + { return last ; } +} ; + +int main() +{ + dynamic_array a(10) ; + a[0] = 1 ; + a[1] = 2 ; +} +\end{lstlisting} + +この\texttt{dynamic\_array}\,は\texttt{T}型の動的な配列クラスだ。配列のサイズは実行時に指定できる。 + +このようなクラスのコピーはどうなるだろうか。 + +コピーコンストラクターは簡単だ。コピー元と同じサイズの配列を動的確保し、要素をコピーすればいいだけだ。 + +\begin{lstlisting}[language={C++}] +dynamic_array( const dynamic_array & r ) + : first( new T[r.size()]), last( first + r.size() ) +{ + std::copy( r.begin(), r.end(), begin() ) ; +} +\end{lstlisting} + +コピー代入演算子でも、場合によっては動的メモリー確保が必要になる。現在所有しているメモリーとは異なるサイズのオブジェクトからコピーする場合だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + dynamic_array a(5) ; + dynamic_array b(10) ; + // aの所有するメモリーはサイズ不足 + a = b ; +} +\end{lstlisting} + +コピー元よりコピー先の方がメモリーが多い場合、つまり\texttt{b = a}の場合は動的メモリー確保をしないという実装もできるが、その場合実際に確保したメモリーサイズと、クラスが認識しているメモリーサイズが異なることになる。今回はサイズが違う場合は必ず動的メモリー確保をすることにしよう。 + +\begin{lstlisting}[language={C++}] +dynamic_array & operator == ( const dynamic_array & r ) +{ + // 自分自身への代入ではない場合 + // かつ + // サイズが違う場合 + if ( this != &r && size() != r.size() ) + { + // コピー処理 + } + return *this ; +} +\end{lstlisting} + +\texttt{new}したメモリーは\texttt{delete}しなければならない。そこで、コピー代入演算子はまず自分の所有するメモリーを\texttt{delete}してから\texttt{new}し、値をコピーすることになる。 + +\begin{lstlisting}[language={C++}] +dynamic_array & operator == ( const dynamic_array & r ) +{ + if ( this != &r && size() != r.size() ) + { + // コピー先が所有しているメモリーの解放 + delete first ; + // コピー元と同じサイズの動的メモリー確保 + first = new T[r.size()] ; + last = first + r.size() ; + // コピー元の値をコピー + std::copy( r.begin(), r.end(), begin() ) ; + } + return *this ; +} +\end{lstlisting} + +\hypersection{ch3208}{vectorのコピー} +\index{vector@\texttt{vector}!こぴ@コピー} + +自作の\texttt{vector}のコピーを実装していこう。 + +\hypersubsection{ch320801}{コピーコンストラクター} +\index{vector@\texttt{vector}!こぴこんすとらくた@コピーコンストラクター}\index{こぴこんすとらくた@コピーコンストラクター}\index{こんすとらくた@コンストラクター!こぴ@コピー〜} + +\texttt{std::vector}では、アロケーターのコピーだけがちょっと特殊になっている。コンテナーのコピーにあたってアロケーターをコピーすべきかどうかは、アロケーターの実装が選べるようになっている。このために、\texttt{std::allocator\_traits::select\_on\_container\_copy\_{\allowbreak}construction(alloc)}を呼び出し、その戻り値でアロケーターを初期化する。\texttt{std::allocator\_{\allowbreak}traits}\,という型については、すでに\texttt{traits}というエイリアスを宣言しているので、以下のようにする。 +\index{allocator\_traits@\texttt{allocator\_traits}}\index{select\_on\_container\_copy\_construction@\texttt{select\_on\_container\_copy\_construction}} + +\begin{lstlisting}[language={C++}] +vector( const vector & r ) + // アロケーターのコピー + : alloc( traits::select_on_container_copy_construction(r.alloc) ) +{ + // コピー処理 +} +\end{lstlisting} + +残りのコピー処理を実装していこう。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + コピー元の要素数を保持できるだけのストレージを確保 +\item + コピー元の要素をコピー構築 +\end{enumerate} + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +vector( const vector & r ) + : alloc( traits::select_on_container_copy_construction( r.alloc ) ) +{ + // コピー元の要素数を保持できるだけのストレージを確保 + reserve( r.size() ) ; + // コピー元の要素をコピー構築 + // destはコピー先 + // [src, last)はコピー元 + for ( auto dest = first, src = r.begin(), last = r.end() ; + src != last ; ++dest, ++src ) + { + construct( dest, *src ) ; + } + last = first + r.size() ; +} +\end{lstlisting} + +\hypersubsection{ch320802}{コピー代入演算子} +\index{こぴだいにゆうえんざんし@コピー代入演算子} + +コピー代入演算子ではアロケーターのコピーをする必要はない。ただし自分自身への代入への対応が必要だ。そして、コピー代入のコピー先とコピー元の要素数が同じであるとは限らない。 + +コピー先とコピー元の要素数が同じである場合、単に要素にコピー代入をすればよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3} ; + std::vector w(3) ; + w = v ; +} +\end{lstlisting} + +これは単に以下のようなコードを実行したものと同じになる。 + +\begin{lstlisting}[language={C++}] +w[0] = v[0] ; +w[1] = v[1] ; +w[2] = v[2] ; +\end{lstlisting} + +要素数が違う場合、2通りの場合がある。 + +コピー先がコピー元の要素数以上の予約数を持っている場合、有効な要素についてはコピー代入され、それ以降の要素はコピー構築される。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + // 要素数5 + std::vector v = {1,2,3,4,5} ; + // 要素数3 + std::vector w(3) ; + // 予約数5 + w.reserve(5) ; + w = v ; +} +\end{lstlisting} + +この場合、\texttt{w[0], w[1], w[2]}についてはそれぞれ\texttt{v[0], v[1], v[2]}からコピー代入される。\texttt{w[3],w[4]}はそれぞれ\texttt{v[3], v[4]}からコピー構築される。 + +コピー先がコピー元の要素数以上の予約数を持っていない場合、コピー元の要素数以上のストレージが予約される。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + // 予約数2だとする。 + std::vector w(2) ; + // 古いストレージが破棄され + // 新しいストレージが確保され + // コピー構築される + w = v ; +} +\end{lstlisting} + +このとき、コピー先の既存の要素をわざわざ新しいストレージにコピー構築する必要はない。なぜならば、既存の要素の値はもういらないからだ。 + +まとめよう。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 自分自身への代入であれば何もしない +\item + 要素数が同じならば要素ごとにコピー代入 +\item + それ以外の場合で、予約数が十分ならば有効な要素にはコピー代入、残りはコピー構築 +\item + それ以外の場合で、予約数が不十分ならば、現在の要素はすべて破棄して新たなストレージを確保してコピー構築 +\end{enumerate} + +\begin{lstlisting}[language={C++}] +vector & operator = ( const vector & r ) +{ + // 1. 自分自身への代入なら何もしない + if ( this == &r ) + return *this ; + + // 2. 要素数が同じならば + if ( size() == r.size() ) + { // 要素ごとにコピー代入 + std::copy( r.begin(), r.end(), begin() ) ; + } + // 3. それ以外の場合で + else + // 予約数が十分ならば、 + if ( capacity() >= r.size() ) + { + // 有効な要素はコピー + std::copy( r.begin(), r.begin() + r.size(), begin() ) ; + // 残りはコピー構築 + for ( auto src_iter = r.begin() + r.size(), src_end = r.end() ; + src_iter != src_end ; ++src_iter, ++last ) + { + construct( last, *src_iter ) ; + } + } + // 4. 予約数が不十分ならば + else + { + // 要素をすべて破棄 + destroy_all() ; + // 予約 + reserve( r.size() ) ; + // コピー構築 + for ( auto src_iter = r.begin(), src_end = r.end(), dest_iter = begin() ; + src_iter != src_end ; ++src_iter, ++dest_iter, ++last ) + { + construct( dest_iter, *src_iter ) ; + } + } + return *this ; +} +\end{lstlisting} + diff --git a/TeX/036-move.tex b/TeX/036-move.tex new file mode 100644 index 0000000..206b9ca --- /dev/null +++ b/TeX/036-move.tex @@ -0,0 +1,278 @@ +\hyperchapter{ch33}{ムーブ}{ムーブ} + +\hypersection{ch3301}{ムーブの使い方} + +ムーブ(move)\index{むぶ@ムーブ}とはコピー(copy)と対になる概念だ。ムーブはちょっと特殊なコピーと考えることもできる。コピーが値をコピー(複製)するのに対し、ムーブは値をムーブ(移動)させる。 + +コピーの仕方を振り返ってみよう。コピーにはコピー構築とコピー代入がある。 + +\begin{lstlisting}[language={C++}] +T source ; +// コピー構築 +T a = source ; +T b( source ) ; +T c{ source ) ; + +T e ; +// コピー代入 +e = source ; +\end{lstlisting} + +コピーにはコピー先とコピー元がある。 + +\begin{lstlisting}[language={C++}] +std::vector v = {1,2,3} ; +std::vector destination = source ; +// destinationは{1,2,3} +\end{lstlisting} + +一般にコピー後のコピー先の値はコピー元の値と等しくなることが期待されている。 + +ムーブはコピーと似ている。コピーをするときに、ムーブ元の変数を\texttt{source}を\texttt{std::move(source)}のように標準ライブラリ\texttt{std::move}\index{move@\texttt{move}}に渡してその戻り値をコピー元の値とすることでムーブになる。ムーブにもコピーと同様にムーブ構築\index{むぶ@ムーブ!こうちく@構築}とムーブ代入\index{むぶ@ムーブ!だいにゆう@代入}がある。 + +\begin{lstlisting}[language={C++}] +T source ; +// ムーブ構築 +T a = std::move(source) ; +T b( std::move(source) ) ; +T c{ std::move(source) ) ; + +T e ; +// ムーブ代入 +e = std::move(source) ; +\end{lstlisting} + +ムーブにもムーブ先\index{むぶ@ムーブ!さき@〜先}とムーブ元\index{むぶ@ムーブ!もと@〜元}がある。 + +\begin{lstlisting}[language={C++}] +std::vector v = {1,2,3} ; +// destinationはムーブ先 +// sourceはムーブ元 +std::vector destination = std::move(source) ; +// destinationの値は{1,2,3} +// sourceの値はわからない +\end{lstlisting} + +コピーと同じく、ムーブ後のムーブ先の値は、ムーブ前のムーブ元の値と等しくなる。 + +ムーブ後のムーブ元の値はわからない。なぜわからないかというと、値を移動しているからだ。 + +ムーブのコストはコピーとまったく同じか、コピーよりも低くなる。 + +ムーブはムーブ元の値をムーブ後に使わない場合に、コピーの代わりに使うことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + + std::vector w = {1,2,3,4,5} ; + // ムーブ + v = std::move(w) ; + // このあとwは使えない + + std::for_each( std::begin(v), std::end(v), + []( auto x ){ + std::cout << x ; + } ) ; +} +\end{lstlisting} + +実際には、上記のコードはムーブ後に変数\texttt{w}を使っている。\texttt{main}関数のスコープを抜けるときに\texttt{w}が破棄されるが、そのときにデストラクターが実行される。 + +C++の標準ライブラリはムーブ後の状態について、その値は「妥当だが未規定の状態」になる。 + +なのでこの場合でもデストラクターを正常に呼び出すことはできる。このとき、\texttt{w.size()}が返す値はわからない。ただし、\texttt{w.resize(n)}\index{resize@\texttt{resize}}を呼び出すと\texttt{n}個の要素を持つようになる。この結果、再び使うこともできるようになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + std::vector w = {1,2,3,4,5} ; + v = std::move(w) ; + // 要素数5 + w.resize(5) ; + // 妥当に使える + w[0] = 1 ; +} +\end{lstlisting} + +\hypersection{ch3302}{ムーブの中身} + +ムーブはいったい何をしているのか。ムーブの実装方法を理解するためには、\texttt{rvalueセマンティクス}\index{rvalueせまんていくす@\texttt{rvalue}セマンティクス}と\texttt{値カテゴリー}\index{あたいかてごり@値カテゴリー}とテンプレートの\texttt{フォワードリファレンス}\index{ふおわどりふあれんす@フォワードリファレンス}\index{てんぷれと@テンプレート!ふおわどりふあれんす@フォワードリファレンス}という難しいC++の機能を理解しなければならない。この機能は次の章から解説するが、その機能を学ぶ動機づけにムーブが何をしているのかを具体的に学ぼう。 + +\texttt{int}や\texttt{double}といった単なるバイト列で表現された値だけで表現できる基本型のオブジェクトの場合、ムーブというのはコピーと何ら変わらない。単に値を表現するバイト列をコピーするだけだ。 + +\begin{lstlisting}[language={C++}] +int a = 0 ; +// コピー +int b = a ; +// ムーブ +// 中身は単なるコピー +int c = std::move(a) ; +\end{lstlisting} + +そのため、\texttt{int}や\texttt{double}のムーブでは、ムーブ後もムーブ元のオブジェクトをそのまま使うことができるし、値も変わらない。 + +\begin{lstlisting}[language={C++}] +int a = 123 +int b = std::move(a) ; +// 123 +std::cout << a ; +a = 456 ; +\end{lstlisting} + +生のポインターのムーブもコピーと同じだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int object { } ; +int * source = object ; +// 中身は単なるコピー +int * destination = std::move(source) ; +\end{lstlisting} + +クラスはどうか。クラスはデフォルトのコピーコンストラクターとコピー代入演算子を生成するように、デフォルトのムーブコンストラクターとムーブ代入演算子を生成する。これはコピーと同じく、メンバーごとにムーブを行う。 + +以下のように書くと、 +\begin{lstlisting}[language={C++}] +struct X +{ + int x ; + int y ; + int z ; +} ; + +int main() +{ + X a{1,2,3} ; + X b ; + b = std::move(a) ; +} +\end{lstlisting} +以下のように書いたものとほぼ同じになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + X a{1,2,3} ; + X b ; + b.x = std::move(a.x) ; + b.y = std::move(a.y) ; + b.z = std::move(a.z) ; +} +\end{lstlisting} + +この場合のムーブは単なるコピーなので、実際には以下のように書くのと同じだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + X a{1,2,3} ; + X b ; + b = a ; +} +\end{lstlisting} + +C++の基本型とクラスのデフォルトのムーブの実装は、単なるコピーと同じだ。コピーと同じなのでムーブ後の値もそのまま使うことができる。 + +ではなぜコピーとムーブが区別され、ムーブ後のオブジェクトは使えないのか。C++ではコピーとムーブが区別されているので、自作のクラスはコピーとムーブで別の実装をすることができる。 + +ムーブ後のオブジェクトは使えない状態になるということは、ムーブ後のオブジェクトの値はどうなってもいいということだ。 + +\texttt{std::vector}のようなクラスは動的メモリー確保をしてポインターでストレージを参照している。自作の\texttt{vector}にコピーを実装するときは、コピー先でも動的メモリー確保をして要素を1つずつコピーしなければならないことを学んだ。 + +とても簡単な、\texttt{T}型の配列を確保する\texttt{dynamic\_array}\,を考えてみよう。 + +\begin{lstlisting}[language={C++}] +template < typename T > +class dynamic_array +{ +private : + T * first ; + T * last ; +public : + dynamic_array( std::size_t size = 0 ) + : first( new T[size]), last( first + size ) + { } + ~dynamic_array() + { delete[] first ; } + + // コピーコンストラクター + dynamic_array( const dynamic_array & r ) ; +} ; +\end{lstlisting} + +このクラスのコピーコンストラクターの定義は以下のように書ける。 + +\begin{lstlisting}[language={C++}] +template < typename T > +dynamic_array::dynamic_array( const dynamic_array & r ) + : first( new T[r.size()] ), last( first + r.size() ) +{ + std::copy( r.begin(), r.end(), begin() ) ; +} +\end{lstlisting} + +これはコストがかかる。以下のようにすればコストがかからないがなぜできないのだろう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] + < typename T > +dynamic_array::dynamic_array( const dynamic_array & r ) + : first( r.first ), last( r.last ) +{ + // 何もしない +} +\end{lstlisting} + +コピーの章でも学んだように、この実装ではコピー先とコピー元が同じポインターを所有してしまうために、デストラクターが実行されるときに同じポインターが2回\texttt{delete}されてしまう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + dynamic_array source(10) ; + dynamic_array destination = source ; + // destinationに対してデストラクターが実行される + // sourceに対してデストラクターが実行される +} +\end{lstlisting} + +ならば、コピー元からポインターの所有権を奪ってしまえばいいのではないだろうか。 + +\begin{lstlisting}[language={C++}] + < typename T > +dynamic_array::dynamic_array( dynamic_array & r ) + : first( r.first ), last( r.last ) +{ + // コピー元を変更 + r.first = nullptr ; + r.last = nullptr ; +} +\end{lstlisting} + +引数が\texttt{const}ではないことに注目しよう。リファレンス型の引数を変更するには、\texttt{const}にはできない。 + +このコピーコンストラクターはコピー元を変更する。\texttt{delete式}は\texttt{nullptr}に対して適用した場合、何もしないことが保証されている。そのため、この場合にデストラクターでnullポインターのチェックは必要がない。 + +このコピーコンストラクターはとてもコストが低いが、このようなコピーの実装はユーザーが期待していない。この実装ではコピー後にコピー元が使えなくなってしまうからだ。 + +例えば、以下のコードが動かないとしたらどうだろう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + std::vector w = v ; + + // これがエラーだとしたら? + v[0] = 0 ; +} +\end{lstlisting} + +C++ではコピーはコピー元を変更しないという慣習がある。このような慣習はすべてC++の標準規格で定められている。 + +このため、C++はコピーのほかにムーブを定めている。ムーブを使うにはムーブ元の変数\texttt{x}を\texttt{std::move(x)}のようにしてコピーする。\texttt{std::move}はこのコピーはコピーではなくムーブしてもよいというヒントになる。 + +ムーブを実装するためには、まず基礎知識として次の章で学ぶ\texttt{rvalue}リファレンス、値カテゴリー、テンプレートのフォワードリファレンスの深い理解が必要になる。 diff --git a/TeX/037-rvalue-reference.tex b/TeX/037-rvalue-reference.tex new file mode 100644 index 0000000..f8092b0 --- /dev/null +++ b/TeX/037-rvalue-reference.tex @@ -0,0 +1,632 @@ +\ifTombow\addtocontents{toc}{\protect\newpage}\fi +\hyperchapter{ch34}{rvalueリファレンス}{rvalueリファレンス} +\index{rvalueりふあれんす@\texttt{rvalue}リファレンス} + +\hypersection{ch3401}{概要} + +いままで使っているリファレンスは、正式には\texttt{lvalue}リファレンスという名前がついている。これは\texttt{lvalue}へのリファレンスという意味だ。\texttt{lvalue}へのリファレンスがあるからには、\texttt{lvalue}ではないリファレンスがあるということだ。C++には\texttt{rvalue}へのリファレンスがある。これを\texttt{rvalue}リファレンスという。 + +この章で説明する内容はとても難しい。完全に理解するためには、何度も読み直す必要があるだろう。 + +\hypersection{ch3402}{rvalueリファレンスの宣言} +\index{rvalueりふあれんす@\texttt{rvalue}リファレンス!せんげん@宣言} + +\texttt{T}型への\texttt{lvalue}型リファレンス型は\texttt{T \&}\,と書く。 + +\begin{lstlisting}[language={C++}] +T & lvalue_reference = ... ; +\end{lstlisting} + +\texttt{T}型への\texttt{rvalue}リファレンス型は\texttt{T \&\&}\,と書く。 +\index{T \&\&@\texttt{T \&\&}}\index{rvalueりふあれんす@\texttt{rvalue}リファレンス!T \&\&@\texttt{T \&\&}} + +\begin{lstlisting}[language={C++}] +T && rvalue_reference = ... ; +\end{lstlisting} + +\texttt{lvalue}リファレンスは\texttt{lvalue}で初期化する。\texttt{rvalue}リファレンスは\texttt{rvalue}で初期化する。 + +\texttt{lvalue}とは名前付きのオブジェクトや戻り値の型としての\texttt{lvalue}リファレンスのことだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int object { } ; +int & f() { return object ; } + +int main() +{ + // lvalueリファレンス + int & a = object ; + int & b = f() ; +} +\end{lstlisting} + +ここで、式\texttt{object}や式\texttt{f()}を評価した結果は\texttt{lvalue}だ。 + +\texttt{rvalue}とは、名前なしのオブジェクトや計算結果の一時オブジェクト、戻り値の型としての\texttt{rvalue}リファレンスのことだ。 + +\begin{lstlisting}[language={C++}] +int && g() { return 0 ; } +int h() { return 0 ; } + +int main() +{ + // rvalueリファレンス + int && a = 0 ; + int && b = 1 + 1 ; + int && c = g() ; + int && d = h() ; +} +\end{lstlisting} + +ここで、式\texttt{0}、式\texttt{1 + 1}、式\texttt{g()}を評価した結果は\texttt{rvalue}だ。 + +\texttt{rvalue}リファレンスを\texttt{lvalue}で初期化することはできない。 + +\begin{lstlisting}[language={C++}] +int object { } ; +int & f() { return object ; } + +int main() +{ + // すべてエラー + int && a = object ; + int && b = f() ; +} +\end{lstlisting} + +\texttt{lvalue}リファレンスを\texttt{rvalue}で初期化することはできない。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int && g() { return 0 ; } +int h() { return 0 ; } + +int main() +{ + // すべてエラー + int & a = 0 ; + int & b = 1 + 1 ; + int & c = g() ; + int & d = h() ; +} +\end{lstlisting} + +リファレンスを初期化することを、リファレンスはリファレンス先を束縛\index{そくばく@束縛}するという。\texttt{lvalue}リファレンスは\texttt{lvalue}を束縛する。\texttt{rvalue}リファレンスは\texttt{rvalue}を束縛する。 + +ただし、\texttt{const}な\texttt{lvalue}リファレンスは\texttt{rvalue}を束縛することができる。 + +\begin{lstlisting}[language={C++}] +int && g() { return 0 ; } + +int main() +{ + // OK、constなlvalueリファレンス + const int & a = 0 ; + const int & b = 1 + 1 ; + const int & c = g() ; +} +\end{lstlisting} + +\texttt{rvalue}リファレンス自体は\texttt{lvalue}だ。なぜならば\texttt{rvalue}リファレンスはオブジェクトに名前を付けて束縛するからだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // rvalueリファレンス + int && a = 0 ; + // OK、rvalueリファレンスaはlvalue + int & b = a ; + // エラー、 rvalueリファレンスaはrvalueではない + int && b = a ; +} +\end{lstlisting} + +\clearpage +\hypersection{ch3403}{値カテゴリー} +\index{あたいかてごり@値カテゴリー} + +\texttt{lvalue}と\texttt{rvalue}とは何か。もともと\texttt{lvalue}\index{lvalue@\texttt{lvalue}}とは左辺値(left--hand value)\index{さへんち@左辺値}、\texttt{rvalue}\index{rvalue@\texttt{rvalue}}とは右辺値(right--hand value)\index{うへんち@右辺値}という語源を持っている。これはまだC言語すらなかったはるか昔から存在する用語で、代入式の左辺に書くことができる値を\texttt{lvalue}、右辺に書くことができる値を\texttt{rvalue}と読んでいたことに由来する。 + +\begin{lstlisting}[language={C++}] +lvalue = rvalue ; +\end{lstlisting} + +例えば、\texttt{int}型の変数\texttt{x}は代入式の左辺に書くことができるから\texttt{lvalue}、整数リテラル\texttt{0}は右辺に書くことができるから\texttt{rvalue}といった具合だ。 + +\begin{lstlisting}[language={C++}] +int x ; +x = 0 ; +\end{lstlisting} + +C++では\texttt{lvalue}と\texttt{rvalue}をこのような意味では使っていない。 + +\texttt{lvalue}と\texttt{rvalue}を理解するには、値カテゴリーを理解しなければならない。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 式(expression)とは\texttt{glvalue}か\texttt{rvalue}である。 +\item + \texttt{glvalue}とは\texttt{lvalue}か\texttt{xvalue}である。 +\item + \texttt{rvalue}とは\texttt{prvalue}か\texttt{xvalue}である。 +\end{enumerate} + +この関係を図示すると以下のようになる。 + +\begin{figure}[htbp] + \centering + \includegraphics[scale=1.0]{fig/fig37-01.eps} + \label{fig:37-01} +\end{figure} + +\hypersubsection{ch340301}{lvalue} +\index{lvalue@\texttt{lvalue}} + +\texttt{lvalue}はすでに説明したとおり名前付きのオブジェクトのことだ。 + +\begin{lstlisting}[language={C++}] +// lvalue +int object ; +int & ref = object ; +\end{lstlisting} + +通常使うほとんどのオブジェクトは\texttt{lvalue}になる。 + +\hypersubsection{ch340302}{prvalue} +\index{prvalue@\texttt{prvalue}} + +\texttt{prvalue}は純粋な\texttt{rvalue}(pure rvalue)のことだ。つまり、名前なしのオブジェクトや計算結果の一時オブジェクトのことだ。 + +\begin{lstlisting}[language={C++}] +int f() { return 0 ; } + +// prvalue +0 ; +1 + 1 ; +f() ; +\end{lstlisting} + +ほとんどの\texttt{prvalue}は式を評価するときに自動的に生成され、自動的に破棄されるので、あまり意識することはない。 + +関数の戻り値の型がリファレンスではない場合、一時オブジェクトが生成される。 + +\begin{lstlisting}[language={C++}] +struct X { } ; +X f() ; +\end{lstlisting} + +演算子も関数の一種なので、 +\begin{lstlisting}[language={C++}] +auto result = x + y + z ; +\end{lstlisting} +のような式がある場合、まず\texttt{x + y}が評価され、その結果が一時オブジェクトとして返される。その一時オブジェクトを仮に\texttt{temp}とすると、\texttt{temp + z}が評価され、また一時オブジェクトが生成され、変数\texttt{result}に代入される。 + +式文全体を評価し終わったあとに、一時オブジェクトは自動的に破棄される。 + +一時オブジェクトは自動的に生成され、自動的に破棄される。ここがとても重要な点だ。これは次の章で説明するムーブセマンティクスに関わってくる。 + +\hypersubsection{ch340303}{xvalue} +\index{xvalue@\texttt{xvalue}} + +\texttt{xvalue}とは寿命が尽きかけている\texttt{lvalue}(eXpiring lvalue)のことだ。\texttt{xvalue}は\texttt{lvalue}や\texttt{prvalue}から変換することで発生する。 + +\texttt{xvalue}となる値は以下のような場合だ。 + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{戻り値の型がオブジェクトの型への\texttt{rvalue}リファレンスである関数の呼び出しの結果} + +\begin{lstlisting}[language={C++}] +int && f() { return 0 ; } + +int main() +{ + // xvalue + int && r = f() ; +} +\end{lstlisting} + +%\vskip 1.0zw +\ifTombow\pagebreak\fi +\noindent +\(\bullet\) \textsf{オブジェクトの型への\texttt{rvalue}リファレンスへのキャスト} + +\begin{lstlisting}[language={C++}] +int main() +{ + int object{} ; + // xvalue + int && r = static_cast(object) ; +} +\end{lstlisting} + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{\texttt{xvalue}配列\index{xvalueはいれつ@\texttt{xvalue}配列}への添字操作} + +\begin{lstlisting}[language={C++}] +int main() +{ + int a[3] = {1,2,3} ; + int && r = static_cast(a)[0] ; +} +\end{lstlisting} + +\texttt{xvalue}配列というのは配列のオブジェクトを配列への\texttt{rvalue}リファレンス型にキャストすると得られる。\texttt{xvalue}配列への添字操作の結果は\texttt{xvalue}だ。 + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{\texttt{xvalue}なクラスのオブジェクトへのリファレンスではない非\texttt{static}データメンバーへのアクセス} + +\begin{lstlisting}[language={C++}] +struct X { int data_member ; } ; + +int main() +{ + X x{} ; + int && r = static_cast(x).data_member ; +} +\end{lstlisting} + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{式\,\texttt{.*}\,で最初のオペランドが\texttt{xvalue}で次のオペランドがデータメンバーへのポインターの場合} + +\begin{lstlisting}[language={C++}] +struct X { int data_member ; } ; + +int main() +{ + X x{} ; + int && r = static_cast(x).*&X::data_member ; +} +\end{lstlisting} + +これも配列と似ていて、\texttt{xvalue}のクラスオブジェクトに対するメンバーへのポインター経由でのメンバーの参照結果は\texttt{xvalue}になるということだ。 + +%\vskip 1.0zw +\ifTombow\pagebreak\fi +重要なのは最初の2つだ。残りは覚える必要はない。重要なのは、\texttt{xvalue}とは、\texttt{lvalue}か\texttt{prvalue}から変換した結果発生するものだ。 + +\hypersubsection{ch340304}{rvalue} +\index{rvalue@\texttt{rvalue}} + +\texttt{prvalue}と\texttt{xvalue}を合わせて、\texttt{rvalue}という。\texttt{rvalue}リファレンスというのは、\texttt{rvalue}でしか初期化できない。\texttt{rvalue}というのは\texttt{prvalue}か\texttt{xvalue}のどちらかだ。 + +\texttt{lvalue}は\texttt{xvalue}に変換できるので、結果として\texttt{rvalue}に変換できることになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // lvalueなオブジェクト + int lvalue { } ; + + // OK、lvalueリファレンスはlvalueで初期化できる + int & l_ref = lvalue ; + + // OK、rvalueリファレンスはrvalueで初期化できる + // rvalueリファレンスにキャストした結果はrvalue + int && r_ref = static_cast(lvalue) ; +} +\end{lstlisting} + +\texttt{lvalue}はそのままでは\texttt{rvalue}ではないが、\texttt{xvalue}に変換すれば\texttt{rvalue}になる。 + +\texttt{prvalue}はもともと\texttt{rvalue}である。 + +この性質は次の章で説明するムーブセマンティクスで利用する。 + +\hypersubsection{ch340305}{glvalue} +\index{glvalue@\texttt{glvalue}} + +\texttt{glvalue}は一般的な\texttt{lvalue}(generalized lvalue)という意味だ。\texttt{glvalue}とは、\texttt{lvalue}か\texttt{xvalue}のことだ。 + +\texttt{lvalue}から変換した\texttt{xvalue}はもともと\texttt{lvalue}だったのだから、\texttt{glvalue}となるのも自然だ。\texttt{xvalue}に変換した\texttt{prvalue}は\texttt{glvalue}になれる。 + +この性質はムーブセマンティクスで利用する。 + +\clearpage +\hypersection{ch3404}{rvalueリファレンスのライブラリ} + +\hypersubsection{ch340401}{std::move} +\index{move@\texttt{move}} + +\texttt{std::move(e)}は値\texttt{e}を\texttt{xvalue}にするための標準ライブラリだ。\texttt{std::move(e)}は値\texttt{e}の型\texttt{T}への\texttt{rvalue}リファレンス型にキャストしてくれるので、\texttt{xvalue}になる。そして\texttt{xvalue}は\texttt{rvalue}だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int lvalue { } ; + int && r = std::move(lvalue) ; +} +\end{lstlisting} + +これは以下のように書いたものと同じようになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int lvalue { } ; + int && r = static_cast(lvalue) ; +} +\end{lstlisting} + +\hypersubsection{ch340402}{std::moveの実装} +\index{move@\texttt{move}!じつそう@実装} + +\texttt{std:move(e)}の実装は少し難しい。根本的には、式\texttt{e}のリファレンスではない型\texttt{T}に対して、\texttt{static\_cast(e)}をしているだけだ。 + +すると以下のような実装だろうか。 + +\begin{lstlisting}[language={C++}] +template < typename T > +T && move( T & t ) noexcept +{ + return static_cast(t) ; +} +\end{lstlisting} + +この実装は\texttt{lvalue}を\texttt{xvalue}に変換することはできるが、\texttt{rvalue}(\texttt{prvalue}と\texttt{xvalue})を\texttt{xvalue}に変換することはできない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // エラー、 prvalueを変換できない + int && r1 = move(0) ; + + int lvalue { } ; + // エラー、 xvalueをxvalueに変換できない + int && r2 = move(move(lvalue)) ; +} +\end{lstlisting} + +\texttt{rvalue}は\texttt{rvalue}リファレンスで受け取れるので、\texttt{lvalue}リファレンスを関数の引数として受け取る\texttt{move}のほかに、\texttt{rvalue}リファレンスを関数の引数として受け取る\texttt{move}を書くとよい。 + +すると以下のように書けるだろうか。 + +\begin{lstlisting}[language={C++}] +// lvalueリファレンス +template < typename T > +T && move( T & t ) noexcept +{ + return static_cast(t) ; +} + +// rvalueリファレンス +template < typename T > +T && move( T && t ) noexcept +{ + return static_cast(t) ; +} +\end{lstlisting} + +しかしこれでは関数の本体の中身がまったく同じ関数が2つできてしまう。もっと複雑な関数を書くときにこのようなコードの重複があると、ソースコードの修正が難しくなる。せっかくテンプレートを使っているのにこれでは意味がない。 + +\hypersubsection{ch340403}{フォワーディングリファレンス} +\index{ふおわでいんぐりふあれんす@フォワーディングリファレンス} + +C++のテンプレートはコードの重複を省くためにある。そのため、C++ではテンプレートパラメーターへの\texttt{rvalue}リファレンスを関数の仮引数として取る場合を、フォワーディングリファレンス(forwarding reference)として、特別に\texttt{lvalue}でも\texttt{rvalue}でも受け取れるようにしている。 + +\begin{lstlisting}[language={C++}] +// T &&はフォワーディングリファレンス +template < typename T > +void f( T && t ) ; +\end{lstlisting} + +このような関数テンプレートの仮引数\texttt{t}に実引数として\texttt{rvalue}を渡すと、\texttt{T}は\texttt{rvalue}の型となり、結果として\texttt{t}の型は\texttt{T \&\&}\,になる。 + +\begin{lstlisting}[language={C++}] +// Tはint +f(0) ; +\end{lstlisting} + +もし実引数として型\texttt{U}の\texttt{lvalue}を渡すと、テンプレートパラメーター\texttt{T}が\texttt{U \&}\,となる。そして、テンプレートパラメーター\texttt{T}に対するリファレンス宣言子(\texttt{\&}, \texttt{\&\&})は単に無視される。 + +\begin{lstlisting}[language={C++}] +int lvalue{} ; +// Tはint & +// T &&はint & +f(lvalue) ; +\end{lstlisting} + +ここで、関数テンプレート\texttt{f}のテンプレートパラメーター\texttt{T}は\texttt{int \&}\,となる。この\texttt{T}にリファレンス宣言子を\texttt{T \&}\,や\texttt{T \&\&}\,のように使っても、単に無視されて、\texttt{T \&}\,となる。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void f( T && t ) +{ + using A = T & ; + using B = T && ; +} + +int main() +{ + // prvalue + f(0) ; + int lvalue{} ; + // lvalue + f(lvalue) ; +} +\end{lstlisting} + +\texttt{f(0)}は\texttt{prvalue}を渡している。この場合、\texttt{T}の型は\texttt{int}となる。\texttt{A}は\texttt{int \&}、\texttt{B}は\texttt{int \&\&}\,となる。 + +\texttt{f(lvalue)}は\texttt{lvalue}を渡している。この場合、\texttt{T}の型は\texttt{int \&}\,となる。この場合の\texttt{T}に\,\texttt{\&}\,や\,\texttt{\&\&}\,を付けても無視される。なので、\texttt{A}, \texttt{B}の型はどちらも\texttt{int \&}\,になる。 + +したがって、以下のように書くと\texttt{move}は\texttt{lvalue}も\texttt{rvalue}も受け取ることができる。 + +\begin{lstlisting}[language={C++}] +// lvalueもrvalueも受け取ることができるmove +template < typename T > +T && move( T && t ) noexcept +{ + return static_cast(t) ; +} +\end{lstlisting} + +ただし、この実装にはまだ問題がある。この\texttt{move}に\texttt{lvalue}を渡した場合、\texttt{lvalue}の型を\texttt{U}とすると、テンプレートパラメーター\texttt{T}は\texttt{U \&}\,になる。 + +\begin{lstlisting}[language={C++}] +U lvalue{} ; +// TはU & +move( lvalue ) ; +\end{lstlisting} + +テンプレートパラメーター名\texttt{T}がリファレンスのとき、\texttt{T}にリファレンス宣言子\,\texttt{\&\&}\,を付けても単に無視されることを考えると、上の\texttt{move}に\texttt{int \&}\,型の\texttt{lvalue}が実引数として渡されたときは、以下のように書いたものと等しくなる。 + +\begin{lstlisting}[language={C++}] +int & move( int & t ) noexcept +{ + return static_cast(t) ; +} +\end{lstlisting} + +\texttt{move(e)}は\texttt{e}が\texttt{lvalue}であれ\texttt{rvalue}であれ、\texttt{xvalue}にする関数だ。そのためには、\texttt{rvalue}リファレンスにキャストしなければならない。テンプレートではフォーワーディングリファレンスという例外的な仕組みによって\texttt{lvalue}も\texttt{rvalue}も\texttt{T \&\&}\,で受け取れるが、\texttt{lvalue}を受け取ったときには\texttt{T \&\&}\,が\texttt{lvalue}リファレンスになってしまうのでは、\texttt{xvalue}にキャストできない。 + +この問題は別のライブラリによって解決できる。 + +\hypersubsection{ch340404}{std::remove\texttt{\_}reference\texttt{\_}t} +\index{remove\_reference\_t@\texttt{remove\_reference\_t}} + +\texttt{std::remove\_reference\_t}\,は\texttt{T}型からリファレンス型を除去してくれるライブラリだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // int + using A = std::remove_reference_t ; + // int + using B = std::remove_reference_t ; + // int + using C = std::remove_reference_t ; +} +\end{lstlisting} + +ということは、これとリファレンス宣言子を組み合わせると、どのような型がテンプレート実引数に渡されても\texttt{rvalue}リファレンスにできる。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void f() +{ + using RT = std::remove_reference_t && ; +} +\end{lstlisting} + +\texttt{add\_pointer\_t/remove\_pointer\_t}があるように、\texttt{remove\_reference\_t}にも対となるリファレンスを追加するライブラリが存在する。ただしリファレンスには\texttt{lvalue}リファレンスと\texttt{rvalue}リファレンスがあるので、それぞれ\texttt{std::add\_lvalue\_reference\_t}\,\index{add\_lvalue\_reference\_t@\texttt{add\_lvalue\_reference\_t}}、\texttt{std::add\_rvalue\_reference\_t}\,\index{add\_rvalue\_reference\_t@\texttt{add\_rvalue\_reference\_t}}となっている。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + // int & + using A = std::add_lvalue_reference_t ; + // int && + using B = std::add_rvalue_reference_t ; +} +\end{lstlisting} + +\hypersubsection{ch340405}{std::moveの正しい実装} +\index{move@\texttt{move}} + +\texttt{std::remove\_reference\_t}\,を使うと、\texttt{move}は以下のように書ける。 + +\begin{lstlisting}[language={C++}] +template < typename T > +std::remove_reference_t && move( T && t ) noexcept +{ + return static_cast< std::remove_reference_t && >(t) ; +} +\end{lstlisting} + +\hypersubsection{ch340406}{std::forward} +\index{forward@\texttt{forward}} + +テンプレートパラメーターに\texttt{rvalue}リファレンス宣言子を使うと\texttt{lvalue}も\texttt{rvalue}も受け取れる。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void f( T && t ) { } + +int main() +{ + int lvalue{} ; + f(lvalue) ; + f(0) ; +} +\end{lstlisting} + +この関数\texttt{f}から別の関数\texttt{g}に値を渡したい場合を考えよう。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void g( T && t ) { } + +template < typename T > +void f( T && t ) +{ + g(t) ; +} +\end{lstlisting} + +このとき、関数\texttt{f}に渡されたものが\texttt{lvalue}でも\texttt{rvalue}でも、関数\texttt{g}に渡される値は\texttt{lvalue}になってしまう。 + +なぜならば、名前付きの\texttt{rvalue}リファレンスに束縛されたオブジェクトは\texttt{lvalue}だからだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 名前付きのrvalueリファレンス + int && rvalue_ref = 0 ; + // これはlvalue + int & lvalue_ref = rvalue_ref ; +} +\end{lstlisting} + +なので、\texttt{g(t)}の\texttt{t}は\texttt{lvalue}となる。 + +ここで\texttt{rvalue}を渡すのは簡単だ。\texttt{std::move}を使えばいい。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void f( T && t ) +{ + g( std::move(t) ) ; +} +\end{lstlisting} + +ただし、これは\texttt{t}が\texttt{lvalue}のときも問答無用で\texttt{xvalue}にしてしまう。 + +\texttt{t}が\texttt{lvalue}ならば\texttt{lvalue}として、\texttt{rvalue}ならば\texttt{xvalue}として、渡された値カテゴリーのまま別の関数に渡したい場合、\texttt{std::forward(t)}が使える。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void f( T && t ) +{ + g( std::forward(t) ) ; +} +\end{lstlisting} + +\texttt{std::forward(t)}の\texttt{T}にはテンプレートパラメーター名を書く。こうすると、\texttt{t}が\texttt{lvalue}ならば\texttt{lvalue}リファレンス、\texttt{rvalue}ならば\texttt{rvalue}リファレンスが戻り値として返される。 + +\texttt{std::forward}の実装は以下のとおりだ。 + +\begin{lstlisting}[language={C++}] +template +constexpr +T && +forward(remove_reference_t& t) noexcept +{ return static_cast(t) ; } + +(@\ifTombow\pagebreak\fi@) +template +constexpr +T && +forward(remove_reference_t&& t) noexcept +{ return static_cast(t) ; } +\end{lstlisting} + +もし\texttt{std::forward(t)}に\texttt{lvalue}が渡された場合、上の\texttt{forward}が呼ばれる。その場合、\texttt{T}は\texttt{lvalue}リファレンスになっているはずなので\texttt{rvalue}リファレンス宣言子は無視され、\texttt{lvalue}リファレンスが戻り値の型になる。 + +\texttt{rvalue}が渡された場合、\texttt{rvalue}リファレンスが戻り値の型になる。 diff --git a/TeX/038-move-semantics.tex b/TeX/038-move-semantics.tex new file mode 100644 index 0000000..6c85b3b --- /dev/null +++ b/TeX/038-move-semantics.tex @@ -0,0 +1,385 @@ +\hyperchapter{ch35}{ムーブの実装}{ムーブの実装} + +ムーブ(move)\index{むぶ@ムーブ}とはコピー(copy)と対になる概念だ。ムーブというのはやや特殊なコピーとみなすこともできる。 + +ムーブの使い方とその内部の挙動についてはムーブの章で説明した。 + +実際に自作のクラスでムーブを実装するには、\texttt{rvalue}リファレンスの章で説明した\texttt{rvalue}リファレンス、値カテゴリー、テンプレートのフォワードリファレンスの詳細な理解が必要になる。 + +まだこの2つの章を読んでいない読者はこの章を理解する準備ができていない。一度だけしか読んでいない読者は完全に理解はできないだろうから、この章を読んだ後にもう一度立ち返って読み直すべきだ。 + +この章ではサンプルコードの簡略化のために、メンバー関数の定義をあたかもクラスの中で書いたかのように扱う。 + +例えば、 +\begin{lstlisting}[language={C++}] +template < typename T > +struct S { T x ; } ; +\end{lstlisting} +があり、このクラス\texttt{S}\,のコンストラクターを続いて +\begin{lstlisting}[language={C++}] +S( T const & x ) : x(x) { } +\end{lstlisting} +と書くことがある。これは実際には間違いで、正しくは以下のように書かなければならない。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +template < typename T > +struct S +{ + T x ; + // 宣言 + S( T const & ) ; +} ; +// 定義 +template < typename T > +S::S( T const & x ) : x(x) { } +\end{lstlisting} + +この章では煩わしいので簡略した書き方を使う。 + +\hypersection{ch3501}{コピーとムーブの判別} + +ムーブはムーブ元のオブジェクトを無効にする可能性がある。そのためムーブはムーブをしても安全な場合にしか行われない。 + +コピーはコピーコンストラクターとコピー代入演算子で実装する。 + +コピーは\texttt{lvalue}リファレンスを取る。通常は\texttt{const}な\texttt{lvalue}リファレンス型を使う。 + +\begin{lstlisting}[language={C++}] +struct X +{ + // コピーコンストラクター + X( const X & ) ; + // コピー代入演算子 + X & operator = ( const X & ) ; +} ; +\end{lstlisting} + +ムーブはムーブコンストラクター\index{むぶこんすとらくた@ムーブコンストラクター}とムーブ代入演算子\index{むぶだいにゆうえんざんし@ムーブ代入演算子}で実装する。 + +ムーブは\texttt{rvalue}リファレンスを取る。 + +\begin{lstlisting}[language={C++}] +struct X +{ + // ムーブコンストラクター + X( X && ) ; + // ムーブ代入演算子 + X & operator = ( X && ) ; +} ; +\end{lstlisting} + +コピーとムーブの区別は\texttt{lvalue}/\texttt{rvalue}リファレンスで行われる。なぜこれで動くのかというと、\texttt{rvalue}リファレンスで束縛できる値は、 +\ifTombow\pagebreak\fi +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 無名の一時オブジェクト(\texttt{prvalue}) +\item + 明示的に\texttt{rvalue}リファレンスにキャストされたオブジェクト(\texttt{xvalue}) +\end{enumerate} +のどちらかだからだ。 + +\begin{lstlisting}[language={C++}] +void f( const int & ) +{ + std::cout << "lvalue\n"s ; +} +void f( int && ) +{ + std::cout << "rvalue\n"s ; +} + +int main() +{ + int object { } ; + + f( object ) ; // lvalue + f( object + object ) ; // rvalue + f( []{ return object ; }() ) ; // rvalue + f( std::move(object) ) ; // rvalue +} +\end{lstlisting} + +変数名を書いた式\texttt{object}を評価した結果は\texttt{lvalue}なので\texttt{lvalue}と表示される。 + +変数を演算子\,\texttt{+}\,で加算する式\texttt{object + object}を評価した結果は\texttt{prvalue}なので\texttt{rvalue}と表示される。 + +戻り値の型が\texttt{int}型のラムダ式を呼び出す式\texttt{[]\{ return 0 ; \}()}を評価した結果は\texttt{prvalue}なので\texttt{rvalue}と表示される。 + +\texttt{std::move(object)}を評価した結果は\texttt{xvalue}なので\texttt{rvalue}と表示される。 + +\texttt{prvalue}は無名の一時オブジェクトなので、その値はすぐに破棄される。どうせ破棄されるのであれば、所有権を横取りしてもよい。 + +\texttt{xvalue}はユーザーが明示的に\texttt{rvalue}リファレンスにキャストした値だ。明示的に\texttt{rvalue}リファレンスにキャストしたということは、ユーザーはその値について、それ以降興味がないという意思を示したことになる。なので、そのような値からは所有権を横取りしてもよい。 + +特殊なルールとして、関数のローカル変数をオペランドに指定した\texttt{return}文はムーブをする可能性がある。 + +\begin{lstlisting}[language={C++}] +std::vector f() +{ + std::vector v ; + v.push_back(1) ; + v.push_back(2) ; + v.push_back(3) ; + // ムーブをする可能性がある + return v ; +} +\end{lstlisting} + +これは関数のローカル変数は\texttt{return}文が実行されたときには無効になるので、特別に存在するルールだ。そもそも、関数の\texttt{return}文はコピーもムーブもしない可能性がある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 先ほどの関数f + auto v = f() ; +} +\end{lstlisting} + +C++コンパイラーは以下のようにコードを変形することも許されているからだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v ; + v.push_back(1) ; + v.push_back(2) ; + v.push_back(3) ; +} +\end{lstlisting} + +\hypersection{ch3502}{ムーブの実装} +\index{むぶ@ムーブ!じつそう@実装} + +以下のようなクラスにムーブを実装しよう。 + +\begin{lstlisting}[language={C++}] +template < typename T > +class dynamic_array +{ +private : + T * first ; + T * last ; +public : + dynamic_array( std::size_t size = 0 ) + : first( new T[size]), last( first + size ) + { } + ~dynamic_array() + { delete[] first ; } +} ; +\end{lstlisting} + +ムーブは所有権の移動だ。所有権の移動は、単にポインターをコピーするだけで済む。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +dynamic_array source(10) ; +// ムーブ +dynamic_array destination = std::move(source) ; +\end{lstlisting} + +具体的な処理としては、 +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + ムーブ先へ所有権の移動 +\item + ムーブ元の所有権の放棄 +\end{enumerate} +となる。 + +\begin{lstlisting}[language={C++}] +// 1. ムーブ先へ所有権の移動 +destination.first = source.first ; +destination.last = source.last ; +// 2. ムーブ元の所有権の放棄 +source.first = nullptr ; +source.last = nullptr ; +\end{lstlisting} +とするのと同じだ。ストレージの所有権を\texttt{source}から\texttt{destination}に移動している。移動後、\texttt{source}の破棄に伴ってストレージが\texttt{delete}されないために、\texttt{source}のポインターの値は\texttt{nullptr}にする。移動後の\texttt{source}はもうストレージを所有していない。 + +\hypersubsection{ch350201}{ムーブコンストラクター} +\index{むぶこんすとらくた@ムーブコンストラクター} + +ムーブコンストラクターは以下のように実装できる。 + +\begin{lstlisting}[language={C++}] +dynamic_array( dynamic_array && r ) + // ムーブ先へ所有権の移動 + : first( r.first ), last( r.last ) +{ + // ムーブ元の所有権の放棄 + r.first = nullptr ; + r.last = nullptr ; +} +\end{lstlisting} + +\hypersubsection{ch350202}{ムーブ代入演算子} +\index{むぶだいにゆうえんざんし@ムーブ代入演算子} + +ムーブ代入の場合、すでにクラスのオブジェクトは構築されている。つまりムーブ先のクラスのオブジェクトはすでにストレージを所有しているかもしれない。 + +\begin{lstlisting}[language={C++}] +dynamic_array source(10) ; +dynamic_array destination(10) ; +// destinationはすでにストレージを所有 +destination = std::move(source) ; +\end{lstlisting} + +そのため、ムーブ代入演算子はまず自身が所有しているストレージを解放する必要がある。そのため、処理は以下のようになる。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + ムーブ先の所有権の解放 +\item + ムーブ先へ所有権の移動 +\item + ムーブ元の所有権の放棄 +\end{enumerate} + +\begin{lstlisting}[language={C++}] +// 1. ムーブ先の所有権の解放 +delete destination.first ; +// 2. ムーブ先へ所有権の移動 +destination.first = source.first ; +destination.last = source.last ; +// 3. ムーブ元の所有権の放棄 +source.first = nullptr ; +source.last = nullptr ; +\end{lstlisting} + +ただし、この実装は自分自身へのムーブ代入\index{むぶ@ムーブ!だいにゆう@代入}に対応できない。 + +\begin{lstlisting}[language={C++}] +destination = std::move( destination ) ; +\end{lstlisting} + +これは意図的なものだ。 + +一般的なムーブ代入、つまり、 +\begin{lstlisting}[language={C++}] +a = std::move(b) ; +\end{lstlisting} +というコードでムーブが実行された場合、変数\texttt{b}はその後使えない状態になる。もし\texttt{b}が\texttt{a}と同じである場合、\texttt{b}が使えない状態になるということは\texttt{a}も使えない状態になることはやむを得ないのが普通の挙動だ。 + +普通の挙動がコピー代入と異なるのは、歴史的経緯やムーブという破壊的な操作の性質から来るものだ。 + +C++の標準ライブラリは自分自身へのムーブ代入後のオブジェクトの状態について、「有効だが未規定の状態」としている。 + +たとえば、現在の主要なC++の実装では、\texttt{std::vector}で自分自身へのムーブ代入を行うと\texttt{clear()}が呼び出される。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,4,5} ; + v = std::move(v) ; + v.size() ; // 0 +} +\end{lstlisting} + +ムーブ代入でも、コピー代入のように何もしない実装にすることもできる。しかし、C++ではさまざまな議論の結果、ムーブ代入は自己代入を積極的に何もしない挙動にはしないということになっている。 + +自分自身へのムーブ代入は誤りである。 + +自分自身へのムーブ代入がうっかり発生する場合は、エイリアシングによるものだ。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void moving( T & a, T & b ) +{ + a = std::move(b) ; +} +\end{lstlisting} + +このコードが以下のように呼ばれた場合、変数\texttt{a}, \texttt{b}ともに同じオブジェクトを指しているので、自分自身へのムーブ代入になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::vector v = {1,2,3,} ; + moving( v, v ) ; +} +\end{lstlisting} + +そのため、素性のわからない間接参照を挟むオブジェクトをムーブ代入するときは、自分自身へのムーブ代入の回避が必要になる。 + +そのための方法は2つある。 + +1つはポインターを比較することだ。 + +\begin{lstlisting}[language={C++}] +template < typename T > +void moving( T & a, T & b ) +{ + if ( &a != &b ) + a = std::move(b) ; +} +\end{lstlisting} + +ただしこれは追加の比較が入るのでパフォーマンスに影響を与える。 + +もう1つは、ユーザーにエイリアシングを起こさないことを求めることだ。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[language={C++}] +// 仕様 +// この関数のa, bに同じオブジェクトを渡してはならない +// 渡した場合の挙動は未定義 +template < typename T > +void moving( T & a, T & b ) +{ + a = std::move(b) ; +} +\end{lstlisting} + +これはつまり、ユーザーに責任を押し付けるということだ。 + +ムーブ代入演算子は以下のように実装できる。 + +\begin{lstlisting}[language={C++}] +dynamic_array & operator = ( dynamic_array && r ) +{ + // ムーブ先のストレージの解放 + delete first ; + + // ムーブ先へ所有権の移動 + first = r.first ; + last = r.last ; + // ムーブ元の所有権の放棄 + r.first = nullptr ; + r.last = nullptr ; + + return *this ; +} +\end{lstlisting} + +\hypersection{ch3503}{デフォルトのムーブ} +\index{むぶ@ムーブ!でふおるとの@デフォルトの〜} + +クラスがムーブを実装しない場合、デフォルトのムーブが暗黙に定義される。 + +\begin{lstlisting}[language={C++}] +struct X +{ + int i {} ; + std::vector v ; +} ; + +int main() +{ + X a ; + X b ; + b = std::move(a) ; +} +\end{lstlisting} + +デフォルトのムーブはクラスのメンバーをそれぞれムーブする。 + +\begin{lstlisting}[language={C++}] +b.i = std::move(a.i) ; +b.v = std::move(a.v) ; +\end{lstlisting} + +デフォルトのコピーと似ている。 diff --git a/TeX/039-disable-copy.tex b/TeX/039-disable-copy.tex new file mode 100644 index 0000000..6014192 --- /dev/null +++ b/TeX/039-disable-copy.tex @@ -0,0 +1,95 @@ +\hypersection{ch3504}{コピーの禁止} +\index{こぴ@コピー!きんし@禁止} + +型によっては、コピーという概念が存在しないものがある。 + +例えばコピー不可能なシステムのリソースを扱うクラスだ。 + +具体的にはファイル、スレッド、プロセス、ネットワークソケットといったリソースだ。このようなリソースを管理するクラスを作ったとして、いったいコピーをどうすればいいのだろうか。 + +コピーできないクラスは\texttt{deleted定義}\index{deletedていぎ@\texttt{deleted}定義}を使ってコピーコンストラクターとコピー代入演算子を消すことができる。 + +\texttt{deleted定義}は関数の本体\,\texttt{\{...\}}\,の代わりに\,\texttt{= delete}\index{= delete@\texttt{= delete}}を書く。\texttt{deleted定義}されている関数を使うとエラーとなる。 + +\begin{lstlisting}[language={C++}] +struct X +{ + // コピーコンストラクター + X( const X & ) = delete ; + // コピー代入演算子 + X & operator = ( const X & ) = delete ; + + // デフォルトコンストラクター + X() { } + // ムーブコンストラクター + X ( X && ) { } + // ムーブ代入演算子 + X & operator = ( X && ) { } +} ; +\end{lstlisting} + +このようなクラス\texttt{X}は、コピーできない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // デフォルト構築できる + X a ; + // エラー、 コピーできない + X b = a ; + b = a ; + // OK、ムーブはできる。 + X c = std::move(a) ; +} +\end{lstlisting} + +クラス\texttt{X}はコピーコンスラクターとコピー代入演算子が\texttt{deleted定義}されているために、コピーをすることができない。 + +コピーやムーブが禁止されている型をデータメンバーに持つクラスは、デフォルトのコピーやムーブができなくなる。 + +\begin{lstlisting}[language={C++}] +// コピーできない型 +struct Uncopyable +{ + Uncopyable(){} + Uncopyable( const Uncopyable & ) = delete ; + Uncopyable & operator = ( const Uncopyable & ) = delete ; +} ; + +// デフォルトのコピーができない +struct X +{ + Uncopyable member ; +} ; +\end{lstlisting} + +\texttt{deleted定義}を使えばムーブも禁止できる。ただし、ムーブを禁止するというのは現実的にはあまり実用性がない。というのも、コピーはムーブでもあるので、コピーを提供している型はムーブとしてコピーを行えばムーブも提供できることになる。 + +\hypersection{ch3505}{5原則} + +C++には「5原則」\index{5げんそく@5原則}\index{C++!5げんそく@5原則}という作法がある。 + +5原則とは、 +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + コピーコンストラクター +\item + コピー代入演算子 +\item + ムーブコンストラクター +\item + ムーブ代入演算子 +\item + デストラクター +\end{enumerate} + +\hspace{2zw}このうちの1つを独自に定義したならば、残りの4つも定義すべきである。 + +\vskip 1.0zw +\noindent +というものだ。 + +なぜか。コピーやムーブを独自に定義するということは、デフォルトのコピーやムーブでは足りない何らかの処理をしたいはずだ。その処理には、たいていの場合何らかの破棄の処理が必要で、するとデストラクターも定義しなければならない。 + +同様に、デストラクターで何らかの独自の処理をするということは、コピーやムーブでも何らかの処理をしたいはずだ。 diff --git a/TeX/040-smart-pointer.tex b/TeX/040-smart-pointer.tex new file mode 100644 index 0000000..76b284c --- /dev/null +++ b/TeX/040-smart-pointer.tex @@ -0,0 +1,386 @@ +\hyperchapter{ch36}{スマートポインター}{スマートポインター} +\index{すまとぽいんた@スマートポインター} + +この章では、コピーできないがムーブできる型として、スマートポインターを説明する。 + +ストレージを動的確保した場合、解放しなければならない。 + +\begin{lstlisting}[language={C++}] +void f() +{ + int * ptr = new int(0) ; + delete * ptr ; +} +\end{lstlisting} + +これを正しく行うのは難しい。というのも、動的確保を複数する場合、動的確保が失敗する可能性があるからだ。 + +\begin{lstlisting}[language={C++}] +void f() +{ + int * p1 = new int(0) ; + int * p2 = new int(1) ; + + delete p2 ; + delete p1 ; +} +\end{lstlisting} + +この何気ない一見問題のなさそうなコードには問題がある。もし\texttt{new int(1)}が失敗した場合、例外が投げられ、そのまま関数\texttt{f}の実行は終わってしまう。後続の\texttt{delete}は実行されない。 + +そのような場合にスマートポインターが使える。スマートポインターはポインターの解放とムーブを代わりに行ってくれる便利なライブラリだ。 + +\hypersection{ch3601}{unique\texttt{\_}ptr} +\index{unique\_ptr@\texttt{unique\_ptr}}\index{make\_unique@\texttt{make\_unique}} + +\texttt{std::unique\_ptr}\,は以下のように使う。 + +\begin{lstlisting}[style=grammar] +auto ptr = std::make_unique<型>( 初期化コンストラクターへの引数 ) +\end{lstlisting} + +具体的には以下のようになる。 + +\begin{lstlisting}[language={C++}] +void f() +{ + // std::unique_ptr + auto p1 = std::make_unique< int >( 0 ) ; + auto p2 = std::make_unique< int >( 1 ) ; +} +\end{lstlisting} + +\texttt{delete}がないが問題はない。\texttt{delete}は\texttt{unique\_ptr}のデストラクターが自動で呼んでくれるからだ。 + +\texttt{p2}の動的確保が失敗した場合でも問題はない。 + +\texttt{unique\_ptr}はポインターとほぼ同じように使うことができる。例えばポインターが参照するオブジェクトを間接的に使いたい場合は\texttt{operator *}\,\index{\protect{*}@\texttt{\protect{*}}}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto p = std::make_unique< int >( 0 ) ; + + *p = 123 ; + std::cout << *p ; +} +\end{lstlisting} + +メンバーにアクセスするときには\texttt{operator ->}\,\index{->@\texttt{->}}も使える。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto p = std::make_unique< std::vector > () ; + p->push_back(0) ; +} +\end{lstlisting} + +\texttt{unique\_ptr}はたいへん便利なのであらゆる箇所で生のポインターの代わりに使うべきだが、古い関数に生のポインターを渡さなければならない場合などは\texttt{unique\_ptr}を渡せない。そのような場合のために\texttt{unique\_ptr}、生のポインターを得る方法がある。メンバー関数\texttt{get}\index{get@\texttt{get}}だ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// 古臭い時代遅れの生ポインターを引数に取る関数 +void old_outdated_ugly_function( int * ptr ) ; + +int main() +{ + auto ptr = std::make_unique(0) ; + old_outdated_ugly_function( ptr.get() ) ; +} +\end{lstlisting} + +ただし\texttt{get}を使うときは生のポインターを使う期間が\texttt{unique\_ptr}の寿命の期間内でなければならない。 + +以下のような場合は使えない。 + +\begin{lstlisting}[language={C++}] +// 前回渡したポインターの参照する値と +// 今回渡したポインターの参照する値が +// 等しい場合にtrueを返す +int * last_ptr ; +bool is_equal_to_last_ptr( int * ptr ) +{ + if ( last_ptr == nullptr ) + last_ptr = ptr ; + + bool b = *ptr == *last_ptr ; + last_ptr = ptr ; + return b ; +} + +void f() +{ + auto p = std::make_unique(0) ; + is_equal_to_last_ptr( p.get() ) ; +} + +int main() +{ + f() ; + // エラー + f() ; +} +\end{lstlisting} + +これは関数\texttt{f}が\texttt{unique\_ptr}の寿命の期間を超えてポインターを保持して参照しているからだ。 + +\texttt{unique\_ptr}はコピーができない。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + auto p = std::make_unique(0) ; + // エラー、 コピーはできない + auto q = p ; +} +\end{lstlisting} + +これはポインターの値をコピーして、ポインターの所有権を持つオブジェクトが複数存在することを防ぐためだ。 + +ムーブはできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto p = std::make_unique(0) ; + auto q = std::move(p) ; +} +\end{lstlisting} + +ムーブしたあとの変数\texttt{p}はポインターの所有権を持たない。 + +\texttt{unique\_ptr}の実装はとても簡単だ。例えば簡易的なものならば1ページに収まるほどのコード量で書ける。 + +\begin{lstlisting}[language={C++}] +template < typename T > +class unique_ptr +{ + T * ptr = nullptr ; +public : + unique_ptr() { } + explicit unique_ptr( T * ptr ) + : ptr( ptr ) { } + ~unique_ptr() + { delete ptr ; } + + // コピーは禁止 + unique_ptr( const unique_ptr & ) = delete ; + unique_ptr & operator =( const unique_ptr & ) = delete ; + + // ムーブ + unique_ptr( unique_ptr && r ) + : ptr( r.ptr ) + { r.ptr = nullptr ; } + unique_ptr & operator = ( unique_ptr && r ) + { + delete ptr ; + ptr = r.ptr ; + r.ptr = nullptr ; + } + + T & operator * () noexcept { return *ptr ; } + T * operator ->() noexcept { return ptr ; } + T * get() noexcept { return ptr ; } +} ; +\end{lstlisting} + +コンストラクターでポインターを受け取り、デストラクターで破棄する。コピーは禁止。ムーブは所有権を移動。特に解説するまでもなくコードを読むだけでいいほどの単純な実装だ。 + +現実の\texttt{unique\_ptr}はもう少し便利な機能を提供しているので、実装はもう少し複雑になっているが、基本的な実装としては変わらない。 + +\hypersection{ch3602}{shared\texttt{\_}ptr} +\index{shared\_ptr@\texttt{shared\_ptr}} + +\texttt{unique\_ptr}は便利だがコピーができない。コピーができないのは\texttt{unique\_ptr}がポインターの所有権を排他的に独占するからだ。これはどうにもならないが、コピーしたいものはコピーしたい。 + +そこで、コピーができるスマートポインターとして\texttt{shared\_ptr}がある。 + +\texttt{unique\_ptr}\,は\texttt{make\_unique(...)}で作るように、\texttt{shared\_ptr}\,は\texttt{std::make\_shared{\allowbreak}(...)}\index{make\_shared@\texttt{make\_shared}}で作る。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto p = std::make_shared(0) ; +} +\end{lstlisting} + +\texttt{unique\_ptr}と同じようにポインターのように使うことができる。 + +\texttt{shared\_ptr}はコピーができる。 + +\begin{lstlisting}[language={C++}] +auto p1 = std::make_shared(0) ; +auto p2 = p1 ; +auto p3 = p1 ; +\end{lstlisting} + +しかも、コピーはすべて同じポインターを持っている。例えば以下のようにすると、 +\begin{lstlisting}[language={C++}] +*p3 = 123 ; +\end{lstlisting} +\texttt{*p1, *p2, *p3}はいずれも\texttt{123}になる。 + +これはどれも同じポインターの値を保持しているためだ。\texttt{p1.get(), p2.get(), p3.get()}はすべて同じポインターの値を返す。 + +\texttt{shared\_ptr}は本当に何も考えずに気軽にコピーしてもよい。例えば以下のような本当に汚いコードですら動く。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +std::shared_ptr last_ptr ; + +bool is_equal_to_last_ptr( std::shared_ptr ptr ) +{ + if ( last_ptr == nullptr ) + last_ptr = ptr ; + + bool b = *last_ptr == *ptr ; + last_ptr = ptr ; + return b ; +} + +int main() +{ + auto p1 = std::make_shared(1) ; + auto p2 = std::make_shared(2) ; + + // true + is_equal_to_last_ptr( p1 ) ; + // false + is_equal_to_last_ptr( p2 ) ; + *p2 = 1 ; + // true + is_equal_to_last_ptr( p1 ) ; +} +\end{lstlisting} + +\texttt{shared\_ptr}はコピーされたすべての\texttt{shared\_ptr}のオブジェクトが同じポインターを共有する。ポインターを所有する最後の\texttt{shared\_ptr}のオブジェクトが破棄されたときに、ポインターが\texttt{delete}される。 + +そのため、\texttt{shared\_ptr}を使うときは、ポインターが有効なオブジェクトを指すかどうかを気にしなくてよい。そのポインターを所有する\texttt{shared\_ptr}のオブジェクトが1つでも生き残っている限り、ポインターは有効になっている。 + +\texttt{shared\_ptr}はどうやって実装されているのだろうか。\texttt{shared\_ptr}\,は\texttt{T}へのポインターのほかに、現在何個の\texttt{shared\_ptr}のオブジェクトがポインターを所有しているのかを数えるカウンターへのポインターを持っている。 +\index{shared\_ptr@\texttt{shared\_ptr}!かうんた@カウンター} + +\begin{lstlisting}[language={C++}] +template < typename T > +class shared_ptr +{ + T * ptr ; + std::size_t * count ; +} ; +\end{lstlisting} + +\texttt{shared\_ptr}が初めて作られるとき、このカウンター用にストレージが動的確保され、値が\texttt{1}になる。 + +\begin{lstlisting}[language={C++}] +explicit shared_ptr( T * ptr ) + : ptr( ptr ), count( new std::size_t(1) ) +{ } +\end{lstlisting} + +コピーされるとき、カウンターがインクリメントされる。 + +\begin{lstlisting}[language={C++}] +shared_ptr( const shared_ptr & r ) + : ptr( r.ptr ), count( r.count ) +{ + ++*count ; +} +\end{lstlisting} + +デストラクターでは、カウンターがデクリメントされる。そしてカウンターがゼロの場合、ポインターが\texttt{delete}される。 + +\begin{lstlisting}[language={C++}] +~shared_ptr() +{ + // カウンターが妥当なポインターを指しているかどうか確認 + if ( count == nullptr ) + return ; + + // デクリメント + --*count ; + // 所有者が0ならば + if ( *count == 0 ) + { // 解放する + delete ptr ; + ptr = nullptr ; + delete count ; + count = nullptr ; + } +} +\end{lstlisting} + +全体としては少し長いが、以下のようになる。 + +\begin{lstlisting}[language={C++}] +template < typename T > +class shared_ptr +{ + T * ptr = nullptr ; + std::size_t * count = nullptr ; + + void release() + { + if ( count == nullptr ) + return ; + + --*count ; + if ( *count == 0 ) + { + delete ptr ; + ptr = nullptr ; + delete count ; + count = nullptr ; + } + } +public : + + shared_ptr() { } + explicit shared_ptr( T * ptr ) + : ptr(ptr), count( new std::size_t(1) ) + { } + ~shared_ptr() + { + release() ; + } + + shared_ptr( const shared_ptr & r ) + : ptr( r.ptr ), count( r.count ) + { + ++*count ; + } + shared_ptr & operator =( const shared_ptr & r ) + { + if ( this == &r ) + return *this ; + + release() ; + ptr = r.ptr ; + count = r.count ; + ++*count ; + } + + shared_ptr( shared_ptr && r ) + : ptr(r.ptr), count(r.count) + { + r.ptr = nullptr ; + r.count = nullptr ; + } + + shared_ptr & operator =( shared_ptr && r ) + { + release() ; + ptr = r.ptr ; + count = r.count ; + r.ptr = nullptr ; + r.count = nullptr ; + } + + T & operator * () noexcept { return *ptr ; } + T * operator ->() noexcept { return ptr ; } + T * get() noexcept { return ptr ; } +} ; +\end{lstlisting} + +これはとても簡易的な\texttt{shared\_ptr}の実装だ。本物の\texttt{std::shared\_ptr}はもっと複雑で、もっと高度な機能を提供している。 diff --git a/TeX/041-move-support.tex b/TeX/041-move-support.tex new file mode 100644 index 0000000..c0cde18 --- /dev/null +++ b/TeX/041-move-support.tex @@ -0,0 +1,690 @@ +\hyperchapter{ch37}{自作の数値クラスで\\演算をムーブに対応する方法}{自作の数値クラスで演算をムーブに対応する方法} + +自作の数値計算をするクラスを実装するとしよう。無限精度整数、ベクトル、行列など、自作のクラスで実装したい数値と演算は世の中にたくさんある。 +\index{すうちけいさんくらす@数値計算クラス}\index{くらす@クラス!すうちけいさん@数値計算〜} + +そのとき、数値の状態を表現するためにストレージを動的確保するとしよう。ここでは例のため、とても簡単な整数型を考える。 + +\begin{lstlisting}[language={C++}] +class Integer +{ + int * ptr ; +} ; +\end{lstlisting} + +\hypersection{ch3701}{基本の実装} + +まずは基本となるコンストラクターとデストラクター、コピー、ムーブを実装しよう。 + +\begin{lstlisting}[language={C++}] +struct Integer +{ + int * ptr ; +public : + explicit Integer( int value = 0 ) + : ptr ( new int(value) ) { } + ~Integer( ) + { delete ptr ; } + +(@\ifTombow\pagebreak\fi@) + // コピー + Integer( const Integer & r ) + : ptr( new int( *r.ptr ) ) { } + Integer & operator = ( const Integer & r ) + { + if ( this != &r ) + *ptr = *r.ptr ; + return *this ; + } + + // ムーブ + Integer( Integer && r ) + : ptr( r.ptr ) + { r.ptr = nullptr ; } + Integer operator =( Integer && r ) + { + delete ptr ; + ptr = r.ptr ; + r.ptr = nullptr ; + return *this ; + } +} ; +\end{lstlisting} + +コンストラクターは動的確保をする。デストラクターは解放する。コピーは動的確保をする。ムーブは所有権の移動をする。とてもよくあるクラスの実装だ。 + +実用的には\texttt{std::unique\_ptr}\,を使うべきだが、低級な処理を説明するためにあえて生のポインターを使っている。 + +今回のコピー代入演算子は単に値をコピーしているが、コピー元とコピー先で確保したストレージのサイズが異なるような型、たとえば無限精度整数や動的なサイズのベクトルや行列などの場合は、コピー代入演算子でもコピー先のストレージを破棄してコピー元と同じサイズのストレージを確保するなどの処理が必要な場合もある。 +\index{ぎようれつくらす@行列クラス}\index{くらす@クラス!ぎようれつ@行列〜} + +\begin{lstlisting}[language={C++}] +// 行列クラス +class matrix +{ + // オブジェクトごとにサイズが異なる + unique_ptr ptr ; + std::size_t rows ; + std::size_t columns ; +public : + // コピー代入演算子 + matrix & operator = ( const matrix & r ) + { + // 自分自身への代入 + if ( this == &r ) + return *this ; + + // 行列のサイズが同じかどうか確認 + if ( rows == r.rows && columns == r.columns ) + { + // コピー元の行列の値をコピー先にコピー + // コピー先のストレージはそのまま使える + } + else + { + // コピー先のストレージを解放 + // コピー先はコピー元の行列サイズと同じストレージを確保 + // 値をコピー + } + } +} ; +\end{lstlisting} + +\hypersection{ch3702}{複合代入演算子} +\index{ふくごうだいにゆうえんざんし@複合代入演算子}\index{えんざんし@演算子!ふくごうだいにゆう@複合代入〜} + +複合代入演算子というのは、\texttt{operator +=}\,や\texttt{operator -=}\,のような演算子だ。これはコピー代入演算子と同じように実装できる。違いは代入の結果、演算をするだけだ。 +\index{\protect{+=}@\texttt{\protect{+=}}}\index{-=@\texttt{-=}} + +クラス\texttt{Integer}の場合、演算の結果ストレージのサイズが変わるということはないので、愚直な実装で済む。 + +\begin{lstlisting}[language={C++}] +Integer & operator +=( const Integer & r ) +{ + *ptr += *r.ptr ; + return *this ; +} + +Integer & operator -=( const Integer & r ) +{ + *ptr -= *r.ptr ; + return *this ; +} +\end{lstlisting} + +複合代入演算子をムーブ代入演算子として実装する理由は、通常はない。 + +\clearpage +\hypersection{ch3703}{単項演算子} +\index{たんこうえんざんし@単項演算子}\index{えんざんし@演算子!たんこう@単項〜} + +演算を表現するクラスでオーバーロードしたい単項演算子には\texttt{operator +}\,と\texttt{operator -}\,がある。特に\texttt{operator -}\,は実用上の意味があるので実装してみよう。 +\index{\protect{+}@\texttt{\protect{+}}}\index{\protect{-}@\texttt{\protect{-}}} + +\begin{lstlisting}[language={C++}] +Integer a(10) ; +auto b = -a ; +// これは二項演算子 operator +の結果に +// 単項演算子operator -を適用 +auto c = -(a + a) ; +\end{lstlisting} + +\texttt{*this}\index{\protect{*}this@\texttt{\protect{*}this}}が\texttt{lvalue}の場合の単項演算子の実装は以下のようになる。 + +\begin{lstlisting}[language={C++}] +Integer operator -() const +{ + Integer result( -*ptr ) ; + return result ; +} + +// operator +()の実装は省略 +\end{lstlisting} + +単項演算子\texttt{operator -}\,は\,\texttt{*this}を書き換えない。負数にした値のコピーを返す。 + +変数\texttt{result}は\texttt{return文}\index{return@\texttt{return}文}のあとは使われないので、\texttt{return std::move(result)} と書くこともできる。しかし、そのように書く必要はない。というのも\texttt{return文}は特別な扱いを受けているので、関数の中の変数を\texttt{return}した場合、自動でムーブが行われるからだ。もちろん、\texttt{std::move}を明示的に書いてもよい。 + +単項演算子\texttt{operator -}\,は\,\texttt{*this}が\texttt{lvalue}のときには上のように実装するしかない。しかしこの実装は非効率的だ。なぜならば、コードを読めばわかるように、追加の一時変数が生成され、追加の動的メモリー確保が行われるからだ。 + +そのため、もしクラス\texttt{Integer}がコピーしか実装していない場合、 +\begin{lstlisting}[language={C++}] +Integer a ; +auto b = -a ; +\end{lstlisting} +というコードは、 +\begin{lstlisting}[language={C++}] +Integer a ; +auto b = a ; +b.make_it_negative() ; +\end{lstlisting} +のような現在の値をそのまま負数にするメンバー関数\texttt{make\_it\_negative}を実装して使った方が効率がよくなる。 + +\begin{lstlisting}[language={C++}] +class Integer +{ + int * ptr ; +public : + void make_it_negative() + { + *ptr = -*ptr ; + } +} ; +\end{lstlisting} + +幸い、クラス\texttt{Integer}はムーブコンストラクターを実装しているので、 +\begin{lstlisting}[language={C++}] +auto b = -a ; +\end{lstlisting} +というコードは、式\,\texttt{-a}によって生成された一時オブジェクトが変数\texttt{b}にムーブされる。 + +しかし、 +\begin{lstlisting}[language={C++}] +auto c = -(a + a) ; +\end{lstlisting} +というコードは依然として非効率的になる。まだ二項演算子\texttt{operator +}は実装していないが、これは、 +\begin{lstlisting}[language={C++}] +auto temp1 = a + a ; +auto temp2 = -temp1 ; +auto c = temp2 ; +\end{lstlisting} +になるからだ。すると以下のように書いた方が効率がよくなる。 + +\begin{lstlisting}[language={C++}] +Integer a ; +auto c = a ; +c += a ; +c.make_it_negative() ; +\end{lstlisting} + +こんなコードを書くのは面倒だ。単に\,\texttt{-(a+a)}と書いて効率的に動いてほしい。そのために単項演算子\texttt{operator -}\,をムーブに対応させる。 + +単項演算子はクラスのメンバー関数として実装する。 + +\begin{lstlisting}[language={C++}] +class Integer +{ +public ; + Integer operator -() const ; +} ; +\end{lstlisting} + +これが非メンバー関数ならば、単に\texttt{rvalue}リファレンスを取ればよい。 + +\begin{lstlisting}[language={C++}] +Integer negate( Integer && object ) ; +\end{lstlisting} + +メンバー関数の場合、\texttt{object}に相当するのは\,\texttt{*this}だ。 + +\begin{lstlisting}[language={C++}] +class Integer +{ +public : + // *thisがobjectに相当する + Integer negate() ; +} ; +\end{lstlisting} + +\texttt{this}がポインターになっているのは歴史的な都合で、本来はリファレンスになっているべきだった。メンバー関数は以下のような隠し引数があるものとして考えるとよい。 + +\begin{lstlisting}[language={C++}] +class Integer +{ +public : + // 隠し引数 + Integer negate( Integer & THIS ) + { + Integer * this = &THIS ; + } +} ; +\end{lstlisting} + +もちろん、このような隠し引数\texttt{THIS}をC++のプログラムから参照する方法はない。あくまでも参考のためのコードだ。 + +メンバー関数を\texttt{const}修飾するというのは、 +\begin{lstlisting}[language={C++}] +class Integer +{ +public : + Integer negate() const ; +} ; +\end{lstlisting} +この隠し引数を\texttt{const}修飾\index{const@\texttt{const}修飾}するのと同じだ。 + +\begin{lstlisting}[language={C++}] +class Integer +{ +public : + Integer negate( const Integer & THIS ) + { + const Integer * this = &THIS ; + // ... + } +} ; +\end{lstlisting} + +これによって\texttt{const}修飾の有無でメンバー関数を呼び分けられる。 + +\begin{lstlisting}[language={C++}] +struct X +{ + void f() ; + void f() const ; +} ; + +int main() +{ + X x ; + x.f() ; // 非const + const X & cx = x ; + cx.f() ; // const +} +\end{lstlisting} + +C++には\texttt{const}修飾と同様に、「リファレンス修飾」\index{りふあれんすしゆうしよく@リファレンス修飾}という機能がある。これを使えば隠し引数に\texttt{lvalue}/\texttt{rvalue}リファレンスの修飾ができる。 + +\begin{lstlisting}[language={C++}] +struct X +{ + // lvalueリファレンス修飾子 + void f() & ; + // rvalueリファレンス修飾子 + void f() && ; +} ; + +int main() +{ + X lvalue ; + // lvalueリファレンス + lvalue.f() ; + // rvalueリファレンス + std::move(lvalue).f() ; +} +\end{lstlisting} + +これは実質的以下のような隠し引数があるものと考えてよい。もちろん隠し引数を使うことはできない。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +struct X +{ + // lvalueリファレンス + void f( X & THIS ) + { + X * this = &THIS ; + } + void f( X && THIS ) + { + X * this = &THIS ; + } +} ; +\end{lstlisting} + +\texttt{void f() \&\&}\,のメンバー関数の中では、\texttt{*this}は\texttt{lvalue}だ。\texttt{rvalue}リファレンスの変数は\texttt{lvalue}であることを思い出そう。 + +\begin{lstlisting}[language={C++}] +void f( int & ) ; +void f( int && ) ; + +int main() +{ + int lvalue {} ; + int && rvalue = std::move(lvalue) ; + // int &を呼び出す + // rvalueリファレンスの変数はlvalue + f( rvalue ) ; +} +\end{lstlisting} + +クラスでメンバー関数にリファレンス修飾子を書かなかった場合、\texttt{lvalue}リファレンス修飾子を書いたものとみなされる。 +\index{りふあれんすしゆうしよくし@リファレンス修飾子} + +\begin{lstlisting}[language={C++}] +struct X +{ + // lvalueリファレンス + void f() ; +} ; +\end{lstlisting} + +もしメンバー関数にリファレンス修飾子を書いた場合、同じ名前のすべてのメンバー関数にリファレンス修飾子を書かなければならない。 + +\begin{lstlisting}[language={C++}] +struct X +{ + // エラー、 リファレンス修飾子がない + void f() ; + void f() & ; + + // OK、リファレンス修飾子がある + void g() & ; + void g() && ; + + // OK リファレンス修飾子を使っていない + // デフォルトでlvalueリファレンス修飾子 + void h() ; +} ; +\end{lstlisting} + +リファレンス修飾子を使い、\texttt{*this}が\texttt{lvalue}と\texttt{rvalue}の場合で実装を分けることができる。 + +\begin{lstlisting}[language={C++}] +class Integer +{ + int * ptr ; +public : + // lvalue版 + Integer operator -() const & + { + auto result = ( -*ptr ) ; + return result ; + } + // rvalue版 + Integer operator -() && + { + auto result = std::move(*this) ; + *result.ptr = -*result.ptr ; + return result ; + } +} ; +\end{lstlisting} + +\texttt{rvalue}リファレンス修飾子を使った単項演算子\texttt{operator -}\,の実装は、\texttt{*this}自身が\texttt{rvalue}であるので、自分自身をムーブしている。ムーブ以降、\texttt{this->ptr}は\texttt{nullptr}になる。なぜならば、\texttt{Integer}のムーブ代入演算子がそのような実装になっているからだ。 + +\begin{lstlisting}[language={C++}] +/// 上で示したのと同じムーブ代入演算子の抜粋 +Integer operator =( Integer && r ) +{ + delete ptr ; + ptr = r.ptr ; + // ムーブ元のポインターをnullptrにする + r.ptr = nullptr ; + return *this ; +} +\end{lstlisting} + +\hypersection{ch3704}{二項演算子} +\index{にこうえんざんし@二項演算子}\index{えんざんし@演算子!にこう@二項〜} + +せっかく数値を表現するクラスなのだから二項演算子を使った演算がしたい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + Integer a(1) ; + Integer b(2) ; + Integer c = a + b ; +} +\end{lstlisting} + +これをどうやって実装するのかというと、\texttt{operator +}演算子\index{\protect{+}@\texttt{\protect{+}}}のオーバーロードで実装する。 +\index{おばろど@オーバーロード}\index{えんざんし@演算子!おばろど@オーバーロード} + +演算子のオーバーロードはメンバー関数による方法と、非メンバー関数による方法がある。 + +\begin{lstlisting}[language={C++}] +struct X +{ + // メンバー関数 + X operator +( const X & r ) ; +} ; + +// 非メンバー関数 +X operator +( const X & l, const X & r ) ; +\end{lstlisting} + +\texttt{operator =}\,のような特殊な演算子以外は、どちらの方法で書いてもいい。メンバー関数として書いた場合、第一引数は\,\texttt{*this}に、第二引数が関数の引数\texttt{r}になる。 + +例えば以下のようなコードで、 +\begin{lstlisting}[language={C++}] +X a ; +X b ; +a + b ; +\end{lstlisting} +メンバー関数の場合、\texttt{*this}は\texttt{a}、\texttt{r}は\texttt{b}になる。 + +非メンバー関数の場合、\texttt{l}は\texttt{a}は、\texttt{r}は\texttt{b}になる。 + +\hypersubsection{ch370401}{ムーブしない実装} +\index{にこうえんざんし@二項演算子!むぶしない@ムーブしない〜} + +二項演算子のオペランドがどちらも\texttt{lvalue}であった場合はムーブができないので、引数は\texttt{const}な\texttt{lvalue}リファレンスで受け取り、\texttt{prvalue}を返す。 + +メンバー関数の場合の実装は以下のようになる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +class Integer +{ + int * ptr ; +public : + // 省略... + + Integer operator +( const Integer & r ) const + { return Integer( *ptr + *r.ptr ) ; } +} ; +\end{lstlisting} + +非メンバー関数の場合は、\texttt{Integer::ptr}が\texttt{private}メンバーであることが問題になる。 + +\begin{lstlisting}[language={C++}] +Integer operator + ( const Integer & l, const Integer & r ) +{ + // エラー、 Integer::ptrはprivateメンバー + return Integer( *l.ptr + *r.ptr ) ; +} +\end{lstlisting} + +これを解決するための方法はいくつかある。 + +\vskip 1.0zw +\noindent +\textsf{1. クラスのメンバー関数として処理を実装し、そのメンバー関数を呼び出す方法} + +\begin{lstlisting}[language={C++}] +class Integer +{ + int * ptr ; +public : + Integer plus( const Integer & r ) const + { return Integer( *ptr + r.ptr ) ; } +} ; + +Integer operator + ( const Integer & l, const Integer & r ) +{ + return l.plus( r ) ; +} +\end{lstlisting} + +\vskip 1.0zw +\noindent +\textsf{2. \texttt{friend}宣言する方法} + +クラスが別のクラスや関数を\texttt{friend}宣言\index{friend@\texttt{friend}}すると、その関数はクラスの\texttt{private}なメンバーを使えるようになる。 + +\begin{lstlisting}[language={C++}] +class X +{ + int member ; + // friend宣言 + friend int get_member( const X & ) ; +} ; + +int get_member( const X & obj ) +{ + // OK、friendなので使える + return obj.member ; +} +\end{lstlisting} + +これを使うと、以下のように\texttt{friend}宣言すれば、動かなかった非メンバー関数による\texttt{operator +}のオーバーロードが動くようになる。 + +\begin{lstlisting}[language={C++}] +class Integer +{ + friend Integer operator +( const Integer &, const Integer & ) ; +} ; +\end{lstlisting} + +\hypersubsection{ch370402}{ムーブをしたくなる状況} +\index{にこうえんざんし@二項演算子!むぶする@ムーブする〜} + +上の二項演算子の実装だけで、クラス\texttt{Integer}は加算ができるようになった。ただし、効率がよくない。 + +例えば以下のようなコードを考えよう。 + +\begin{lstlisting}[language={C++}] +Integer a ; +auto b = a + a + a ; +\end{lstlisting} + +これはどのように評価されるかというと、\texttt{a+a+a}は、\texttt{(a+a)+a}となり、\texttt{(a+a)}を評価した結果の一時オブジェクトが生成され、その一時オブジェクトを仮に\texttt{temp}と呼ぶと、\texttt{temp+a}される。 + +結果として、以下のようなコードと同じになる。 + +\begin{lstlisting}[language={C++}] +Integer a ; +auto temp = a + a ; +auto b = temp + a ; +\end{lstlisting} + +ムーブを実装していない場合、以下のように書いた方が効率がよくなる。 + +\begin{lstlisting}[language={C++}] +Integer a ; +auto b = a ; +b += a ; +b += a ; +\end{lstlisting} + +このようなコードは面倒だ。できれば\texttt{a + a + a}と書きたい。 + +\ifTombow\pagebreak\fi +二項演算子は\texttt{operator +}だけではない。 + +\begin{lstlisting}[language={C++}] +auto result = a + b - c * d / e ; +\end{lstlisting} +のようなコードも書きたい。これを効率化のために、 +\begin{lstlisting}[language={C++}] +auto result = a; +a += b ; +auto temp = c ; +temp *= d ; +temp /= e ; +result -= temp ; +\end{lstlisting} +のように書かなければならないとしたら悲惨だ。 + +\texttt{a+a+a}のような式が効率的に動くためには、二項演算子で\texttt{lvalue}/\texttt{rvalue}リファレンスを取り、\texttt{rvalue}リファレンスの場合はムーブするコードを書く。\texttt{rvalue}からは所有権を横取りしてもよいからだ。 + +二項演算子は引数が2つあり、それぞれに\texttt{lvalue}/\texttt{rvalue}があるので、4通りのオーバーロードを書かなければならない。 + +非メンバー関数で実装するには、以下のように宣言を書く。 + +\begin{lstlisting}[language={C++}] +class Integer +{ + friend integer operator + ( const Integer & l, const Integer & r ) ; + friend integer operator + ( Integer && l, const Integer & r ) ; + friend integer operator + ( const Integer & l, Integer && r ) ; + friend integer operator + ( Integer && l, Integer && r ) ; +} ; + +// lvalue + lvalue +Integer operator + ( const Integer & l, const Integer & r ) ; +// rvalue + lvalue +Integer operator + ( Integer && l, const Integer & r ) ; +// lvalue + rvalue +Integer operator + ( const Integer & l, Integer && r ) ; +// rvalue + rvalue +Integer operator + ( Integer && l, Integer && r ) ; +\end{lstlisting} + +具体的な実装としては、まず\texttt{rvalue}リファレンスで束縛したリファレンスを関数のローカル変数にムーブしたあとで、その変数を\texttt{return}する。 + +\ifTombow\pagebreak\fi +第一引数が\texttt{rvalue}の場合は、以下のようになる。 + +\begin{lstlisting}[language={C++}] +Integer operator + ( Integer && l, const Integer & r ) +{ + auto result = std::move(l) ; + result += r ; + return result ; +} +\end{lstlisting} + +第一引数は\texttt{rvalue}なので、ムーブしてもよい。 + +先ほども説明したように、\texttt{'return文'}\,\index{return@\texttt{return}文}が関数のローカル変数を返すときは自動でムーブしてくれる。もちろん\,\texttt{'return std::move(result)'}\,と書いてもよい。 + +第二引数が\texttt{rvalue}の場合は、ムーブすべきオブジェクトが第二引数になる。 + +\begin{lstlisting}[language={C++}] +Integer operator + ( const Integer & l, Integer && r ) +{ + auto result = std::move(r) ; + result += l ; + return result ; +} +\end{lstlisting} + +この実装はすべてに使えるわけではない。加算の場合は、一般に交換法則を満たすことが期待できる。つまり、 +\[ a + b = b + a \] +であることが期待できるが、除算演算子\texttt{operator /}\,は交換法則を満たさない。今回の\texttt{Integer}のような簡単な作りのクラスならば実装できるが、クラスの実装と演算次第では第二引数のみが\texttt{rvalue}の場合にはムーブできない場合もあるだろう。そういう場合には実装しなくてもよい。実装できないものは実装しないのが正しい。 + +第一引数、第二引数のいずれかが\texttt{rvalue}であるときにムーブする演算子のオーバーロードを両方とも実装した場合、両方の引数が\texttt{rvalue}である場合のオーバーロードも実装しなければならない。 + +第一引数、第二引数が両方共\texttt{rvalue}である場合というのは、例えば以下のような場合だ。 + +\begin{lstlisting}[language={C++}] +Integer a ; +auto b = (a + a) + (a + a) ; +\end{lstlisting} + +\texttt{a+a}を評価した結果は\texttt{rvalue}だ。この式では\texttt{rvalue}と\texttt{rvalue}を\texttt{operator +}に渡しているので、引数は両方とも\texttt{rvalue}になる。 + +もし、\texttt{rvalue + lvalue}と\texttt{lvalue + rvalue}に対応する演算子しかオーバーロードしていない場合、関数呼び出しが曖昧になってしまう。そこで、\texttt{rvalue + rvalue}の演算子オーバーロードも書く。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +Integer operator +( Integer && l, Integer && r ) +{ + return std::move(l) + r ; +} ; +\end{lstlisting} + +この実装は単に\texttt{rvalue + lvalue}の演算子オーバーロードに実際の処理を丸投げしている。変数\texttt{r}はここでは\texttt{lvalue}だ。何度も言うように\texttt{rvalue}を束縛した\texttt{rvalue}リファレンスの変数は\texttt{lvalue}だ。ここでは第一引数をムーブし、第二引数は\texttt{lvalue}として扱っている。 + +メンバー関数で実装する場合、二項演算子の第一引数は\,\texttt{*this}、第二引数がメンバー関数の第一引数になる。 + +\begin{lstlisting}[language={C++}] +class Integer +{ +public : + // lvalue + lvalue + Integer operator + ( const Integer & r ) const & ; + // rvalue + lvalue + Integer operator + ( const Integer & r ) && ; + // lvalue + rvalue + Integer operator + ( Integer && r ) const & ; + // rvalue + rvalue + Integer operator + ( Integer && r ) && ; +} ; +\end{lstlisting} + +\texttt{a + b}のとき、\texttt{*this}が\texttt{a}、\texttt{r}が\texttt{b}だ。あとの実装は非メンバー関数の場合と変わらない。 + +例えばメンバー関数で\texttt{rvalue + lvalue}の実装は以下のようになる。 + +\begin{lstlisting}[language={C++}] +Integer Integer::operator +( const Integer & r ) && +{ + auto result = std::move(*this) ; + result += r ; + return result ; +} +\end{lstlisting} + diff --git a/TeX/042-string-intro.tex b/TeX/042-string-intro.tex new file mode 100644 index 0000000..11bffc0 --- /dev/null +++ b/TeX/042-string-intro.tex @@ -0,0 +1,1386 @@ +\hyperchapter{ch38}{文字列}{文字列} +\index{もじれつ@文字列} + +\hypersection{ch3801}{はじめに} + +とうとう文字列を学ぶべきときがやってきた。文字列自体は最初から使ってきた。 + +\begin{lstlisting}[language={C++}] +auto s = "hello"s ; +\end{lstlisting} + +これは文字列の表面的な使い方だけだ。しかも、本書ではこれまで文字列に日本語を使ってこなかった。これには理由がある。たとえば、 +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << "(@\ifColor\textcolor[rgb]{0,0.533,0}{こんにちは}\else\textcolor{black}{こんにちは}\fi@)"s ; +} +\end{lstlisting} +のようなコードが動くかどうかは実装依存だからだ。試しにコンパイルして実行してみよう。もし画面に「こんにちは」と表示されたのであれば、どうやら読者の環境はこのコードで日本語を出力、表示できるようだ。 + +\hypersection{ch3802}{基本ソース文字セット} +\index{きほんそすもじせつと@基本ソース文字セット} + +C++では、基本ソース文字セットと呼ばれる文字がある。C++のソースコードで安全に使うことができる文字だ。ラテンアルファベットの大文字小文字、記号、制御文字からなる文字セットで、96文字ある。 + +空白文字、水平タブ、垂直タブ、フォームフィード、改行の5文字と、印字可能な以下の91文字だ。 + +\begin{lstlisting}[style=grammar] +a b c d e f g h i j k l m n o p q r s t u v w x y z +A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +0 1 2 3 4 5 6 7 8 9 +_ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " ' +\end{lstlisting} + +\hypersection{ch3803}{基本実行文字セット} +\index{きほんじつこうもじせつと@基本実行文字セット} + +基本実行文字セットは基本ソース文字セットにアラート、バックスペース、キャリッジリターン、null文字を加えたものだ。 + +null文字\index{nullもじ@null文字}は整数の\texttt{0}に等しいという特別な特徴を持つ文字だ。 + +\hypersection{ch3804}{文字を表現する方法} +\index{もじのひようげん@文字の表現} + +文字をコンピューターで扱うには、ビット列で文字を表現できなければならない。C++でアドレス可能な最小単位はバイトなので、文字というのはバイト列で表現する。 + +\hypersubsection{ch380401}{ASCII} +\index{ASCII} + +ASCIIはとても広く普及した文字のエンコード方法だ。ASCIIでは7ビットの整数値で1文字を表現する。 + +C++の基本実行文字セットは特定の文字エンコードであると規定されてはいないが、ASCIIを参考にしている。ただしASCIIには基本実行文字セットにはない、ダラーサイン(\$)、アットマーク(@)、バッククオート(`)といくつかの制御文字がある。 + +\hypersubsection{ch380402}{Unicode} +\index{Unicode} + +Unicode、もしくはISO/IEC 10646(Universal Coded Character Set, UCS)は文字のコードポイントを定める規格だ。 +\index{ISO/IEC 10646}\index{UCS(Universal Coded Character Set)} + +Unicodeは当初、16bitの符号なし整数値でコードポイントを表現する規格であった。この当時、1コードポイントは1文字であり16bitであった。 + +そのような当初の目論見はすぐに破綻し、いまでは1コードポイントは21bit弱(U+0000からU+10FFFF)であり、1コードポイントは1文字を意味しないようになった。複数のコードポイントを組み合わせて1文字が表現されることもあるからだ。 + +Unicodeはコードポイント\index{こどぽいんと@コードポイント}について定めた規格であり、バイト列で文字を表現する規格ではない。Unicodeを元にしたバイト列によって文字を表現するエンコード方式に、UTF--8, UTF--16, UTF--32が存在する。 + +\hypersubsubsection{ch38040201}{UTF--16} +\index{UTF--16} + +UTF--16は16bitの符号なし整数値によってUnicodeのコードポイントを表現するエンコード方式だ。まだUnicodeが16bitのコードポイントですべての文字を表現すると考えていたころに考案されたUCS--2が元になっている。 + +その後、Unicodeのコードポイントが21bit弱に拡張されたので、UCS--2からUTF--16が考案された。 + +UTF--16は16bitを1単位とした符号なし整数で21bit弱のコードポイントを表現するために、1単位で表現できないコードポイントを、サロゲートペア\index{さろげとぺあ@サロゲートペア}と呼ばれる連続した2単位で表現する。 + +そのため、UTF--16の任意の1単位を切り出すと、それは1つのコードポイントを表現するサロゲートペアの片方である可能性があり、文字として壊れてしまう可能性がある。 + +\hypersubsubsection{ch38040202}{UTF--32} +\index{UTF--32} + +UTF--32は32bitの符号なし整数値によってUnicodeのコードポイントを表現するエンコード方式だ。UTF--32の1単位は32bit符号なし整数なので、Unicodeの任意の1コードポイントを表現できる。 + +ただし問題は、Unicodeではもはや1コードポイントは1文字ではないということだ。したがってUTF--32の1単位は1文字ではない。 + +UTF--32の1単位は1コードポイントだが、UTF--32の任意の1単位を切り出すことはできない。 + +連続した複数のコードポイントによって表現された1文字が壊れる可能性があるからだ。 + +\hypersubsubsection{ch38040203}{エンディアンの問題} + +UTF--16とUTF--32は1単位が複数のバイトからなるエンコード方式だ。複数バイトからなる整数にはエンディアン(Endian)\index{えんでいあん@エンディアン}の問題がある。 + +エンディアンとは複数の連続したバイト列の順序のことだ。 + +1バイトが8bitの環境で2バイトの符号なし整数を考えよう。C++には16bit符号なし整数型である\texttt{std::uint16\_t}がある。 + +\begin{lstlisting}[language={C++}] +std::uint16_t value = (@\textcolor{black}{\texttt{0b00000001'00000010}}@) ; +\end{lstlisting} + +2バイトの符号なし整数である\texttt{value}の2つの連続したバイトの上位桁を表現するバイトを上位バイト\index{じよういばいと@上位バイト}\index{ばいと@バイト!じようい@上位〜}、下位桁を表現するバイトを下位バイト\index{かいばいと@下位バイト}\index{ばいと@バイト!かい@下位〜}と呼ぶ。上のコードは上位バイトに1、下位バイトに2が表現されている。このバイト列を直接見てみよう。 + +\begin{lstlisting}[language={C++}] +// byte表示用の関数 +void print( std::byte x ) +{ + std::cout << static_cast(x) ; +} + +int main() +{ + // 上位バイトに1 + // 下位バイトに2 + std::uint16_t value = (@\textcolor{black}{\texttt{0b00000001'00000010}}@) ; + + // 2バイトの配列 + std::byte rep[2] ; + + // バイト列をコピー + std::memcpy( rep, &value, 2 ) ; + + // 上位バイト + print( rep[0] ) ; + // 下位バイト + print( rep[1] ) ; +} +\end{lstlisting} + +筆者の環境では\,\texttt{"21"}\,と表示される。これはつまり、2つのバイトのうち、下位バイトの方が先に配置されているということだ。 + +世の中にはリトルエンディアン(Little Endian)\index{りとるえんでいあん@リトルエンディアン}とビッグエンディアン(Big Endian)\index{びつぐえんでいあん@ビッグエンディアン}がある。これは複数バイトの順序の違いだ。 + +リトルエンディアンは下位バイトから配置する。 + +ビッグエンディアンは上位バイトから配置する。 + +リトルエンディアン環境では、上のプログラムは\,\texttt{"21"}\,と表示する。ビッグエンディアン環境では、\texttt{"12"}\,と表示する。 + +エンディアンの存在により、UTF--16とUTF--32は2つのバイト列表現が存在することになる。 + +\hypersubsubsection{ch38040204}{UTF--8} +\index{UTF--8} + +UTF--8は最も後発のUnicodeのコードポイントの文字エンコードだ。 + +UTF--8は8bitを1単位とし、1単位から4単位までの連続した単位列によってUnicodeの1コードポイントを表現する。 + +UTF--8が1単位だけでコードポイントを表現するとき、下位7bitはASCIIの文字の値に等しい。その点でUTF--8はASCIIと互換性がある。 + +これにより従来ASCIIを使っていたコードやシステムとの親和性が高く、普及した。 + +UTF--8は現在最も普及している文字コードだ。 + +\hypersection{ch3805}{OS} + +C++プログラムが実行できるOSとしては以下のようなものがある。 + +\begin{itemize} +\item + GNU/Linux +\item + Android +\item + FreeBSD +\item + DragonflyBSD +\item + OpenBSD +\item + NetBSD +\item + Apple macOS +\item + Apple iOS +\item + Microsoft Windows +\end{itemize} + +このほかにもOSはさまざまあるが、情報を得るだけでもNDAを結ぶ必要がある表に出てこないOSであったり、実験的すぎたりして、C++を学習する環境としては不適切だ。 + +このうち、Microsoft Windowsを除くOSはUTF--8を使用している。 + +Microsoft WindowsはUTF--16を使用している。ただし、この状況はMicrosoft Windowsは最近UTF--8ロケールを実装したので将来的に変わるだろう。 + +\hypersection{ch3806}{リテラル} +\index{りてらる@リテラル} + +\hypersubsection{ch380601}{通常の文字リテラル} +\index{もじりてらる@文字リテラル}\index{りてらる@リテラル!もじ@文字〜} + +通常の文字リテラルは単一引用符で1つの文字を囲む。 + +\begin{lstlisting}[style=grammar] +'a' +'b' +'c' +\end{lstlisting} + +通常の文字リテラルの型は\texttt{char}\index{char@\texttt{char}}だ。 + +\begin{lstlisting}[language={C++}] +char a = 'a' ; +char b = 'b' ; +char c = 'c' ; +\end{lstlisting} + +文字リテラルには以下のようなエスケープシーケンス\index{えすけぷしけんす@エスケープシーケンス}がある。これは一部の印字不可能な文字や、文法上の理由で直接リテラルの中に書くことができない文字を書けるようにするための代替手段だ。 + +\vskip 1.0zw +\begin{small} +\begin{longtable}[]{@{\,\,}lc@{\,\,}} +\hline%\toprule +\textsf{意味} & \textsf{リテラル}\tabularnewline +\hline%\midrule +\endhead +改行 & \texttt{{\textbackslash}n}\tabularnewline +水平タブ & \texttt{{\textbackslash}t}\tabularnewline +垂直タブ & \texttt{{\textbackslash}v}\tabularnewline +バックスペース & \texttt{{\textbackslash}b}\tabularnewline +キャリッジリターン & \texttt{{\textbackslash}r}\tabularnewline +フォームフィード & \texttt{{\textbackslash}f}\tabularnewline +アラート & \texttt{{\textbackslash}a}\tabularnewline +バックスラッシュ & \texttt{{\textbackslash}}\tabularnewline +疑問符 & \texttt{{\textbackslash}?}\tabularnewline +単一引用符 & \texttt{{\textbackslash}'}\tabularnewline +二重引用符 & \texttt{{\textbackslash}"}\tabularnewline +\hline%\bottomrule +\end{longtable} +\end{small} + +\ifTombow\pagebreak\fi +これを使えば、単一引用符の文字リテラルは +\begin{lstlisting}[language={C++}] +char c = '\'' ; +\end{lstlisting} +と書ける。エスケープシーケンスにはバックスラッシュを使うため、文字リテラルのなかでバックスラッシュを使うには、エスケープシーケンスが必要だ。 + +\begin{lstlisting}[language={C++}] +char c = '\\' ; +\end{lstlisting} + +通常の文字がどのような文字エンコードを使っているかは実装定義だ。 + +そのほかにも文字の数値を直接指定するエスケープシーケンスとして、8進数エスケープシーケンス\index{8しんすうえすけぷしけんす@8進数エスケープシーケンス}\index{えすけぷしけんす@エスケープシーケンス!8しんすう@8進数〜}と16進数エスケープシーケンス\index{16しんすうえすけぷしけんす@16進数エスケープシーケンス}\index{えすけぷしけんす@エスケープシーケンス!16しんすう@16進数〜}がある。 + +\begin{lstlisting}[language={C++}] +char oct = '\101' ; +char hex = '\x41' ; +\end{lstlisting} + +このコードは、8進数で\texttt{101}、16進数で\texttt{41}になる何らかの文字を表現している。もし通常の文字リテラルがASCIIかUTF--8でエンコードされている場合、この文字は\texttt{A}になる。 + +\hypersubsection{ch380602}{ユニバーサルキャラクター名} +\index{ゆにばさるきやらくためい@ユニバーサルキャラクター名}\index{りてらる@リテラル!ゆにばさるきやらくためい@ユニバーサルキャラクター名} + +文字リテラルには特殊なエスケープシーケンスであるユニバーサルキャラクター名(Universal Character name)を使うことができる。 + +\begin{lstlisting}[language={C++}] +\uNNNN +\UNNNNNNNNN +\end{lstlisting} + +文法は\texttt{{\textbackslash}u}に続いて16進数を4文字書くとこれはUnicodeコードポイントにおける\texttt{U+0000NNNN}になる。\texttt{{\textbackslash}U}に続いて16進数を8文字書くと、これはUnicodeコードポイントにおける\texttt{U+NNNNNNNN}になる。 + +\hypersubsection{ch380603}{通常の文字列リテラル} +\index{もじれつりてらる@文字列リテラル}\index{りてらる@リテラル!もじれつ@文字列〜} + +通常の文字列リテラルは二重引用符で文字列を囲む。 + +\begin{lstlisting}[language={C++}] +"abc" ; +"hello" ; +"This is a pen." ; +\end{lstlisting} + +通常の文字列リテラルの型は\texttt{const}な文字型の配列になる。具体的な型としては\texttt{const char [n]}\index{const char [n]@\texttt{const char [n]}}になる。\texttt{n}は文字列のサイズだ。通常の文字列リテラルの中の文字が基本実行文字だけであれば、書かれている文字数+1になる。しかし、この文字数というのも難しい。 + +文字列リテラルが連続している場合、1つにまとめられる。 + +\begin{lstlisting}[language={C++}] +auto s = "abc" "def" ; +\end{lstlisting} +というコードは、 +\begin{lstlisting}[language={C++}] +auto s = "abcdef" ; +\end{lstlisting} +と書くのと同じだ。 + +文字列リテラルの中のエスケープシーケンス\index{えすけぷしけんす@エスケープシーケンス}は対応する文字になる。 + +\begin{lstlisting}[language={C++}] +"\n" ; +\end{lstlisting} +という通常の文字列リテラルは、バックスラッシュとラテンアルファベットnではなく、改行文字1文字になる。 + +通常の文字列リテラルは末尾にnull文字(\texttt{{\textbackslash}0})\index{nullもじ@null文字}\index{{\textbackslash}0@\texttt{{\textbackslash}0}}が付与される。このために、配列のサイズは文字数+1になる。 + +具体的な例では、\texttt{"abc"}\,という通常の文字列リテラルの型は\texttt{const char [4]}になる。これは以下のような配列に等しい。 + +\begin{lstlisting}[language={C++}] +const char s[4] = {'a', 'b', 'c', '\0'} ; +\end{lstlisting} + +\texttt{"hello"}\,の型は\texttt{const char [6]}になる。 + +\begin{lstlisting}[language={C++}] +const char s[6] = {'h', 'e', 'l', 'l', 'o', '\0' } ; +\end{lstlisting} + +\texttt{char}型の配列の初期化に通常の文字列リテラルを使うことができる。 + +\begin{lstlisting}[language={C++}] +char s[6] = "hello" ; +\end{lstlisting} + +配列の添字を書かない場合、文字列リテラルのサイズになる。 + +\begin{lstlisting}[language={C++}] +// char [6] +char s[] = "hello" ; +\end{lstlisting} + +また、文字列リテラルは配列であるので、先頭要素へのポインターに暗黙に型変換される。 + +\begin{lstlisting}[language={C++}] +const char * p = "hello" ; +\end{lstlisting} + +文字列リテラルを\texttt{auto}\index{auto@\texttt{auto}}で変数の初期化子に書くと、型はポインターになる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// const char * +auto pointer = "hello" ; +\end{lstlisting} + +\texttt{decltype(auto)}\index{decltype(auto)@\texttt{decltype(auto)}}という\texttt{auto}と似ているがあまり暗黙の型変換を行わない別のキーワードを使うと、配列へのリファレンス型になる。 + +\begin{lstlisting}[language={C++}] +// const char (&) [6] +decltype(auto) reference = "hello" ; +\end{lstlisting} + +\hypersection{ch3807}{ワイド文字} +\index{わいどもじ@ワイド文字} + +ワイド文字リテラルとワイド文字列リテラルはリテラルにエンコードプレフィクス\texttt{L}\index{L@\texttt{L}}を付ける。 + +\begin{lstlisting}[language={C++}] +// ワイド文字リテラル +L'A' ; +// ワイド文字列リテラル +L"hello" ; +\end{lstlisting} + +ワイド文字リテラルの型は\texttt{wchar\_t}\index{wchar\_t@\texttt{wchar\_t}}、ワイド文字列リテラルの型は\texttt{const wchar\_t [n]}\index{const wchar\_t [n]@\texttt{const wchar\_t [n]}}になる。 + +\begin{lstlisting}[language={C++}] +wchar_t c = L'A' ; +const wchar_t (&ref)[6] = L"hello" ; +\end{lstlisting} + +ワイド文字は失敗した機能だ。まだUnicodeが16bitで世界中の文字を表現できるという妄想にとらわれていたころに提案された時代遅れの実装不可能な機能だ。 + +C++の規格では、「ワイド文字は\texttt{wchar\_t}型のオブジェクト1つがシステムがサポートする任意の1文字を表現可能である」と規定している。そのような文字エンコード方式はいまだかつて存在していない。Unicodeの1コードポイントは1文字を意味しないので、UTF--32を使ってもワイド文字の規定を満たすことはできない。そのため、現在規格準拠の方法でワイド文字を実装しているC++コンパイラーは存在しない。 + +Microsoft Windowsはワイド文字をUTF--16で表現している。それ以外の主要なOSはUTF--32を使っている。 + +\clearpage +\hypersection{ch3808}{UTF--8/UTF--16/UTF--32} + +UTF--8\index{UTF--8}の文字型は\texttt{char8\_t}\index{char8\_t@\texttt{char8\_t}}でエンコードプレフィクスは\texttt{u8}\index{u8@\texttt{u8}}。 + +UTF--16\index{UTF--16}の文字型は\texttt{char16\_t}\index{char16\_t@\texttt{char16\_t}}でエンコードプレフィクスは\texttt{u}\index{u/U@\texttt{u/U}}。 + +UTF--32\index{UTF--32}の文字型は\texttt{char32\_t}\index{char32\_t@\texttt{char32\_t}}でエンコードプレフィクスは\texttt{U}。 + +\begin{lstlisting}[language={C++}] +char8_t utf8 = u8'a' ; +char16_t utf16 = u'(@\ifColor\textcolor[rgb]{0,0.533,0}{あ}\else\textcolor{black}{あ}\fi@)' ; +char32_t utf32 = U'(@\ifColor\textcolor[rgb]{0,0.533,0}{あ}\else\textcolor{black}{あ}\fi@)' ; +\end{lstlisting} + +UTF--8文字型である\texttt{char8\_t}はUTF--8の1単位なので、UTF--8の1単位で表現できる文字しか表現できない。 + +UTF--8/UTF--16/UTF--32の文字列リテラルは、それぞれの\texttt{const}な文字型の配列になる。エンコードプレフィクスは文字リテラルと同じだ。 + +\begin{lstlisting}[language={C++}] +// char8_t [6] +char8_t s1[] = u8"hello" ; +// char16_t [6] +char16_t s2[] = u"hello" ; +// char32_t [6] +char32_t s3[] = U"hello" ; +\end{lstlisting} + +\texttt{"}\textsf{いろは}\texttt{"}\,をそれぞれの文字列リテラルで表現すると以下のようになる。 + +\begin{lstlisting}[language={C++}] +// char8_t [10] +char8_t s1[] = u8"(@\ifColor\textcolor[rgb]{0,0.533,0}{いろは}\else\textcolor{black}{いろは}\fi@)" ; +// char16_t [4] +char16_t s2[] = u"(@\ifColor\textcolor[rgb]{0,0.533,0}{いろは}\else\textcolor{black}{いろは}\fi@)" ; +// char32_t [4] +char32_t s3[] = U"(@\ifColor\textcolor[rgb]{0,0.533,0}{いろは}\else\textcolor{black}{いろは}\fi@)" ; +\end{lstlisting} + +これは以下のように書くのと同じだ。 + +\begin{lstlisting}[language={C++}] +char8_t s1[10] = { 0xe3, 0x81, 0x84, 0xe3, 0x82, 0x8d, 0xe3, 0x81, 0xaf, 0x0 } ; +char16_t s2[4] = { 0x3044, 0x308d, 0x306f, 0x0 } ; +char32_t s3[4] = { 0x3044, 0x308d, 0x306f, 0x0 } ; +\end{lstlisting} + +文字\,\texttt{'}\textsf{い}\texttt{'}\,のUnicodeコードポイントは\texttt{U+3044}で、これはUTF--16/UTF--32では1単位で表現できるが、UTF--8では3単位で\texttt{0xe3, 0x81, 0x84}のように表現する。 + +臼(うす U+81FC)の別字である𦥑(うす U+26951)のコードポイントは16bit符号なし整数で表現できないので、UTF--16ではサロゲートペアを使って2単位表現される。UTF--8では4単位を使って表現される。 + +以下のコードは、 +\begin{lstlisting}[language={C++}] +char8_t s1[] = u8"(@\ifColor\textcolor[rgb]{0,0.533,0}{𦥑}\else\textcolor{black}{𦥑}\fi@)" ; +char16_t s2[] = u"(@\ifColor\textcolor[rgb]{0,0.533,0}{𦥑}\else\textcolor{black}{𦥑}\fi@)" ; +char32_t s3[] = U"(@\ifColor\textcolor[rgb]{0,0.533,0}{𦥑}\else\textcolor{black}{𦥑}\fi@)" ; +\end{lstlisting} +以下のように解釈される。 +\begin{lstlisting}[language={C++}] +char8_t s1[5] = { 0xf0, 0xa6, 0xa5, 0x91, 0x0 } ; +char16_t s2[2] = { 0xd85a, 0xdd51, 0x0 } ; +char32_t s3[2] = { 0x26951, 0x0 } ; +\end{lstlisting} + +文字\,\texttt{'}\textsf{が}\texttt{'}\,はUnicodeコードポイントでは結合済みコードポイントの\texttt{U+304C}で表現できるが、コードポイントU+304B(HIRAGANA LETTER KA)のあとに直ちに続いて、コードポイントU+3099(COMBINING KATAKANA--HIRAGANA VOICED SOUND MARK)を使って表現してもよい。 + +\begin{lstlisting}[language={C++}] +// u8"\u304C" +char8_t ga1[] = u8"(@\ifColor\textcolor[rgb]{0,0.533,0}{が}\else\textcolor{black}{が}\fi@)" ; +// u8"\u304B\u3099" +char8_t ga2[] = u8"(@\ifColor\textcolor[rgb]{0,0.533,0}{か}\else\textcolor{black}{か}\fi@)\u3099" ; +\end{lstlisting} + +これは以下のコードと等しい。 + +\begin{lstlisting}[language={C++}] +char8_t ga1[4] = { 0xe3, 0x81, 0x8c, 0x0 } ; +char8_t ga2[7] = { 0xe3, 0x81, 0x8b, 0xe3, 0x82, 0x99, 0x0 } ; +\end{lstlisting} + +変数\texttt{ga1, ga2}はどちらもUnicodeとして正しい「が」という1文字の表現だ。Unicodeでは複数のコードポイントで1文字を表現することもあるし、意味的に表示的に同じ文字に対して複数の表現方法がある。 + +Apple macOSはUnicodeの正規化として一般的なNFC(Canonical Composition)ではなくNormalization Form D(NFD)を使っているので、濁点や半濁点は必ず分解される。Apple macOSでは\,\texttt{u8"{\textbackslash}u304B{\textbackslash}u3099"}\,が一般的な表現で、それ以外の環境では\,\texttt{u8"{\textbackslash}u304C"}\,が一般的な表現だ。しかし、どちらも意味上は同じ表現だ。 + +Unicodeの奇妙で面白い例は枚挙に暇がない。ここでは日本語を扱う際によくある注意点を説明したが、ほかにも絵文字、デーヴァナーガリー(ヒンディー語、マラーティー語、ネパール語)、モンゴル文字、アラビア文字、ヘブライ文字など扱いの難しい文字がたくさんある。 + +重要な点をまとめると、 + +\begin{itemize} +\item + 文字型の1つのオブジェクトは1文字ではない +\item + 1コードポイントは1文字ではない +\end{itemize} + +\hypersection{ch3809}{生文字列リテラル} +\index{なまもじれつりてらる@生文字列リテラル}\index{りてらる@リテラル!なまもじれつ@生文字列〜} + +エスケープシーケンスは文法上の理由で直接ソースコード上に記述することができない文字を文字リテラルと文字列リテラルに記述できる機能だ。 + +\begin{lstlisting}[language={C++}] +u8"\nは改行文字" ; +\end{lstlisting} + +しかしエスケープシーケンスがあるために、バックスラッシュを普通に使うには、\texttt{{\textbackslash}}\,と書かなければならない。例えば上の文字列リテラルを改行文字に続いて「は改行文字」ではなく、本当に「\texttt{{\textbackslash}n}は改行文字」という文字列にしたい場合、以下のように書かなければならない。 + +\begin{lstlisting}[language={C++}] +u8"\\nは改行文字" ; +\end{lstlisting} + +また、単一引用符\,\texttt{'}\,や二重引用符\,\texttt{"}\,もエスケープシーケンスが必要だ。 + +\begin{lstlisting}[language={C++}] +u8"\'は単一引用符" ; +u8"\"(@\ifColor\textcolor[rgb]{0,0.533,0}{は二重引用符}\else\textcolor{black}{は二重引用符}\fi@)" ; +\end{lstlisting} + +また、以下のような内容の文字列をリテラルとして書きたい場合、 +\begin{lstlisting}[style=terminal] +foo +bar +baz +\end{lstlisting} +以下のように書かなければならない。 +\begin{lstlisting}[language={C++}] +"foo\nbar\nbaz" ; +\end{lstlisting} + +このようなわかりにくい記述ではなく、ソースコードに書いたままの文字列を文字列として扱いたい。そのための機能が生文字列リテラル(Raw String Literal)だ。 + +生文字列リテラルは以下のような文法で書く。 +\index{R@\texttt{R}} + +\begin{lstlisting}[style=grammar] +R"(...)" +\end{lstlisting} + +例えば以下のように書くと、 +\begin{lstlisting}[language={C++}] +R"(foo +bar +baz)" ; +\end{lstlisting} +\ifTombow\pagebreak\fi +以下のような文字列リテラルと同じ意味になる。 +\begin{lstlisting}[language={C++}] +"foo\nbar\nbaz" ; +\end{lstlisting} + +エスケープシーケンスも書いたままに文字列となる。 + +\begin{lstlisting}[language={C++}] +R"( +'は単一引用符 +"(@\ifColor\textcolor[rgb]{0,0.533,0}{は二重引用符}\else\textcolor{black}{は二重引用符}\fi@) +(@\ifColor\textcolor[rgb]{0,0.533,0}{{\textbackslash}nは改行文字}\else\textcolor{black}{{\textbackslash}nは改行文字}\fi@) +(@\ifColor\textcolor[rgb]{0,0.533,0}{)}\else\textcolor{black}{)}\fi@)" ; +\end{lstlisting} + +これは以下の文字列リテラルと同じ意味だ。 + +\begin{lstlisting}[language={C++}] +"\n\'は単一引用符\n(@\ifColor\textcolor[rgb]{0,0.533,0}{{\textbackslash}"は二重引用符}\else\textcolor{black}{{\textbackslash}"は二重引用符}\fi@)\n\\nは改行文字\n" +\end{lstlisting} + +\hypersection{ch3810}{文字列の表現方法} +\index{もじれつ@文字列!ひようげん@表現} + +文字列というのは文字型の配列で表現される。文字列を表現するには、配列の先頭へのポインターと配列のサイズが必要になる。 + +\hypersubsection{ch381001}{null終端文字列} +\index{nullしゆうたんもじれつ@null終端文字列} + +C++の文字列リテラルは、末尾にnull文字\index{nullもじ@null文字}が付与された\texttt{const}な文字型への配列だ。 + +\begin{lstlisting}[language={C++}] +"abc" ; +\end{lstlisting} +という文字列リテラルは型とその値としては +\begin{lstlisting}[language={C++}] +const char st[4] = { 'a', 'b', 'c', '\0' } ; +\end{lstlisting} +になる。 + +null終端文字列とはC言語から使われている文字列の表現方法だ。文字型の配列の末尾にnull文字を番兵として配置することで文字列の終端を表現している。C言語では文字列は文字型へのポインターとして表現される。ポインターが指す配列のサイズはわからないが、妥当な文字列はnull終端されているので、ポインターをインクリメントしていけばいずれnull文字が現れる。そこが文字列の終わりだ。これによって文字列のサイズもわかる。 + +\ifTombow\pagebreak\fi +例えば、以下はC言語でよく書かれる典型的文字列を処理する関数だ。 + +\begin{lstlisting}[language={C++}] +void process_string( const char * str ) +{ + // strが指す配列のサイズを取得 + auto str_size = std:strlen( str ) ; + // 残りの処理 +} +\end{lstlisting} + +\texttt{std::strlen}\index{strlen@\texttt{strlen}}はポインターが指し示すnull終端された配列のnull文字を除くサイズ\index{もじれつ@文字列!さいず@サイズ}を返す。以下のような実装だ。 + +\begin{lstlisting}[language={C++}] +std::size_t strlen( const char * s ) +{ + auto i = s ; + while ( *i != '\0' ) + { ++i ; } + return i - s ; +} +\end{lstlisting} + +ここで言う「文字列のサイズ」とは、ポインターが指し示す文字型の配列の要素数であって、文字数ではない。 + +null終端文字列は文字型へのポインター1つだけなので取り回しがよい。ただし、文字列のサイズは実行時に文字列の先頭から末尾までイテレートして計算しなければならない。これは文字列の長さに比例したオーダー\(O(N)\)の処理量がかかる。 + +\hypersubsection{ch381002}{std::basic\texttt{\_}string} +\index{basic\_string@\texttt{basic\_string}} + +いままで文字列の型として使ってきた\texttt{std::string}\index{string@\texttt{string}}は、実はクラステンプレートで実装されている。 + +\begin{lstlisting}[language={C++}] +namespace std { + template< + typename charT, + typename traits = char_traits, + typename Allocator = allocator + > + class basic_string ; +} +\end{lstlisting} + +テンプレートパラメーターのうち、\texttt{charT}が文字型、\texttt{traits}は文字を処理するための補助的なライブラリ、\texttt{Allocator}がアロケーターだ。 + +\ifTombow\pagebreak\fi +これに対し、以下のようなエイリアスが存在する。 + +\begin{lstlisting}[language={C++}] +namespace std { + using string = basic_string ; + using u8string = basic_string ; + using u16string = basic_string ; + using u32string = basic_string ; + using wstring = basic_string ; +} +\end{lstlisting} + +それぞれの文字型に対応した\texttt{basic\_string}のクラスだ。 + +これに対して、ユーザー定義リテラルという機能を使い、文字列リテラルのサフィックスに\texttt{s}\index{s@\texttt{s}}を付けることで、文字列リテラルを対応する\texttt{basic\_string}のクラス型に変換できる。 + +\begin{lstlisting}[language={C++}] +// string +auto str = "hello"s ; +// u8string +auto u8str = u8"hello"s ; +// u16string +auto u16str = u"hello"s ; +// u32string +auto u32str = U"hello"s ; +// wstring +auto wstr = L"hello"s ; +\end{lstlisting} + +ユーザー定義リテラルの詳細については本書では詳しく説明しないが、演算子のオーバーロードと同じだ。演算子をオーバーロードするようにリテラル演算子をオーバーロードする。 +\index{ゆざていぎりてらる@ユーザー定義リテラル} + +\begin{lstlisting}[language={C++}] +std::string operator ""s( const char * ptr, std::size_t n ) +{ return std::string( ptr, n ) ; } +std::u8string operator ""s( const char8_t * ptr, std::size_t n ) +{ return std::u8string( ptr, n ) ; } +std::u16string operator ""s( const char16_t * ptr, std::size_t n ) +{ return std::u16string( ptr, n ) ; } +std::u32string operator ""s( const char32_t * ptr, std::size_t n ) +{ return std::u32string( ptr, n ) ; } +std::wstring operator ""s( const wchar_t * ptr, std::size_t n ) +{ return std::wstring( ptr, n ) ; } +\end{lstlisting} + +ユーザー定義リテラルを正しく実装するには複雑なルールがある。例えばユーザー定義のサフィックス名はアンダースコア1つから始まっていなければならないなどだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// OK +int operator "" _abc( unsigned long long int ) ; +// エラー、 アンダースコア1つから始まっていない +int operator ""abc( unsigned long long int ) ; +\end{lstlisting} + +これは将来の拡張のためにアンダースコアから始まらないサフィックス名をC++規格が予約しているためだ。 + +\texttt{basic\_string}による文字列の表現方法は、文字型配列の先頭要素へのポインター、文字型配列のサイズ、アロケーターだ。 + +\begin{lstlisting}[language={C++}] +template < + typename charT, + typename traits = char_traits, + typename Allocator = allocator +> +class basic_string +{ + charT * ptr ; + std::size_t size ; + Allocator alloc ; +} ; +\end{lstlisting} + +あるいは、配列のサイズを表現するために、配列の最後の要素の1つ次のポインターを使っているかもしれない。 + +\begin{lstlisting}[language={C++}] + charT * ptr ; + charT * last ; + Allocator alloc ; +\end{lstlisting} + +\texttt{std::vector}と同じで、どちらの方が効率がいいかはアーキテクチャにより異なる。 + +\texttt{basic\_string}は文字列を表現するためのストレージを所有するクラスだ。コンストラクターでストレージを動的確保し、デストラクターで解放する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 少なくともchar [5]を格納できるだけのストレージを動的確保する + std::string hello("hello") ; + // helloが破棄される + // デストラクターはストレージを解放する +} +\end{lstlisting} + +コピーはストレージの動的確保、ムーブはストレージの所有権の移動になる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + std::string s1 = "hello" ; + // コピー、 動的確保 + std::string s2 = s1 ; + // ムーブ、 所有権の移動 + std::string s3 = std::move(s1) ; +} +\end{lstlisting} + +\hypersubsection{ch381003}{std::basic\texttt{\_}string\texttt{\_}view} +\index{basic\_string\_view@\texttt{basic\_string\_view}} + +\texttt{basic\_string\_view}はストレージを所有しないクラスだ。以下のような宣言になる。 + +\begin{lstlisting}[language={C++}] +namespace std { + template < + typename charT, + typename traits = char_traits + > + class basic_string_view ; +} +\end{lstlisting} + +その実装は文字型へのポインター2つか、文字型へのポインター1つと配列のサイズを保持する整数型になる。 + +\begin{lstlisting}[language={C++}] + charT * first ; + charT * last ; +\end{lstlisting} +もしくは、 +\begin{lstlisting}[language={C++}] + charT * first ; + std::size_t size ; +\end{lstlisting} + +\texttt{basic\_string\_view}には\texttt{basic\_string}と対になる各文字型に対する特殊化がある。 + +\begin{lstlisting}[language={C++}] +namespace std { + using string_view = basic_string_view ; + using u8string_view = basic_string_view ; + using u16string_view = basic_string_view ; + using u32string_view = basic_string_view ; + using wstring_view = basic_string_view ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +さらに、各\texttt{basic\_string}に対するユーザー定義リテラルサフィックス\texttt{sv}\index{sv@\texttt{sv}}がある。 + +\begin{lstlisting}[language={C++}] +// string_view +auto str = "hello"sv ; +// u8string_view +auto u8str = u8"hello"sv ; +// u16string_view +auto u16str = u"hello"sv ; +// u32string_view +auto u32str = U"hello"sv ; +// wstring_view +auto wstr = L"hello"sv ; +\end{lstlisting} + +\texttt{basic\_string\_view}は文字列がnull終端文字列と\texttt{basic\_string}のどちらで表現されていても問題なく受け取るためのクラスだ。この2つの文字列の表現を別々に使う場合、文字列を受け取る関数は、 +\begin{lstlisting}[language={C++}] +void process_string( const char * s ) +{ + // 文字列に対する処理 +} + +void process_string( const std::string & s ) +{ + // 文字列に対する上と同じ処理 +} + +int main() +{ + auto null_terminated_string = "hello" ; + auto basic_string = "hello"s ; + + // const char * + process_string( null_terminated_string ) ; + // const std::string & + process_string( basic_string ) ; +} +\end{lstlisting} +のようにほとんど同じ関数を2つ書かなければならない。\texttt{basic\_string\_view}を使えば、 +\begin{lstlisting}[language={C++}] +void process_string( std::string_view s ) +{ + // 文字列に対する処理 +} + +(@\ifTombow\pagebreak\fi@) +int main() +{ + auto null_terminated_string = "hello" ; + auto basic_string = "hello"s ; + + // どちらも同じ関数を呼ぶ + process_string( null_terminated_string ) ; + process_string( basic_string ) ; +} +\end{lstlisting} +のように、どちらの文字列表現を使っても1つの関数を書くだけで済む。 + +\texttt{basic\_string\_view}はストレージを所有しないので関数の引数として使うときはリファレンスで取る必要はない。 + +\begin{lstlisting}[language={C++}] +// リファレンスで取る必要はない +void f( const std::string_view & ref ) +// これでいい +void g( std::string_view obj ) ; +\end{lstlisting} + +\hypersection{ch3811}{文字列の操作} +\index{もじれつ@文字列!そうさ@操作} + +\hypersubsection{ch381101}{null終端文字列の操作} + +null終端文字列\index{nullしゆうたんもじれつ@null終端文字列}は文字列の先頭となる文字型へのポインター型のオブジェクト1つで表現されるので、文字型の配列のサイズを取得するにも、いちいちnull文字が見つかるまでポインターをインクリメントしていく必要がある。この処理をやってくれるのが\texttt{std::strlen}\index{strlen@\texttt{strlen}}だ。 + +\begin{lstlisting}[language={C++}] +void f( const char * ptr ) +{ + auto size = std::strlen( ptr ) ; +} +\end{lstlisting} + +文字列リテラルの型は\texttt{const}な文字型の配列なので、文字列を変更することができない。 + +\begin{lstlisting}[language={C++}] +const char * ptr = "abc" ; +// エラー +ptr[0] = 'x' ; +\end{lstlisting} + +\ifTombow\pagebreak\fi +文字型への配列ならば変更できる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + char s[] = "abc" ; + s[0] = 'x' ; + // sは{'x','b','c','\0'} +} +\end{lstlisting} + +文字の長さを短くしたい場合は、終端をnull文字にする。 + +\begin{lstlisting}[language={C++}] +int main() +{ + char s[] = "abc" ; + s[1] = '\0' ; + // sは{'a','\0', 'c','\0'} +} +\end{lstlisting} + +この変数\texttt{s}の型は\texttt{char [4]}だが、null終端文字列としてのサイズは1だ。 + +文字列のサイズを長くするには、当然大きな配列が必要になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + char s[10] = "abc" ; + s[3] = 'd' ; + s[4] = 'e' ; + s[5] = 'f' ; + s[6] = '\0' ; +} +\end{lstlisting} + +このコードで、変数\texttt{s}は最終的に\,\texttt{"abcdef"}\,という文字列になる。最後のnull文字による終端を忘れてはならない。 + +ここで、配列\texttt{s}の要素数は7以上でなければならない。最終的なnull終端文字列を表現するには最低でも\texttt{char [7]}が必要だからだ。 + +例えば2つのnull終端文字列を結合する場合で、どちらも\texttt{const}であったり、十分なサイズがなかった場合、2つの文字列を保持できるサイズのメモリーを確保して、コピーしなければならない。 + +\begin{lstlisting}[language={C++}] +// s1, s2を結合して使う関数 +void concat_str( const char * s1, const char * s2 ) +{ + // 2つの文字列のサイズの合計 + null文字 + auto size = std::strlen( s1 ) + std::strlen( s2 ) + 1 ; + // 文字列を保持するメモリーを確保する + char * ptr = new char[size] ; + + char * i = ptr ; + // s1をコピー + while ( *s1 != '\0' ) + { + *i = *s1 ; + ++i ; ++s1 ; + } + // s2をコピー + while ( *s2 != '\0' ) + { + *i = *s2 ; + ++i ; ++s2 ; + } + // null終端する + *i = '\0' ; + + // 結合した文字列を使う + + // 使い終わったのでメモリーを解放する + delete[] ptr ; +} +\end{lstlisting} + +C言語の標準ライブラリにはnull終端文字列を扱うためのライブラリが多数ある。C言語の標準ライブラリを使えば、上のコードは以下のように書ける。 + +\begin{lstlisting}[language={C++}] +void concat_str( const char * s1, const char * s2 ) +{ + auto size = std::strlen( s1 ) + std::strlen( s2 ) + 1 ; + char * ptr = new char[size] ; + + // s1をptrにコピー + std::strcpy( ptr, s1 ) ; + // ptrとs2を結合 + std::strcat( ptr, s2 ) ; + + delete[] ptr ; +} +\end{lstlisting} + +\hypersubsection{ch381102}{basic\texttt{\_}stringの操作} +\index{basic\_string@\texttt{basic\_string}!そうさ@操作} + +\texttt{basic\_string}はストレージを所有するクラスだ。ストレージの解放と確保を自動でやってくれる上に、便利な操作がたくさんある。 + +\ifTombow\pagebreak\fi +例えば上の\texttt{concat\_str}を\texttt{basic\_string}で実装すると以下のようになる。 + +\begin{lstlisting}[language={C++}] +void concat_str( const char * s1, const char * s2 ) +{ + std::string s = s1 ; + s += s2 ; + + // sを使う + // sは自動的に破棄される +} +\end{lstlisting} + +C++の作法に従って、引数\texttt{s1, s2}をnull終端文字列文字型ではなく、\texttt{basic\_string\_view}にすると以下のようになる。 + +\begin{lstlisting}[language={C++}] +void concat_str( std::string_view s1, std::string_view s2 ) +{ + std::string s = s1 ; + s += s2 ; + + // sを使う + // sは自動的に破棄される +} +\end{lstlisting} + +\vskip -1.0zw +\hypersubsubsection{ch38110201}{初期化} +\index{basic\_string@\texttt{basic\_string}!しょよきか@初期化} + +\texttt{basic\_string}はnull終端文字列、\texttt{basic\_string\_view}、\texttt{basic\_string}で初期化、代入できる。 + +\begin{lstlisting}[language={C++}] +// null終端文字列 +std::string s1("hello") ; +// basic_string_view +std::string s2("hello"sv) ; +// basic_string +std::string s3("hello"s) ; +\end{lstlisting} + +\vskip -1.0zw +\hypersubsubsection{ch38110202}{結合} +\index{basic\_string@\texttt{basic\_string}!けつごう@結合} + +\texttt{basic\_string}は\texttt{operator +}\index{\protect{+}@\texttt{\protect{+}}}で文字列を結合できる。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[language={C++}] +// "foobar" +auto s = "foo"s + "bar"s ; +\end{lstlisting} + +\texttt{operator +=}\,\index{\protect{+=}@\texttt{\protect{+=}}}は第一オペランドを書き換える。 + +\begin{lstlisting}[language={C++}] +auto s = "foo"s ; +s += "bar"s ; +// sは"foobar" +\end{lstlisting} + +\texttt{basic\_string::append(s)}\index{append@\texttt{append}}というメンバー関数もある。 + +\begin{lstlisting}[language={C++}] +auto s = "foo"s ; +s.append("bar"sv) ; +// sは"foobar" +\end{lstlisting} + +\vskip -1.0zw +\hypersubsubsection{ch38110203}{イテレーター} +\index{basic\_string@\texttt{basic\_string}!いてれた@イテレーター} + +\texttt{basic\_string}にはイテレーターがある。イテレーターの取得方法は\texttt{std::vector}と同じだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto s = "hello"s ; + for ( auto i = s.begin() ; i != s.end() ; ++i ) + { + std::cout << *i ; + } +} +\end{lstlisting} + +これは以下のようにも書ける。 + +\begin{lstlisting}[language={C++}] +for ( auto i = std::begin(s) ; i != std::end(s) ; ++i ) +{ + std::cout << *i ; +} +\end{lstlisting} + +\vskip -1.0zw +\hypersubsubsection{ch38110204}{部分文字列の検索} +\index{basic\_string@\texttt{basic\_string}!けんさく@検索} + +イテレーターがあるので、\texttt{basic\_string}は汎用的なアルゴリズムに渡すことができる。例えばある文字列がその一部の別の文字列を含むかどうかを調べる場合、以下のように書ける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "quick brown fox jumps over the lazy dog."s ; + auto word = "fox"s ; + + auto i = std::search( std::begin(text), std::end(text), std::begin(word), std::end(word) ) ; + + if ( i != std::end(text) ) + std::cout << "fox found!\n"sv ; + else + std::cout << "no fox...\n"sv ; +} +\end{lstlisting} + +イテレーターを使うのは煩わしいが、C++20では\texttt{Range}ライブラリ\index{Range@\texttt{Range}}が追加され、以下のように書ける予定だ。 + +\begin{lstlisting}[language={C++}] +auto r = std::ranges::search( text, word ) ; +if ( !std::ranges::empty(r) ) + // ... +\end{lstlisting} + +名前空間を省くと、\texttt{!empty(search(text, word))} になるが、これでもまだわかりづらい。そこで\texttt{basic\_string::find}\index{find@\texttt{find}}がある。これは\,\texttt{}\,の\texttt{std::find}とは別物で、文字列から部分文字列を探し、その部分文字列に一致する文字へのインデックスを返す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "quick brown fox jumps over the lazy dog."s ; + + // 12 + auto fox = text.find("fox"sv) ; + // 32 + auto dog = text.find("dog"sv) ; +} +\end{lstlisting} + +文字列\,\texttt{"fox"}\,に一致する部分文字列の先頭\,\texttt{'f'}\,の文字型の値へのインデックスは\texttt{12}で、\texttt{"dog"}\,の\,\texttt{'d'}\,は\texttt{36}だ。この結果は、上のソースコードに使っている文字が1文字につき1文字型の値を使うためだ。通常は文字数と連続した文字型の要素へのインデックスは等しくならない。 + +例えば以下のコードを実行すると、 +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = u8"(@\ifColor\textcolor[rgb]{0,0.533,0}{すばしっこい茶色の狐がノロマな犬を飛び越した。}\else\textcolor{black}{すばしっこい茶色の狐がノロマな犬を飛び越した。}\fi@)"s ; + + auto fox = text.find(u8"(@\ifColor\textcolor[rgb]{0,0.533,0}{狐}\else\textcolor{black}{狐}\fi@)"sv) ; + auto dog = text.find(u8"(@\ifColor\textcolor[rgb]{0,0.533,0}{犬}\else\textcolor{black}{犬}\fi@)"sv) ; + + std::cout << "fox: "sv << fox << "\n"sv + << "dog: "sv << dog ; +} +\end{lstlisting} +以下のように出力される。 +\begin{lstlisting}[style=terminal] +fox: 27 +dog: 45 +\end{lstlisting} + +もし部分文字列が見つからない場合、\texttt{basic\_string::npos}\index{npos@\texttt{npos}}が返る。\texttt{npos}は``no position''という意味で、\texttt{-1}と等しい。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "quick brown fox jumps over the lazy dog."s ; + + // 見つからない場合はnposが返る。 + auto index = text.find("abc"sv) ; + + // 見つからなかった判定 + if ( index != std::string::npos ) + // 見つかった + std::cout << "found." ; + else + // 見つからなかった + std::cout << "not found." ; +} +\end{lstlisting} + +この場合、変数\texttt{text}に文字列\,\texttt{"abc"}\,はないので、\texttt{npos}が返る。\texttt{npos}が返ったかどうかは\texttt{npos}と比較すればわかる。\texttt{npos}は\,\texttt{-1}と等しいので、以下のようにも書ける。 + +\begin{lstlisting}[language={C++}] +if ( index != -1 ) + // ... +\end{lstlisting} + +\texttt{find}の亜種として、\texttt{rfind}\index{rfind@\texttt{rfind}}がある。 + +\texttt{find}は最初の部分文字列を見つけるが、\texttt{rfind}は最後の部分文字列を見つける。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "word word word"s ; + + // 0 + auto first = text.find("word"sv) ; + // 10 + auto last = text.rfind("word"sv) ; +} +\end{lstlisting} + +\texttt{find}は最初に一致した部分文字列の先頭へのインデックスを返すので、この場合\texttt{0}が返る。\texttt{rfind}は最後に見つかった部分文字列の先頭へのインデックスを返すので、この場合\texttt{10}になる。 + +C++20では、\texttt{starts\_with/ends\_with}\index{starts\_with@\texttt{starts\_with}}\index{ends\_with@\texttt{ends\_with}}という2つの便利なメンバー関数が追加される。 + +\texttt{starts\_with(str)}は文字列が部分文字列\texttt{str}で始まっている場合に\texttt{true}を返す。そうでない場合は\texttt{false}を返す。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "aa bb cc"s ; + + // true + bool b1 = text.starts_with("a"sv) ; + bool b2 = text.starts_with("aa"sv) ; + bool b3 = text.starts_with("aa "sv) ; + + // false + bool b4 = text.starts_with("b"sv) ; + bool b5 = text.starts_with("aaa"sv) ; +} +\end{lstlisting} + +\texttt{ends\_with(str)}は文字列が部分文字列\texttt{str}で終わっている場合に\texttt{true}を返す。そうでない場合は\texttt{false}を返す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "aa bb cc"s ; + + // true + bool b1 = text.ends_with("c"sv) ; + bool b2 = text.ends_with("cc"sv) ; + bool b3 = text.ends_with(" cc "sv) ; + + // false + bool b4 = text.ends_with("b"sv) ; + bool b5 = text.ends_with("ccc"sv) ; +} +\end{lstlisting} + +\vskip -1.0zw +\hypersubsubsection{ch38110205}{その他のメンバー関数} + +\texttt{size}, \texttt{empty}, \texttt{resize}, \texttt{capacity}, \texttt{reserve}, \texttt{shrink\_to\_fit}, \texttt{clear}といったおなじみのメンバー関数もある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::string s ; + s.size() ; + s.resize(10) ; + s.clear() ; +} +\end{lstlisting} + +\vskip -1.0zw +\hypersubsubsection{ch38110206}{文字列の挿入} +\index{basic\_string@\texttt{basic\_string}!そうにゆう@挿入} + +文字列の挿入は\texttt{insert(pos, str)}\index{insert@\texttt{insert}}で行える。 + +\texttt{pos}は挿入場所へのインデックスで、\texttt{str}は挿入する文字列だ。 + +文字列の先頭や末尾への挿入は以下のようになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "cat"s ; + text.insert( 0, "long "sv ) ; + // textは"long cat" + text.insert( text.size(), " is loong."sv ) ; + // textは"long cat is loong." +} +\end{lstlisting} + +末尾への挿入は文字列の結合と同じ効果だ。 + +インデックスで中間に挿入するのは以下のとおり。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "big cat"s ; + text.insert( text.find("cat"sv), "fat "sv ) ; + // textは"big fat cat" +} +\end{lstlisting} + +これは\texttt{text.find("cat"sv)}でまず部分文字列\,\texttt{"cat"}\,の先頭へのインデックスを探し、そこに文字列\,\texttt{"fat "}\,を挿入している。結果として変数\texttt{text}は\,\texttt{"big fat cat"}\,となる。 + +\hypersubsubsection{ch38110207}{部分文字列の削除} +\index{basic\_string@\texttt{basic\_string}!さくじよ@削除} + +文字列から部分文字列を削除するには\texttt{erase(pos, n)}\index{erase@\texttt{erase}}を使う。\texttt{pos}は削除すべき先頭のインデックスで、\texttt{n}は削除すべきインデックス数だ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "dirty cat"s ; + auto dirty = "dirty "sv ; + text.erase( 0, dirty.size() ) ; + // textは"cat" +} +\end{lstlisting} + +このプログラムは文字列\,\texttt{"dirty cat"}\,から\,\texttt{"dirty "}\,を削除し、\texttt{"cat"}\,にする。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "big fat cat"s ; + auto fat = "fat "sv ; + text.erase( text.find(fat), fat.size() ) ; + // textは"big cat" +} +\end{lstlisting} + +このプログラムは文字列\,\texttt{"big fat cat"}\,から部分文字列\,\texttt{"fat"}\,を検索し、その先頭から変数\texttt{fat}のサイズ文の部分文字列を削除する。結果として変数\texttt{text}は\,\texttt{"big cat"}\,になる。 + +先頭から末尾までを削除すると、\texttt{clear()}と同じ意味になる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "abc"s ; + text.erase( 0, text.size() ) ; + // text.empty() == true +} +\end{lstlisting} + +\vskip -1.0zw +\hypersubsubsection{ch38110208}{部分文字列の置換} +\index{basic\_string@\texttt{basic\_string}!ちかん@置換} + +\texttt{replace(pos, n1, str)}\index{replace@\texttt{replace}}を使うと、文字列のインデックス\texttt{pos}から\texttt{n1}個までの文字型の値を、文字列\texttt{str}で置き換える。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "ugly cat"s ; + auto ugly = "ugly"sv ; + auto pretty = "pretty"sv ; + text.replace( text.find(ugly), ugly.size(), pretty ) ; + // textは"pretty cat" +} +\end{lstlisting} + +このコードは、文字列\texttt{text}から部分文字列\,\texttt{"ugly"}\,を探し、その先頭へのインデックスと文字列\,\texttt{"ugly"}\,のサイズを指定することで、部分文字列\texttt{"ugly"}を、文字列\texttt{pretty}の値である\texttt{"pretty"}\,に置換する。結果として\texttt{text}は\texttt{"pretty cat"}\,になる。 + +\hypersubsubsection{ch38110209}{その他の推奨できない操作} + +\texttt{basic\_string}にはこのほかにさまざまな、現代では推奨できない操作がある。 + +例えば\texttt{operator []}で文字列をインデックスでアクセスできる。これは基本実行文字セットに対しては動く。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "abc"s ; + // 'a' + std::cout << text[0] ; + // 'b' + std::cout << text[1] ; + + text[0] = 'x' ; + // textは"xbc" +} +\end{lstlisting} + +これは、\texttt{basic\_string}が設計された時代は、1文字型は1文字を表現できるという前提があったからだ。 + +現代の文字列の表現方法であるUnicodeとUTFによるエンコードではこの前提が成り立たない。例えば、最もよく使われているUTF--8の場合、以下のようになる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = u8"(@\ifColor\textcolor[rgb]{0,0.533,0}{いろは}\else\textcolor{black}{いろは}\fi@)"s ; + // 0xe3 + auto c = text[0] ; +} +\end{lstlisting} + +\texttt{text}のインデックス\texttt{0}にあたる文字型の値は\texttt{u8'}\textsf{い}\texttt{'}ではない。UTF--8は文字「い」を文字型1つで表現できないからだ。\texttt{u8"}\textsf{いろは}\texttt{"}というUTF--8文字列リテラルはすでに学んだように、以下のように表現される。 + +\begin{lstlisting}[language={C++}] +// u8"いろは" +char8_t iroha[10] = { 0xe3, 0x81, 0x84, 0xe3, 0x82, 0x8d, 0xe3, 0x81, 0xaf, 0x0 } ; +\end{lstlisting} + +文字「い」をUTF--8で表現するためには、\texttt{char8\_t}型の値が3つ必要で、\texttt{0xe3, 0x81, 0x84}というシーケンスでなければならない。そのため、個々の文字型の値をインデックスでアクセスしても意味がない。また、\texttt{size()}は文字数を返すのではなく、インデックス数を返す。 + +\texttt{basic\_string}にはリバースイテレーターを返す\texttt{rbegin/rend}もあるが、Unicodeでエンコードされた文字列では、複数の値のシーケンスで1文字を表現しているため、単に値単位で逆順のイテレートすることは、技術的には可能だが、意味的には壊れてしまう。 + +\texttt{basic\_string}には最初に発見したいずれかの文字へのインデックスを返す\texttt{find\_first\_of}\index{find\_first\_of@\texttt{find\_first\_of}}がある。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "quick brown fox jumps over the lazy dog."s ; + // 3 + auto i = text.find_first_of("abc"sv) ; +} +\end{lstlisting} + +\texttt{i}は\texttt{3}になる。なぜならば、\texttt{find\_first\_of("abc"sv)}は\texttt{a}, \texttt{b}, \texttt{c}のうちいずれかの文字である最初のインデックスを返すからだ。 + +この機能はUnicodeでは使えない。というのも1文字型で1文字を表現できないからだ。 + +\hypersubsection{ch381103}{basic\texttt{\_}string\texttt{\_}viewの操作} +\index{basic\_string\_view@\texttt{basic\_string\_view}!そうさ@操作} + +\texttt{basic\_string\_view}は\texttt{basic\_string}とほぼ同じ操作が行える。ただし、\texttt{basic\_string\_view}は書き換えることができないので、一部の操作が使えない。\texttt{append, insert, erase, replace}は使えない。\texttt{basic\_string\_view}同士の\texttt{operator +}もない。 + +C++20では、文字列の先頭と末尾を指定したインデックス数分削ることはできる。 + +先頭を削るには\texttt{remove\_prefix(i)}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "quick brown fox jumps over the lazy dog." ; + text.remove_prefix( "quick "sv.size() ) ; + // textは"brown fox jumps over the lazy dog." + text.remove_prefix( "brown"sv.size() ) ; + // textは"fox jumps over the lazy dog." +} +\end{lstlisting} + +末尾を削るには\texttt{remove\_suffix(i)}\index{remove\_suffix@\texttt{remove\_suffix}}を使う。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto text = "quick brown fox jumps over the lazy dog." ; + text.remove_suffix( " dog."sv.size() ) ; + // textは"quick brown fox jumps over the lazy" + text.remove_suffix( " lazy".sv.size() ) ; + // textは"quick brown fox jumps over the" +} +\end{lstlisting} + diff --git a/TeX/043-random.tex b/TeX/043-random.tex new file mode 100644 index 0000000..c2cd034 --- /dev/null +++ b/TeX/043-random.tex @@ -0,0 +1,1202 @@ +\hyperchapter{ch39}{乱数}{乱数} +\index{らんすう@乱数} + +乱数はプログラミングにおいてよく使う。例えば6面ダイスをプログラムで実装するには、1, 2, 3, 4, 5, 6までのいずれかの目を出す。 + +\begin{lstlisting}[style=terminal] +$ ./dice +1 +5 +$ ./dice +3 +5 1 6 +$ ./dice +10 +5 1 6 6 1 6 6 2 4 2 +\end{lstlisting} + +このプログラム\texttt{dice}は標準入力から整数型の値\texttt{n}を取り、1, 2, 3, 4, 5, 6のいずれかをそれぞれ\(\frac{1}{6}\)の確率で\texttt{n}個出力する。 + +まずこの\texttt{dice}プログラムを作ることを目標にC++の乱数アルゴリズムである\,\texttt{}\,\index{random@\texttt{random}}の使い方を学んでいく。 + +\hypersection{ch3901}{疑似乱数} +\index{ぎじらんすう@擬似乱数}\index{らんすう@乱数!ぎじ@擬似〜} + +コンピューターで使われる乱数のほとんどは疑似乱数と呼ばれる方法で生成されている。さまざまなアルゴリズムがあるが、とても簡単に理解できる疑似乱数のアルゴリズムに、線形合同法(Linear congruential generator)\index{せんけいごうどうほう@線形合同法}がある。 + +\ifTombow\pagebreak\fi +線形合同法ではいまの乱数を\(X_n\)、次の乱数を\(X_{n+1}\)とすると、\(X_{n+1}\)は以下のように求められる。 +\[ +X_{n+1} = (a \times X_{n} + c) \bmod m +\] + +たとえば\(a = 3, c = 5, m = 2^{sizeof(std::uint32_t) \times 8}\)の場合で、\(X_0 = 0\)のとき、 +\begin{gather*} +X_0 = 0 \\ +X_1 = 3 \times 0 + 5 \bmod 2^{32}-1 = 5 \\ +X_2 = 3 \times X_1 + 5 \bmod 2^{32}-1 = 20 \\ +X_3 = 3 \times X_2 + 5 \bmod 2^{32}-1 = 65 +\end{gather*} + +「これはぜんぜん乱数ではない。予測可能じゃないか」と考えるかもしれない。しかし中でどのように乱数が生成されているかわからなければ、外部からは乱数のように見える。これが擬似乱数の考え方だ。 + +\hypersection{ch3902}{乱数エンジン} +\index{らんすうえんじん@乱数エンジン} + +\texttt{乱数エンジン}は生の乱数を生成するライブラリだ。クラスで実装されている。 + +乱数エンジンはメンバー関数\texttt{min()}\index{min()@\texttt{min()}}で最小値を、メンバー関数\texttt{max()}\index{max()@\texttt{max()}}で最大値を、\texttt{operator()}\index{operator()@\texttt{operator()}}で最小値から最大値の間の乱数を返す。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +void f( Engine & e ) +{ + // 最小値 + auto a = e.min() ; + // 最大値 + auto b = e.max() ; + // 乱数 + auto r1 = e() ; + // 次の乱数 + auto r2 = e() ; +} +\end{lstlisting} + +乱数エンジンのオブジェクト\texttt{e}は\texttt{operator()}を呼び出すたび、つまり\texttt{e()}をするたびに変更される。これは疑似乱数のための内部状態を更新するためだ。そのため、乱数エンジンは\texttt{const}では新しい乱数を作るのに使えない。 + +標準ライブラリはデフォルトの乱数エンジンとして\texttt{std::default\_random\_engine}\index{default\_random\_engine@\texttt{default\_random\_engine}}\index{らんすうえんじん@乱数エンジン!default\_random\_engine@\texttt{default\_random\_engine}}を提供している。 + +以下のプログラムはデフォルトの乱数エンジンから乱数を10個出力する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 乱数エンジン + std::default_random_engine e ; + for ( int i = 0 ; i != 10 ; ++i ) + { + // 乱数を出力 + std::cout << e() << "\n"sv ; + } +} +\end{lstlisting} + +標準ライブラリの提供する乱数エンジンにはさまざまなものがあるが、本書ではもう1つ、メルセンヌツイスター\index{めるせんぬついすた@メルセンヌツイスター}\index{らんすうえんじん@乱数エンジン!めるせんぬついすた@メルセンヌツイスター}というアルゴリズムを実装した乱数エンジンを紹介する。\texttt{std::mt19937}\index{mt19937@\texttt{mt19937}}\index{らんすうえんじん@乱数エンジン!mt19937@\texttt{mt19937}}だ。 + +\texttt{std::mt19937}を使うには、\texttt{st::default\_random\_engine}を置き換えるだけでいい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::mt19937 e ; + for ( int i = 0 ; i != 10 ; ++i ) + { + std::cout << e() << "\n"sv ; + } +} +\end{lstlisting} + +メルセンヌツイスターはとても優秀な乱数エンジンだ。乱数が必要な多くの場面では、メルセンヌツイスターを使っておけばまず問題はない。 + +では乱数エンジンを使って、生の乱数を標準入力で得た個数だけ出力するプログラムを書いてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 乱数エンジン + std::mt19937 e ; + + // 標準入力からnを得る + unsigned n {} ; + std::cin >> n ; + // n個出力 + for ( unsigned int i = 0 ; i != n ; ++i ) + { + std::cout << e() << " "sv ; + } +} +\end{lstlisting} + +実行結果は以下のようになる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=terminal] +$ dice +10 +3499211612 581869302 3890346734 3586334585 545404204 4161255391 3922919429 949333985 2715962298 1323567403 +\end{lstlisting} + +乱数エンジンで生成されるのは生の乱数だ。これは通常、32bit符号なし整数とか64bit符号なし整数で表現できる全範囲の値として生成される。これは実際に必要な乱数とは値の範囲が違う。実際に必要な乱数とは、例えば6面ダイスの場合は、\texttt{int}型で1, 2, 3, 4, 5, 6のいずれかの値がそれぞれ\(\frac{1}{6}\)の確率で出てほしい。 + +\hypersection{ch3903}{乱数分布} +\index{らんすうぶんぷ@乱数分布} + +\texttt{乱数分布}とは生の乱数を望みの範囲の乱数に加工するためのライブラリだ。クラスで実装されている。 + +乱数分布ライブラリにもさまざまなものがあるが、6面ダイスのプログラムを実装するのに使うのは\texttt{std::uniform\_int\_distribution}\,だ。 +\index{uniform\_int\_distribution@\texttt{uniform\_int\_distribution}} + +この乱数分布ライブラリは、\texttt{T}にほしい乱数の整数型を指定する。コンストラクター引数を2つ取るので、1つ目の引数に最小値、2つ目の引数に最大値を指定する。 + +\begin{lstlisting}[language={C++}] +std::uniform_int_distribution d(a, b) ; +\end{lstlisting} + +この乱数分布クラスの変数\texttt{d}は、\(a \leq r \leq b\)までの範囲の\texttt{int}型の乱数\texttt{r}を作り出す。 + +6面ダイスを作るには、\texttt{d(a, b)}を\texttt{d(1, 6)}にすればよい。 + +\begin{lstlisting}[language={C++}] +std::uniform_int_distribution d(1, 6) ; +\end{lstlisting} + +乱数分布クラスのオブジェクト\texttt{d}を作ったならば、\texttt{operator()}に乱数エンジンのオブジェクトを引数に渡すことで乱数が作れる。乱数エンジンのオブジェクトを\texttt{e}とすると、\texttt{d(e)}だ。 + +\begin{lstlisting}[language={C++}] +template < typename Engine, typename Distribution > +void f( Engine & e, Distribution d) +{ + // 乱数 + auto r1 = d(e) ; + // 次の乱数 + auto r2 = d(e) ; + // 次の乱数 + auto r3 = d(e) ; +} +\end{lstlisting} + +以上の知識を利用して、プログラム\texttt{dice}を作ってみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 乱数エンジン + std::mt19937 e ; + // 乱数分布 + std::uniform_int_distribution d(1, 6) ; + + // 入力を処理 + unsigned n {} ; + std::cin >> n ; + for ( unsigned int i = 0 ; i != n ; ++i ) + { + // 乱数出力 + std::cout << d(e) << " "sv ; + } +} +\end{lstlisting} + +さっそく実行してみよう。 + +\begin{lstlisting}[style=terminal] +$ ./dice +5 +5 1 6 6 1 +$ ./dice +10 +5 1 6 6 1 6 6 2 4 2 +$ ./dice +20 +5 1 6 6 1 6 6 2 4 2 1 4 2 2 4 6 6 6 6 6 +\end{lstlisting} + +この場合、乱数値は正の整数しか生成しないので、型を\texttt{int}ではなく\texttt{unsigned int}にすることもできる。 + +\begin{lstlisting}[language={C++}] +std::uniform_int_distribution d( 1, 6 ) ; +\end{lstlisting} + +ただし、乱数の結果の型を\texttt{unsigned int}にすると、生成した乱数を使うときに負数が出てくるような計算で問題になる。例えば6面ダイスを2回振り、1回目の出目から2回目の出目を引き算するコードを書いてみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::mt19937 e ; + std::uniform_int_distribution d( 1, 6 ) ; + auto a = d(e) ; // 1回目 + auto b = d(e) ; // 2回目 + + auto result = a - b ; // 結果 + std::cout << result ; +} +\end{lstlisting} + +もし2回目の出目の方が1回目の出目より大きかった場合、結果は負数になってしまうが、\texttt{unsigned int}型は負数を表現できない。 + +そのため、通常は符号付きの整数型を使った方が安全だ。 + +また、分布クラスのテンプレートパラメーターにはデフォルトテンプレート実引数が指定されているので、デフォルトでよければ省略することもできる。 + +\begin{lstlisting}[language={C++}] +// std::uniform_int_distributionと同じ +std:uniform_int_distribution d( 1, 6 ) ; +\end{lstlisting} + +ところで、上のコードは動くのだが、別のプログラムを実行しても毎回同じ出力になる。これでは実用的な6面ダイスプログラムとは言えない。プログラムの実行ごとに結果を買えたい場合、シードを設定する。 + +\hypersection{ch3904}{シード} +\index{しど@シード}\index{らんすう@乱数!しど@シード} + +線形合同法を思い出してみよう。線形合同法で次の乱数\(X_{n+1}\)を計算するには、いまの乱数\(X_{n}\)に対して\(X_{n+1} = (a \times X_{n} + c) \bmod m\)という計算をする。 + +線形合同法とは現在の乱数値を内部状態として持ち、そこに計算を加えることで次の乱数を作り出すのだ。 + +一般化すると、疑似乱数は内部状態\(S_n\)を持ち、計算を加える関数\(f(x)\)を適用することで、次の内部状態\(S_{n+1}=f(S_n)\)を作り出すのだ。単純な線形合同法の場合、内部状態がそのまま乱数の値になるが、複雑な疑似乱数アルゴリズムでは、内部状態から乱数を求めるのにさらに計算を加えるものもある。 + +乱数エンジンをデフォルト初期化すると、この内部状態もデフォルト初期化される。そのため、いままで使っていた乱数は、プログラムの実行ごとに同じ乱数列を作り出すのだ。 + +疑似乱数の内部状態の初期状態を設定するための値をシード(seed)という。シードを設定するには、\texttt{std::seed\_seq}\index{seed\_seq@\texttt{seed\_seq}}というクラスのオブジェクトを乱数エンジンのコンストラクターに渡す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::seed_seq s{124} ; + std::mt19937 e(s) ; + std::cout << e() ; +} +\end{lstlisting} + +\texttt{std::seed\_seq s(\{n\})}の\texttt{n}の値を変更し、異なるシード値が異なる生の乱数を生成しているのを確かめよう。 + +シード値は乱数エンジンのメンバー関数\texttt{seed(s)}でも渡すことができる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::seed_seq s{123} ; + // シード値を設定 + std::mt19937 e(s) ; + // 乱数を生成 + // 内部状態が変わる + auto r1 = e() ; + // シード値を設定 + e.seed(s) ; + // 乱数を生成 + auto r2 = e() ; + // 同じシード値による乱数は同じ値になる + // r1 == r2 +} +\end{lstlisting} + +\texttt{r1 == r2}になるのは、同じシード値を渡して内部状態を設定しているからだ。 + +\texttt{std::seed\_seq}には複数の符号なし32bit整数を渡すことができる。\texttt{= \{n1, n2, n3,...\}}\,のように初期化することもできるし、イテレーターを使って\texttt{(first, last)}のように設定することもできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::seed_seq s1 = {1,2,3,4,5} ; + + std::vector v = {1,2,3,4,5} ; + std::seed_seq s2( std::begin(v), std::end(v) ) ; +} +\end{lstlisting} + +乱数エンジンをコピーすると、その内部状態もコピーされる。これを利用して、乱数を保存しておくこともできる。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::mt19937 e1 ; + // 2回乱数を生成 + e1() ; e1() ; + // コピー、 内部状態もコピーされる + std::mt19937 e2 = e1 ; + // true + bool b1 = e1() == e2() ; + // true + bool b2 = e1() == e2() ; +} +\end{lstlisting} + +乱数エンジン\texttt{e1}, \texttt{e2}は同じ状態を持っているので、同じ回数乱数生成をすると、同じ乱数列が生成される。 + +\hypersection{ch3905}{予測不可能な乱数} +\index{らんすう@乱数!よそくふかのうな@予測不可能な〜} + +シード値を設定すれば乱数エンジンに異なった乱数列を生成させることができる。しかし、シード値はどうやって生成すればいいのだろうか。シード値をデフォルト初期化した乱数エンジンで生成しても意味がない。なぜならば初期状態はプログラムの実行ごとに同じだからだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::mt19937 e1 ; + std::seed_seq s = { e1(), e1(), e1() } ; + // 意味がない + std::mt19937 e2(s) ; +} +\end{lstlisting} + +内部状態を更新するのではない、本当に予測不可能な乱数を生成するには、ハードウェアの支援が必要だ。例えば放射性同位体がいつ放射性崩壊を起こすかは予測不可能だ。したがって放射線量を計測するガイガーカウンターの値は予測不可能だ。コンピューターにガイガーカウンターが取り付けられていれば、その値を読むことによって予測不可能な値を得ることができる。ほかにもコンピューターにはさまざまな予測不可能な値を得る方法がある。\texttt{std::random\_device}\index{random\_device@\texttt{random\_device}}\index{らんすうえんじん@乱数エンジン!random\_device@\texttt{random\_device}}はそのような実装依存のコンピューターの支援を受け、予測不可能な乱数を生成する乱数エンジンだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::random_device rd ; + for ( int i = 0 ; i != 10 ; ++i ) + std::cout << rd() << " "sv ; +} +\end{lstlisting} + +\texttt{std::random\_device}を使えば、\texttt{std::seed\_seq}\index{seed\_seq@\texttt{seed\_seq}}を予測不可能な値で初期化できる。 + +\begin{lstlisting}[language={C++}] +// 予測不可能な乱数エンジン +std::random_device rd ; +// シード値 +std::seed_seq s = { rd(), rd(), rd() } ; +// シード値を指定して初期化 +std::mt19937 e(s) ; +\end{lstlisting} + +\clearpage +\hypersection{ch3906}{十分なシード値の量} + +\texttt{std::seed\_seq}の初期化では32bit符号なし整数をいくつでも指定できる。 + +\begin{lstlisting}[style=grammar] +std::random_device rd ; +std::seed_seq s = { rd(), rd(), rd(), ...} ; +\end{lstlisting} + +ではいくつの値を渡せばいいのだろうかということは、初期化する乱数エンジンの内部状態のサイズによって異なってくる。現在、C++標準規格には乱数エンジンを適切に初期化する簡単な方法がない。 + +1つの目安としては、乱数エンジンのオブジェクトサイズがある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::cout << sizeof(std::mt19937) ; +} +\end{lstlisting} + +これを筆者の環境で実行すると、\texttt{5000}と出力された。つまり筆者の使っているC++実装の\texttt{std::mt19937}のオブジェクトサイズは5000バイトだということだ。 + +\texttt{std::random\_device}は\texttt{unsigned int}型の乱数を返す。筆者の環境では\texttt{sizeof(unsigned int) == 4}になる。すると\(5000 \div 4 = 1250\) となる。とすると安全のためには、\texttt{std::seed\_seq}には\texttt{std::random\_device}の乱数を1250個渡すべきだろう。 + +\begin{lstlisting}[language={C++}] +std::random_device rd ; +std::seed_seq s = { rd(), rd(), rd(), ... /* 残り1247個のrd() */ } ; +std::mt19937 e( s ) ; +\end{lstlisting} + +筆者の環境では\texttt{sizeof(std::default\_random\_engine) == 8}であった。すると2個でよいことになる。 + +\begin{lstlisting}[language={C++}] +std::random_device rd ; +std::seed_seq s = { rd(), rd() } ; +std::default_random_engine e(s) ; +\end{lstlisting} + +C++標準規格にはいずれ、乱数エンジンを予測不可能なシード値で適切に初期化する簡単な方法が追加されるはずだ。 + +\clearpage +\hypersection{ch3907}{乱数分布ライブラリ} +\index{らんすうぶんぷらいぶらり@乱数分布ライブラリ} + +生の乱数は使いづらい。生の乱数というのは\(n\)ビットの整数値だ。それに対して、我々が使いたい実用的な乱数というのは以下のようなものだ。 + +\begin{itemize} +\item + コイントスの結果 +\item + 6面ダイスを振った結果 +\item + 当選確率1\%のくじ引きの結果 +\item + 浮動小数点数0.0から1.0の範囲の値 +\end{itemize} + +コイントスの結果は表か裏かの2値になる。いま、\(n\)ビットの整数値\texttt{r}のすべてのビットが等しく乱数ビットであるならば、2値の乱数は単に\texttt{r \& 0b1}で得られる。 + +では6面ダイスはどうか。6面ダイスは\(1 \leq r \leq 6\)までの6通りの状態を持つ乱数が必要だ。6通りの状態を表現するには、少なくとも3ビットの乱数が必要になる計算だ。しかし、3ビットの乱数は実際には多すぎる。3ビットの乱数で表現できるのは\(2^3=8\)通りの状態だ。したがって、\texttt{r \& 0x111}というわけにはいかない。 + +巷には間違った乱数の分布方法として、\(a \leq n \leq b\)の範囲の乱数\texttt{n}を生の乱数\texttt{r}から得るために、以下のような計算式を用いる方法が蔓延している。 +\[ +n = r \bmod |b-a| + a +\] + +この間違った計算式を使うと、6面ダイスの乱数値\texttt{n}は生の乱数\texttt{r}(\(0 \leq r\))から以下のようにC++で計算できる。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +int dice( Engine & e ) +{ + auto r = e() ; + auto n = r % 6 + 1 ; + return n ; +} +\end{lstlisting} + +剰余を使って値を\(0 \leq n \leq 5\)までの範囲にし、そこに1を加えることで\(1 \leq n \leq 6\)にしようというものだ。残念ながら、この方法は偏った6面ダイスを作ってしまう。なぜか。 + +生の乱数\texttt{r}には3ビット以上の情報が必要だ。コンピューターは整数をビット列で表現するのですべてのビットが等しく乱数の場合、\(n\)ビットの乱数値は\(2^n\)個の状態を持つ。これを\(0 \leq r \leq 2^n-1\)に割り振った符号なし整数にしたとする。\texttt{r}が3ビットの場合、その値の範囲は\(0 \leq r \leq 7\)だ。 + +上のコードでは、0から5まではそのまま1から6になる。剰余のため、6と7はそれぞれ1と2になる。すべての取り得る乱数を書き出してみよう。 + +\ifTombow\pagebreak\fi +\begin{small} +\begin{longtable}[]{@{\,\,}cc@{\,\,}} +\hline%\toprule +\textsf{生の乱数} & \textsf{ダイス目}\tabularnewline +\hline%\midrule +\endhead +0 & 1\tabularnewline +1 & 2\tabularnewline +2 & 3\tabularnewline +3 & 4\tabularnewline +4 & 5\tabularnewline +5 & 6\tabularnewline +6 & 1\tabularnewline +7 & 2\tabularnewline +\hline%\bottomrule +\end{longtable} +\end{small} + +するとこの6面ダイスは1, 2の出目の確率が\(\frac{2}{8}\)で、3, 4, 5, 6の出目の確率が\(\frac{1}{8}\)になる。 + +よりビット数の大きな生の乱数を使ってもこの問題は解決しない。ビット数を増やせば増やすほど、偏りを減らすことはできるが、偏りは絶対になくならない。理由は、6の素因数3は2で割り切れないためだ。 + +では\(1 \leq n \leq 6\)までの乱数を得るにはどうするのかというと以下のようなアルゴリズムで分布を行う。 + +\begin{enumerate} +\def\labelenumi{\arabic{enumi}.} +\item + 3bitの生の乱数rを得る +\item + \texttt{r}が\(0 \leq r \leq 5\)なら`\texttt{r}+1'が分布された乱数 +\item + それ以外の場合、1.に戻る +\end{enumerate} + +これを実装すると以下のようになる。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto dice( Engine & e ) +{ + // ループを実行する + while (true) + { + // 3bitの生の乱数を得る + auto r = e() & 0b111 ; + // 0-5なら乱数分布終わり + if ( r <= 5 ) + return r + 1 ; + // それ以外ならば振り直し + } +} +\end{lstlisting} + +この関数の実行時間は確率的に決まる。この実装はとても非効率的に見えるが、これ以外に公平に2で割り切れない素因数を含む状態数の乱数を生成する方法はない。 + +このコードは1回の乱数生成をキャッシュして複数回の3bitの乱数を切り出すなどの処理をすれば、乱数生成にコストがかかる場合、その分高速化できる。 + +\hypersection{ch3908}{分布クラス} +\index{ぶんぷくらす@分布クラス} + +分布クラスには共通の機能がある。本書ではすべてを解説しないが、重要な機能を解説する。 + +まず標準ライブラリの分布クラスに共通する機能を説明する。 + +分布クラスはオブジェクトを作り、そのオブジェクトを乱数エンジンと組み合わせて使う。その際、コンストラクターの引数で細かい設定を指定する。 + +\begin{lstlisting}[style=grammar] +distribution_type d( /* 設定 */ ) ; +\end{lstlisting} + +分布クラスは\texttt{operator ()}を呼び出して乱数を分布させる。その際、引数には乱数エンジンへの非\texttt{const}なリファレンスを指定する + +\begin{lstlisting}[language={C++}] +template < typename Engine, typename Distribution > +void f( Engine & e, Distribution & d ) +{ + auto r = d(e) ; +} +\end{lstlisting} + +\texttt{r}が分布された乱数。乱数エンジン\texttt{e}と乱数分布\texttt{d}は乱数を生成したので内部状態が変更される。 + +分布クラスはメンバー関数\texttt{min}\index{min@\texttt{min}}と\texttt{max}\index{max@\texttt{max}}で分布する乱数の最小値、最大値が得られる。 + +\begin{lstlisting}[language={C++}] +template < typename Distribution > +void f( Distribution & d ) +{ + auto a = d.min() ; + auto b = d.max() ; +} +\end{lstlisting} + +分布クラスは構築時の実引数を同名のメンバー関数で取得することができる。 + +例えば、\texttt{std::uniform\_int\_distribution( a, b )}\index{uniform\_int\_distribution@\texttt{uniform\_int\_distribution}}の場合、構築時に渡した\texttt{a}, \texttt{b}を引数の名前でメンバー関数として取得できる。 + +\begin{lstlisting}[language={C++}] +std::uniform_int_distribution d( 1, 6 ) ; +d.a() ; // 1 +d.b() ; // 6 +\end{lstlisting} + +分布クラスは内部状態のリセット\index{ぶんぷくらす@分布クラス!りせつと@リセット}ができる。 + +分布クラスは内部的に乱数値をキャッシュしている可能性がある。例えば乱数値が0か1である場合、1 bitの乱数しか必要ない。ここで渡した乱数エンジンが2 bit以上の乱数を生成できるのであれば、乱数値をキャッシュしておいて、1 bitずつ切り出して使うという最適化が考えられる。 + +しかしこの場合、同じ乱数エンジンを渡したのに、結果が違うということが起こり得る。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::uniform_int_distribution a( 1, 6 ) ; + std::uniform_int_distribution b( 1, 6 ) ; + + std::mt19937 x ; + + // 乱数を生成 + // aは内部に乱数をキャッシュするかもしれない。 + a( x ) ; + + // yはxと同じ内部状態を持つ + // つまり生成する生の乱数は同じ + std::mt19937 y = x ; + + auto r1 = a( x ) ; + auto r2 = b( y ) ; + + // r1 == r2 である保証はない + +} +\end{lstlisting} + +このような場合に、内部状態をリセットするメンバー関数\texttt{reset}\index{reset@\texttt{reset}}を呼び出せば、同じ内部状態になることが保証される。 + +\begin{lstlisting}[language={C++}] +// 内部状態をリセット +a.reset() ; +// true +auto bool = ( a(x) == b(y) ) ; +\end{lstlisting} + +また、この内部状態を取り出すこともできる。内部状態はネストされた型名\texttt{param\_type}\index{param\_type@\texttt{param\_type}}で保持できる。内部状態を取り出すにはメンバー関数\texttt{param()}\index{param@\texttt{param}}を呼び出す。分布クラスのコンストラクターにこの\texttt{param\_type}の値を渡すと、同じ内部状態の分布クラスを作り出すことができる。またメンバー関数\texttt{param(parm)}で\texttt{param\_type}の値を渡して内部状態を設定することも可能だ。 + +\begin{lstlisting}[language={C++}] +template < typename Distribution > +void f( Distribution & d ) +{ + // 内部状態の取り出し + // Distribution::param_type型 + auto p = d.param() ; + + // dと同じ内部状態を持つ変数 + Distribution same_d( p ) ; + + Distribution other ; + // 既存の変数の内部状態を変更 + other.param( p ) ; +} +\end{lstlisting} + +\hypersection{ch3909}{一様分布(Uniform Distribution)} +\index{いちようぶんぷ@一様分布}\index{らんすうぶんぷ@乱数分布!いちようぶんぷ@一様分布} + +一様分布とは乱数の取り得る状態がすべて等しい確率で出現する乱数のことだ。 + +\hypersubsection{ch390901}{整数の一様分布(std::uniform\texttt{\_}int\texttt{\_}distribution\texttt{<}IntType\texttt{>})} +\index{いちようぶんぷ@一様分布!せいすう@整数} + +\texttt{uniform\_int\_distribution}\,は整数型の乱数\(i\), \(a \leq i \leq b\)を以下の定数離散確率関数に従って分布させる。 +\index{uniform\_int\_distribution@\texttt{uniform\_int\_distribution}} +\[ +P(i\,|\,a,b) = 1 / (b - a + 1) \text{ .} +\] + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::uniform_int_distribution d( a, b ) ; +\end{lstlisting} + +\texttt{IntType}は整数型でデフォルトは\texttt{int}、\texttt{a}は最小値、\texttt{b}は最大値。ただし\(a \leq b\)。 + +エンジンも含めた使い方は以下のとおり。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +void f( Engine & e ) +{ + std::uniform_int_distribution d(1, 10) ; + d.a() ; // 1 + d.b() ; // 10 + + // 1から10までの範囲の乱数 + auto r = d(e) ; +} +\end{lstlisting} + +値の範囲には負数も使える。 + +\begin{lstlisting}[language={C++}] +std::uniform_int_distribution d( -3, 3 ) ; +\end{lstlisting} + +この分布は、\(-3\), \(-2\), \(-1\), 0, 1, 2, 3のいずれかをそれぞれ\(\frac{1}{7}\)の等しい確率で返す。 + +\ifTombow\pagebreak\fi +\hypersubsection{ch390902}{浮動小数点数の一様分布(uniform\texttt{\_}real\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{いちようぶんぷ@一様分布!ふどうしようすうてんすう@浮動小数点数} + +\texttt{uniform\_real\_distribution}\,は浮動小数点数型の乱数\(x\), \(a \leq x < b\)を以下の定数確率密度関数に従って分布させる。 +\index{uniform\_real\_distribution@\texttt{uniform\_real\_distribution}} +\[ +p(x\,|\,a,b) = 1 / (b - a) \text{ .} +\] + +\texttt{a == b}のときは未定義となる。 + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::uniform_real_distribution d( a, b ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{double}、\texttt{a}は最小値、\texttt{b}は最大値。値の範囲は\(a \leq b\) かつ \(b - a \leq \text{\texttt{'RealType'}\,型の最大値}\)。 + +エンジンも含めた使い方は以下のとおり。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +void f( Engine & e ) +{ + std::uniform_real_distribution d(0.0, 1.0 ) ; + d.a() ; // 0.0 + d.b() ; // 1.0 + + // 0.0から1.0までの範囲の乱数 + auto r = d(e) ; +} +\end{lstlisting} + +浮動小数点数の難しいところは、整数と違って値の範囲の状態が多いことだ。例えば\texttt{0.0}と\texttt{1.0}の間には\texttt{0.5}もあるし、\texttt{0.01}もあるし\texttt{0.001}もある。浮動小数点数の実装が表現できる状態はとても多い。\texttt{uniform\_real\_distribution}は指定された値の範囲で浮動小数点数が表現できるすべての値のうちから乱数を生成してくれる。そのため読者は難しいことを考える必要はない。 + +\hypersection{ch3910}{ベルヌーイ分布(Bernoulli distributions)} +\index{べるぬいぶんぷ@ベルヌーイ分布}\index{らんすうぶんぷ@乱数分布!べるぬいぶんぷ@ベルヌーイ分布} + +ベルヌーイ分布(bernoulli distribution)とは、ベルヌーイ試行(bernoulli trial)に関する分布だ。 + +ベルヌーイとは数学者ヤコブ・ベルヌーイ(Jacob Bernoulli, 1655--1705)\index{Bernoulli, Jacob}に由来する。ヤコブ・ベルヌーイは西洋数学史上、ジェロラモ・カルダーノ(Gerolamo Cardano, 1501--1576)\index{Cardano, Gerolamo}に続く二人目の、数学的に乱数をまともに文章に書き残した数学者で、現在の統計の基礎を切り開いた人物だ。西洋数学史において乱数と統計の研究は遅れた。この理由は主に宗教的なもので、運命とは神の決定したもうことであり、人の子の及ぶところではないとする考え方が一般的だった。そのため、まともな数学者は乱数を研究しなかった。ヤコブ・ベルヌーイの乱数に関する論文も、けっきょく本人は完成させることができず、論文完成に息子が着手しようとするも、これまた乱数はまともな数学者の取り組むべきところではないという周囲の圧力のために断念するなどの興味深い歴史がある。 + +\hypersubsection{ch391001}{ベルヌーイ試行} +\index{べるぬいしこう@ベルヌーイ試行} + +ベルヌーイ試行とは、独立した試行で結果が2種類のものだ。 + +「独立した試行」\index{どくりつしたしこう@独立した試行}というのは、試行結果が前回の試行に影響されないことをいう。例えばコイントスの結果は表と裏だが、前回のコイントスの結果は今回のコイントスに影響しない。 + +結果が2種類というのは、試行をした結果、2種類の結果のうちのどちらか一方が出ることを言う。成功/失敗、表/裏、勝ち/負け、\texttt{true}/\texttt{false}など、なんでもいい。数学的には成功/失敗を使うが、C++では\texttt{true}/\texttt{false}で表現する。 + +ベルヌーイ試行において、確率\(p\)が成功する確率である場合、確率\(q=1-p\)が失敗の確率だ。 + +\begin{itemize} +\item + \(p=1\)の場合、必ず成功する。失敗しない +\item + \(p=0\)の場合、必ず失敗する。成功しない +\item + \(p=0.5\)の場合、成功と失敗は同じ確率になる +\item + \(p=0.4\)の場合、\(\frac{2}{5}\)の確率で成功し、\(\frac{3}{5}\)の確率で失敗する +\end{itemize} + +具体的なベルヌーイ試行の例を挙げると、 + +\begin{itemize} +\item + コイントスの結果、表か、裏か +\item + 6面ダイスを振って6が出るか、6以外が出るか +\item + 6面ダイスを振って5, 6が出るか、1, 2, 3, 4が出るか +\item + 確率1\%で当たるくじ引きの結果がアタリか、ハズレか +\item + 赤玉と白玉が多数入ったツボの中身をよくかき混ぜ、玉を1つだけ取り出し、戻す。引いた玉の色が赤か、白か +\end{itemize} + +このような結果を2種類に分けることができ、そのうちのどちらか一方だけが結果として出る、かつ1回1回が独立した試行をベルヌーイ試行と呼ぶ。 + +ベルヌーイ分布を使うと、一様分布ではない2値(\texttt{true}/\texttt{false})の確率的な結果について乱数で得ることができる。例えば、ビデオゲームで宝箱を開けると32\%の確率でアイテムが入っており、68\%の確率で空っぽであるとする。これを一様分布で実装すると、以下のようになる。 + +\begin{lstlisting}[language={C++}] +// 宝箱にアイテムが入っている場合trueを返す +template < typename Engine > +bool open_chest( Engine & e ) +{ + // 1から100までの整数の乱数を生成する + std::uniform_int_distribution d(1, 100) ; + // 32以下ならアイテムが入っている + // 33以上ならば空っぽ + return d(e) <= 32 ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +このようなコードを書くのは間違いの元だ。確率32\%というのは\texttt{32.0/100.0}という\texttt{double}型の値で表現できる。この値だけ指定して、残りはライブラリに任せたい。そのようなときに使うのがベルヌーイ分布だ。 + +\hypersubsection{ch391002}{ベルヌーイ分布(std::bernoulli\texttt{\_}distribution)} +\index{べるぬいぶんぷ@ベルヌーイ分布}\index{らんすうぶんぷ@乱数分布!べるぬいぶんぷ@ベルヌーイ分布} + +ベルヌーイ分布(bernoulli distribution)は1回のベルヌーイ試行の結果を乱数として返す。 + +\texttt{std::bernoulli\_distribution}\index{bernoulli\_distribution@\texttt{bernoulli\_distribution}}は\texttt{bool}型の乱数\(b\)を以下の離散確率関数に従って分布する。 + +\[ + P(b\,|\,p) = \left\{ \begin{array}{ll} + p & \text{ if $b = true$, or} \\ + 1 - p & \text{ if $b = false$.} + \end{array}\right. +\] + +確率\(p\)で\texttt{true}が、確率\(1-p\)で\texttt{false}が返る。 + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::bernoulli_distribution d( p ) ; +\end{lstlisting} + +\texttt{p}は\texttt{double}型で、値の範囲は\(0 \leq p \leq 1\)。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::bernoulli_distribution d( 0.5 ) ; + d.p() ; // 0.5 ; + + std::mt19937 e ; + // 乱数生成 + d(e) ; +} +\end{lstlisting} + +\texttt{bernoulli\_distribution}はテンプレートクラスではない。生成する乱数の型は\texttt{bool}だ。\texttt{p}は\texttt{double}型で確率\(p\)のことだ。値の範囲は\(0 \leq p \leq 1\)。 + +例えば前述の32\%の確率でアイテムが入っている宝箱を実装するには以下のようになる。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +bool open_chest( Engine & e ) +{ + std::bernoulli_distribution d( 32.0 / 100.0 ) + return d(e) ; +} +\end{lstlisting} + +この関数\texttt{open\_chest}は確率32\%で\texttt{true}を、確率68\%で\texttt{false}を返す。 + +本当にそうだろうか。確かめてみよう。 + +32\%の確率で\texttt{true}になり、68\%の確率で\texttt{false}になっているかどうかを確かめるには、大量の乱数を生成して\texttt{true}/\texttt{false}をカウントし、それぞれ乱数を生成した数で割って割合を見ればよい。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 試行回数 + const int trial_count = 100 ; + + std::mt19937 e ; + std::bernoulli_distribution d( 32.0 / 100.0 ) ; + + std::array result{} ; + for ( int i = 0 ; i != trial_count ; ++i ) + // boolからintへの変換は + // falseが0, trueが1 + ++result[ d(e) ] ; + + std::cout << "false: "sv << double(result[0]) / double(trial_count) * 100.0 << "%\n"sv + << "true : "sv << double(result[1]) / double(trial_count) * 100.0 << "%\n"sv ; +} +\end{lstlisting} + +これを実行してみると、筆者の環境では、 +\begin{lstlisting}[style=terminal] +false: 72% +true : 28% +\end{lstlisting} +と出力された。少し違う。乱数なので試行回数が少なすぎる場合は、大きく偏ることもある。では試行回数を増やしてみよう。変数\texttt{trial\_count}が試行回数だ。 + +200回試行すると、 + +\begin{lstlisting}[style=terminal] +false: 72.5% +true : 27.5% +\end{lstlisting} +まだダメだ。1000回試行してみよう。 +\begin{lstlisting}[style=terminal] +false: 68.5% +true : 31.5% +\end{lstlisting} + +だいぶ近くなった。 + +ちなみに、このままどんどん1万回、10万回と試行回数を増やしていっても、精度はそれほど上がらない。このことはヤコブ・ベルヌーイの研究と関わってくる。 + +\texttt{std::bernoulli\_distribution}のコンストラクターに与える\texttt{double}型の確率\texttt{p}の範囲は\(0.0 \leq p \leq 1.0\)だ。 + +\(p=1.0\)ならば常に\texttt{true}, \(p=0.0\)なら常に\texttt{false}、\(p=0.5\)ならば\texttt{true/false}が一様分布する。 + +\hypersubsection{ch391003}{二項分布(std::binomial\texttt{\_}distribution\texttt{<}IntType\texttt{>})} +\index{にこうぶんぷ@二項分布}\index{らんすうぶんぷ@乱数分布!にこうぶんぷ@二項分布} + +二項分布(binomial distribution)は確率\(p\)で成功するベルヌーイ試行を\(t\)回行ったときに成功した回数\(i\)を乱数として返す。 + +具体的に例えると、100回コイントスをした結果出た表の数だ。コイントスは表と裏とそれぞれ 50\%ずつの確率で出す。表を成功(\texttt{true})とすると、つまり、\(p=0.5\)のベルヌーイ試行だ。100回コイントスをするというのは\(t=100\)だ。つまり、100回コイントスをした結果出た表の数というのは、100回ベルヌーイ試行した結果の成功数になる。この結果は、期待値としては50だが、ここで生成するのは乱数なので、50回出るわけではない。運が悪ければ1回も表が出ないこともあり得る。 + +6面ダイスを60回振った結果出た1の目の回数もそうだ。この場合、\(p=\frac{1}{6}\)のベルヌーイ試行を\(t=60\)回行うことになる。期待値は\(10\)だ。成功を2, 3の目が出た回数と考えることもできる。この場合期待値は\(20\)だ。 + +確率1\%で当たるくじを100回引いた場合もそうだ。この場合、\(p=0.01\)で\(t=100\)になる。期待値は\(1\)なので、1回当たることが平均的に期待できる。ちなみに、実際に100回くじ引きをして1回でも当たる確率は約63\%だ。 + +\texttt{std::binomial\_distribution}\,\index{binomial\_distribution@\texttt{binomial\_distribution}}は\texttt{IntType}型の乱数\(i \geq 0\)を以下の離散確率関数に従って分布する。 +\[ +P(i\,|\,t,p) = \binom{t}{i} \cdot p^i \cdot (1-p)^{t-i} \text{ .} +\] + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::binomial_distribution d( t, p ) ; +\end{lstlisting} + +\texttt{IntType}は整数型でデフォルトは\texttt{int}だ。\texttt{t}は\texttt{IntType}型の整数値で、値の範囲は\(0 \leq t\)だ。\texttt{p}は\texttt{double}型の値で確率を指定する。\texttt{p}の値の範囲は\(0 \leq p \leq 1\)だ。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::binomial_distribution d( 1, 0.5 ) ; + d.t() ; // 1 + d.p() ; // 0.5 + + std::mt19937 e ; + // 乱数生成 + d(e) ; +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +100回コイントスをした結果、表が出た回数を乱数で得る関数\texttt{coinflips100}は以下のように書ける。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto coinflips100( Engine & e ) +{ + // t == 100, p == 0.5 + std::binomial_distribution d( 100, 0.5 ) ; + return d(e) ; +} +\end{lstlisting} + +100回のベルヌーイ試行をするので\(t=100\)で、ベルヌーイ試行の成功確率は\(p=\frac{1}{2}=0.5\)になる。 + +これを10回ぐらい呼んでみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::mt19937 e ; + for ( int i = 0 ; i != 10 ; ++i ) + std::cout << coinflips100( e ) << ", "sv ; +} +\end{lstlisting} + +筆者の環境では結果は以下のようになった。 + +\begin{lstlisting}[style=terminal] +53, 54, 43, 56, 51, 50, 45, 48, 49, 47, +\end{lstlisting} + +期待値は50なので、50前後の乱数が出やすい。 + +6面ダイスを60回振った結果出た1の目の合計を乱数で返す関数\,\texttt{roll\_for\_one}\,は以下のようになる。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto roll_for_one( Engine & e ) +{ + // t == 60, p == 1.0/ 6.0 + std::binomial_distribution d( 60, 1.0 / 6.0 ) ; + return d(e) ; +} +\end{lstlisting} + +60回のベルヌーイ試行をするので\(t=60\)で、ベルヌーイ試行の確率は6面ダイスの1の目が出る確率なので、\(p=\frac{1}{6}\)になる。 + +確率1\%で当たるくじを100回引いた結果アタリの回数を返す関数\texttt{lootbox}は以下のように実装できる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto lootbox( Engine & e ) +{ + // t == 100, p = 0.01 + std::binomial_distribution d( 100, 1.0 / 100.0 ) ; + return d(e) ; +} +\end{lstlisting} + +この関数を10回呼び出してみると結果は以下のようになる。 + +\begin{lstlisting}[style=terminal] +1, 0, 2, 1, 0, 0, 0, 0, 1, 3, +\end{lstlisting} + +確率1\%で当たるくじを100回引くと、複数回当たることもあれば、1回も当たらないこともある。期待値は1だが、期待値というのは平均的に期待できる結果でしかない。読者諸君もくじ引きをするときは確率に気を付けよう。たとえくじが毎回公平であったとしても、確率は無記憶性なのだ。「もう90回くじを引いたからあと10回引けば当たるはず」という考え方は通用しない。 + +\hypersubsection{ch391004}{幾何分布(std::geometric\texttt{\_}distribution)} +\index{きかぶんぷ@幾何分布}\index{らんすうぶんぷ@乱数分布!きかぶんぷ@幾何分布} + +幾何分布(geometric distribution)とは、確率\(p\)で成功するベルヌーイ試行を初めて成功するまで行った回数を乱数として分布する。 + +具体的な例で例えると、 +\begin{itemize} +\item + コイントスを表が出るまで行った回数 +\item + 6面ダイスを1の目が出るまで振った回数 +\item + 確率1\%で当たるくじ引きをアタリが出るまで引いた回数 +\end{itemize} + +コイントスの例で考えよう。コイントス1回をベルヌーイ試行とし、成功を表とする。表が出るまでコイントスをしてみよう。コイントスを何回する必要があるだろうか。運がよければ1回で表が出るので1回だ。運が悪ければ、5回コイントスをしても全部裏なこともあるだろう。100回コイントスをして表が一度も出ないことは、確率的にはあり得る。ただしその確率は\(\frac{1}{2^{100}}\)なので、およそあり得ない確率ではある。 + +\texttt{std::geometric\_distribution}\,\index{geometric\_distribution@\texttt{geometric\_distribution}}は\texttt{IntType}型の乱数\(i\), \(i \geq 0\)を以下の離散確率関数に従って分布する。 +\[ +P(i\,|\,p) = p \cdot (1-p)^{i} \text{ .} +\] + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::geometric_distribution d( p ) ; +\end{lstlisting} + +\texttt{IntType}は整数型でデフォルトは\texttt{int}、\texttt{p}は確率で値の範囲は\(0 < p < 1\)だ。\texttt{p}の値の範囲に注意すること。0と1であってはならない。幾何分布は成功するまでベルヌーイ試行した回数を返すので、\(p=0\)の場合、必ず失敗するベルヌーイ試行になり意味がない。\(p=1\)のときは必ず成功するベルヌーイ試行であり、やはり意味がない。 + +\texttt{geometric\_distribution}の生成する乱数の範囲にも注意が必要だ。生成される乱数\(i\)の範囲は\(i \geq 0\)だ。0もあり得る。0ということは、最初のベルヌーイ試行が成功したということだ。1は2回目のベルヌーイ試行が成功したということだ。幾何分布はベルヌーイ試行が初めて成功するまでのベルヌーイ試行の回数を返すので、成功したベルヌーイ試行は回数に含めない。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + // p == 0.5 + std::geometric_distribution d( 0.5 ) ; + d.p() ; // 0.5 ; + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + +コイントスを表が出るまで繰り返し、その合計回数を乱数で返す関数\texttt{try\_coinflips}を書いてみよう。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto try_coinflips( Engine & e ) +{ + std::geometric_distribution d( 0.5 ) ; + return d(e) + 1; +} +\end{lstlisting} + +最後に\,\texttt{+1}しているのは、この文脈では表を出したときのコイントスも数えるからだ。つまり成功したベルヌーイ試行も回数に数えるので、幾何分布の生成する乱数より1多い数になる。 + +10回呼び出してみたところ、以下のような戻り値を返した。 + +\begin{lstlisting}[style=terminal] +1, 3, 6, 1, 1, 2, 1, 8, 9, 5, +\end{lstlisting} + +運がよければ1回で表が出るが、運が悪ければ9回かかる。もちろんもっとかかる可能性もある。 + +6面ダイスを1の目が出るまで振り、その合計回数を返す関数\texttt{try\_rolls}を書いてみよう。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto try_rolls( Engine & e ) +{ + std::geometric_distribution d( 1.0 / 6.0 ) ; + return d(e) + 1; +} +\end{lstlisting} + +これも10回呼び出してみると筆者の環境では以下のようになった。 + +\begin{lstlisting}[style=terminal] +1, 10, 20, 2, 3, 5, 2, 28, 31, 19, +\end{lstlisting} + +6面ダイスを振ると、運がよければ1回で1の目が出るが、運が悪いと何十回も振る必要がある。 + +確率1\%のくじを初めて当たるまで引き続け、くじを引いた回数を返す関数\texttt{try\_lootboxes}も書いてみよう。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +unsigned int try_lootboxes( Engine & e ) +{ + std::geometric_distribution d( 1.0 / 100.0 ) ; + return d(e) + 1; +} +\end{lstlisting} + +10回呼び出してみよう。 + +\begin{lstlisting}[style=terminal] +15, 180, 346, 25, 37, 79, 21, 493, 562, 342, +\end{lstlisting} + +確率1\%のくじを当てるには、運が悪いと何百回も引かなければならない。 + +\hypersubsection{ch391005}{負の二項分布(std::negative\texttt{\_}binomial\texttt{\_}distribution)} +\index{ふのにこうぶんぷ@負の二項分布}\index{らんすうぶんぷ@乱数分布!ふのにこうぶんぷ@負の二項分布} + +負の二項分布(negative binomial distribution)は幾何分布に似ている。幾何分布がベルヌーイ試行が1回成功するまでに行ったベルヌーイ試行の回数を乱数として分布するのに対し、負の二項分布はベルヌーイ試行が\(k\)回成功するまでに行ったベルヌーイ試行の回数を乱数として分布する。 + +負の二項分布を具体的な例で考えよう。 + +\begin{itemize} +\item + コイントスを、10回、表が出るまで行った回数 +\item + 6面ダイスを、10回、1の目が出るまで振った回数 +\item + 確率1\%で当たるくじ引きを、10回、アタリが出るまで引いた回数 +\end{itemize} + +幾何分布は負の二項分布で表現することもできる。 + +\begin{itemize} +\item + コイントスを、1回、表が出るまで行った回数 +\item + 6面ダイスを、1回、1の目が出るまで振った回数 +\item + 確率1\%で当たるくじ引きを、1回、アタリが出るまで引いた回数 +\end{itemize} + +\texttt{std::negative\_binomial\_distribution}\,\index{negative\_binomial\_distribution@\texttt{negative\_binomial\_distribution}}は\texttt{IntType}型の乱数\(i\), \(i \geq 0\)を以下の離散確率関数に従って分布する。 +\[ +P(i\,|\,k,p) = \binom{k+i-1}{i} \cdot p^k \cdot (1-p)^i \text{ .} +\] + +\(p = 1\)のときの\(P(i\,|\,k,p)\)は未定義だ。 + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::negative_binomial_distribution d( k, p ) ; +\end{lstlisting} + +\texttt{IntType}は整数型でデフォルトは\texttt{int}、\texttt{k}は\texttt{IntType}型の値\(0 < k\)で成功させるベルヌーイ試行の回数、\texttt{p}は\texttt{double}型の確率\(- < p \leq 1\)だ。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + // k == 1, p == 0.5 + std::negative_binomial_distribution d( 1, 0.5 ) ; + d.k() ; // 1 + d.p() ; // 0.5 + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + +幾何分布と同じく、負の二項分布が生成する乱数\texttt{i}は\texttt{k}回のベルヌーイ試行を成功させるまでに失敗したベルヌーイ試行の数を返す。 + +例えば、コイントスで10回表が出るまでに失敗したコイントスの数を返す。コイントスがベルヌーイ試行で、表が成功だ。成功したベルヌーイ試行の数は返さない。そのため、結果の乱数は10以下、時には0であることもあり得る。0というのは10回コイントスをしたらすべて表になったので1回もベルヌーイ試行が失敗しなかった場合だ。 + +コイントスを10回表が出るまでに行ったコイントスの回数を返す関数\texttt{count\_10\_coinflips}は以下のように書く。 + +\begin{lstlisting}[language={C++}] +// 10回表が出るまでに行ったコイントスの数 +// 表が出たコイントスも含める +template < typename Engine > +auto count_10_coinflips( Engine & e ) +{ + std::negative_binomial_distribution d( 10, 0.5 ) ; + return d(e) + 10 ; +} +\end{lstlisting} + +最後に\,\texttt{+10}しているのは、この関数は成功も含めたコイントスの回数を返すからだ。10回表が出るまでに失敗した、つまり裏になったコイントス回数がほしければ、そのままの値を使えばいい。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// 10回表が出るまでに行った失敗したコイントスの数 +// 表が出たコイントスは含めない +template < typename Engine > +auto count_failed_coinflips_until_10_heads( Engine & e ) +{ + std::negative_binomial_distribution d( 10, 0.5 ) ; + return d(e) ; +} +\end{lstlisting} + +参考までに、\texttt{n}回表が出るまでに行ったコイントスの回数を乱数で返す関数\texttt{count\_n\_coinflips}は以下のとおり。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto count_n_coinflips( unsigned int n, Engine & e ) +{ + std::negative_binomial_distribution d( n, 0.5 ) ; + return d(e) + n ; +} +\end{lstlisting} + +6面ダイスを10回、1の目が出るまで振った回数を乱数で返す関数\,\texttt{count\_10\_rolls}\,は以下のとおり。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto count_10_rolls( Engine & e ) +{ + std::negative_binomial_distribution d( 10, 1.0/6.0 ) ; + return d(e) + 10 ; +} +\end{lstlisting} + +確率1\%のくじを10回当てるまでくじを引いた回数を返す関数\texttt{count\_10\_lootboxes}は以下のとおり。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto count_10_lootboxes( Engine & e ) +{ + std::negative_binomial_distribution d( 10, 0.01 ) ; + return d(e) + 10 ; +} +\end{lstlisting} + diff --git a/TeX/044-random-part2.tex b/TeX/044-random-part2.tex new file mode 100644 index 0000000..d59ef35 --- /dev/null +++ b/TeX/044-random-part2.tex @@ -0,0 +1,281 @@ +\hypersection{ch3911}{ポアソン分布} +\index{ぽあそんぶんぷ@ポアソン分布}\index{らんすうぶんぷ@乱数分布!ぽあそんぶんぷ@ポアソン分布} + +ポアソン分布(Poisson distribution)とは、シメオン・ドニ・ポアソン(Sim\'eon Denis Poisson 1781--1840)\index{Poisson, Sim\'eon Denis}が1837年に発表した論文、「刑事民事の判決における確率の調査」で初めて公開されたものだ。この論文でポアソンは、ある国における冤罪の数について、ある時間間隔における冤罪の発生数を乱数とし、そのような乱数の分布について考察した。その結果がポアソン分布だ。 + +ある時間間隔に発生する離散的な事象の多くがポアソン分布に従う。例えば以下は具体的な例だ。 + +\begin{itemize} +\item + 1年間に発生する冤罪の数 +\item + 1ヶ月に発生する交通事故の数 +\item + 1年間で地球に飛来する隕石の数 +\item + 1時間である放射性同位体が放射性崩壊する回数 +\end{itemize} + +冤罪や交通事故の発生件数は乱数ではないように考えるかもしれない。しかし、結果的にみれば乱数のように振る舞っている。1ヶ月に発生する交通事故が10件であったとする。これは平均すると約3日に1回交通事故が起こっていることになるが、実際に3日に1回交通事故が起こったわけではない。交通事故の発生は離散的で、1日に複数件起こることもあれば、1週間無事故のときもある。なので3日に1回交通規制を敷いても交通事故を防ぐことはできない。 + +ポアソン分布に従う乱数の特徴としてもう1つ、無記憶性というものがある。3日間に1件の割合で交通事故が起こっているとしよう。その場合、常にいまから3日以内に1件の交通事故が起きることが期待できるだけであって、3日以内に必ず起こるわけではない。そして、2日待ったから明日交通事故が起こるというわけでもない。交通事故が起こる確率は常にいまから3日間につき1件だ。ポアソン分布は無記憶性なので、すでに2日間待っているという過去は未来に影響しない。 + +具体的なC++ライブラリのポアソン分布の使い方としては、ある所定の時間に平均して起こる事象の回数\texttt{mean}を指定すると、その所定の時間に起こった事象が乱数で返される。 + +\hypersubsection{ch391101}{ポアソン分布(poisson\texttt{\_}distribution)} +\index{ぽあそんぶんぷ@ポアソン分布} + +ポアソン分布の\texttt{std::poisson\_distribution}\,\index{std::poisson\_distribution@\texttt{std::poisson\_distribution}}は整数型\texttt{T}の乱数\(i\), \(i \geq 0\)を以下の離散確率関数に従って分布する。 +\[ +P(i\,|\,\mu) = \frac{e^{-\mu} \mu^{i}}{i\,!} \text{ .} +\] + +ここで\(\mu\)を\texttt{mean}とする。\(\mu > 0\)ではない場合未定義だ。 + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::poisson_distribution d( mean ) ; +\end{lstlisting} + +\texttt{T}は整数型でデフォルトは\texttt{int}、\texttt{mean}は\texttt{RealType}型。\(\mu\)と同じで浮動小数点数型の値で所定の時間に平均して発生する事象の回数だ。値の範囲は\(0 < mean\)。 + +\ifTombow\pagebreak\else{\vskip 1.0zw}\fi +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::poisson_distribution d( 1.0 ) ; + d.mean() ; // 1.0 + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + +ポアソン分布が生成する乱数は0以上の事象が発生した回数となる。 + +例えば、1ヶ月に交通事故が平均して10件発生するとする。1ヶ月に発生した交通事故の件数は平均が10件になるように増減するはずだ。1ヶ月の交通事故の発生件数を乱数で返す関数\texttt{traffic\_accidents}は以下のようになる。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto traffic_accidents( Engine & e ) +{ + std::poisson_distribution d(10.0) ; + return d(e) ; +} +\end{lstlisting} + +これを10回呼び出すと以下のような乱数列が生成された。 + +\begin{lstlisting}[style=terminal] +14, 6, 11, 8, 8, 14, 7, 16, 12, 17, +\end{lstlisting} + +だいぶばらつきがある。ポアソン分布とはこういうものだ。離散的に起こる事象を乱数として取ると、このようにばらつく。現実でも、1ヶ月に交通事故が平均して10件起きている場合、20件起きる月や無事故の月が存在する可能性があるのだ。 + +これを合計すると113となり、10で割って平均を取ると、10.0に近い値になる。もっと多くの乱数を生成して平均を取るとより近くなる。 + +\hypersubsection{ch391102}{指数分布(std::exponential\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{しすうぶんぷ@指数分布}\index{らんすうぶんぷ@乱数分布!しすうぶんぷ@指数分布} + +指数分布(exponential distribution)とは、ポアソン分布に従う事象が起こる時間間隔の分布だ。 + +ポアソン分布がある時間間隔における事象の発生回数の分布であることを思い出そう。 + +ポアソン分布による乱数は例えば、 +\begin{itemize} +\item + 1ヶ月に平均して10件発生する交通事故がある1ヶ月に発生した件数 +\end{itemize} +が乱数だった。1ヶ月が時間間隔で、交通事故が事象だ。10件が平均だ。 + +\ifTombow\pagebreak\fi +抽象的に書くと、 +\begin{itemize} +\item + 時間間隔に平均して\(N\)回発生する事象があるとき、ある時間間隔における事象発生の回数 +\end{itemize} +の分布だ。 + +指数分布では具体的には以下のようになる。 + +\begin{itemize} +\item + 1ヶ月に平均して10件発生する交通事故が発生してから、次の交通事故が発生するまでの時間間隔 +\end{itemize} + +ポアソン分布に従う離散的な事象のある時間間隔における平均の発生回数が与えられているとする。例えば上の場合、交通事故が事象で、「1ヶ月」が時間間隔で、「平均して10件」が平均の発生回数だ。平均して約3日に1件ほど発生していることになる。ところでいままさに交通事故が発生したとする。このとき、次の交通事故が発生するまでの時間間隔はどのくらいだろうか。平均すると約3日に1件だが、交通事故は離散的な事象なので、1時間後にまた起きるかもしれないし、1週間交通事故が起こらないかもしれない。長期的に統計を取ると月に平均して10件発生している場合、次の交通事故が発生するまでの時間間隔を集計して平均すると約3日に1件発生する確率になる。 + +指数分布が扱うのはこの次の交通事故が発生するまでの時間間隔だ。抽象的にもう一度書くと、ポアソン分布に従う離散的な事象の平均回数が与えられている場合に、ある事象から次の事象が発生するまでの時間間隔を分布する。 + +\texttt{std::exponential\_distribution}\,\index{exponential\_distribution@\texttt{exponential\_distribution}}は浮動小数点型\texttt{RealType}の乱数\(x \geq 0\)を以下の確率密度関数に従って分布させる。 +\[ +p(x\,|\,\lambda) = \lambda e^{-\lambda x} \text{ .} +\] + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::exponential_distribution d( lambda ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{double}、\texttt{lambda}は\texttt{RealType}型。ポアソン分布の\texttt{mean}と同じで、ある時間間隔における事象の発生回数だ。値の範囲は\(0 < lambda\)。 + +\texttt{std::exponential\_distribution}の生成する乱数は\texttt{1.0}のとき、ある時間間隔に等しくなる。\texttt{0.5}なら半分の時間間隔、\texttt{2.0}なら2倍の時間間隔だ。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::exponential_distribution d( 1.0 ) ; + d.lambda() ; // 1.0 ; + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + +1ヶ月に10件の交通事故がポアソン分布に従って発生する場合に、ある交通事故から次の交通事故までの時間間隔の乱数を日数で得る関数\texttt{until\_next\_traffic\_accident}は以下のように書く。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +auto until_next_traffic_accident( Engine & e ) +{ + std::exponential_distribution d(10.0) ; + return d(e) * 30.0 ; +} +\end{lstlisting} + +ある時間間隔に10回起こるので、\texttt{lambda}には\texttt{10.0}を指定する。ここでは簡単のために1ヶ月を30日とする。結果の乱数は1.0がある時間間隔に等しいので、つまり1.0は30日に等しい。結果に\texttt{30.0}を掛けることで日数を計算する。 + +この関数を10回呼び出すと以下のようになった。 + +\begin{lstlisting}[style=terminal] +0.436732, 5.40559, 10.4085, 0.749364, 1.10523, 2.37705, 0.626176, 14.8351, 16.932, 10.2976, +\end{lstlisting} + +早いときは1日も立たずして次の交通事故が起きるが、遅いときは10日を超えてもなかなか交通事故が起きない。平均すると3日に1件交通事故が起きる確率の乱数が生成される。 + +\hypersubsection{ch391103}{ガンマ分布(std::gamma\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{がんまぶんぷ@ガンマ分布}\index{らんすうぶんぷ@乱数分布!がんまぶんぷ@ガンマ分布} + +\texttt{std::gamma\_distribution}\,\index{gamma\_distribution@\texttt{gamma\_distribution}}は浮動小数点数型の乱数\(x > 0\)を以下の確率密度関数に従って分布する。 +\[ + p(x\,|\,\alpha,\beta) = + \frac{e^{-x/\beta}}{\beta^{\alpha} \cdot \Gamma(\alpha)} \, \cdot \, x^{\, \alpha-1} + \text{ .} +\] + +\(\alpha\)を\texttt{alpha}、\(\beta\)を\texttt{beta}とする。 + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::gamma_distribution d( alpha, beta ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{double}。\texttt{alpha}, \texttt{beta}は\texttt{RealType}型。値の範囲は\(0 < alpha\), \(0 < beta\)。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::gamma_distribution d( 1.0, 1.0 ) ; + d.alpha() ; // 1.0 + d.beta() ; // 1.0 + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + +\hypersubsection{ch391104}{ウェイブル分布(std::weibull\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{うえいぶるぶんぷ@ウェイブル分布}\index{らんすうぶんぷ@乱数分布!うえいぶるぶんぷ@ウェイブル分布} + +\texttt{std::weibull\_distribution}\,\index{weibull\_distribution@\texttt{weibull\_distribution}}は浮動小数点数型の乱数\(x > 0\)を以下の確率密度関数に従って分布する。 +\[ +p(x\,|\,a,b) = \frac{a}{b} + \cdot \left(\frac{x}{b}\right)^{a-1} + \cdot \, \exp\left( -\left(\frac{x}{b}\right)^a\right) + \text{ .} +\] + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::weibull_distribution d( a, b ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{double}。\texttt{a}, \texttt{b}は\texttt{RealType}型。値の範囲は\(0 < a\), \(0 < b\)。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::weibull_distribution d( 1.0, 1.0 ) ; + d.a() ; // 1.0 + d.b() ; // 1.0 + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + +\hypersection{ch3912}{極値分布(std::extreme\texttt{\_}value\texttt{\_}distribution{\allowbreak}\texttt{<}RealType\texttt{>})} +\index{きよくちぶんぷ@極値分布}\index{らんすうぶんぷ@乱数分布!きよくちぶんぷ@極値分布} + +\texttt{std::extreme\_value\_distribution}\,\index{extreme\_value\_distribution@\texttt{extreme\_value\_distribution}}は浮動小数点数型の乱数\(x\)を以下の確率密度関数に従って分布する。 +\[ +p(x\,|\,a,b) = \frac{1}{b} + \cdot \exp\left(\frac{a-x}{b} - \exp\left(\frac{a-x}{b}\right)\right) + \text{ .} +\] + +極値分布(extreme value distribution)は、ガンベルI型(Gumbel Type I)、対数ウェイブル(log-Weibull)、フィッシャー=ティペットI型(Fisher-Tippett Type I)という名前の分布と呼ばれることもある。 + +\ifTombow\pagebreak\else{\vskip 1.0zw}\fi +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::extreme_value_distribution d( a, b ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{double}。\texttt{a}, \texttt{b}は\texttt{RealType}型。値の範囲は\(0 < b\)。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::extreme_value_distribution d( 1.0, 1.0 ) ; + d.a() ; // 1.0 + d.b() ; // 1.0 + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + diff --git a/TeX/045-random-part3.tex b/TeX/045-random-part3.tex new file mode 100644 index 0000000..fe2e50f --- /dev/null +++ b/TeX/045-random-part3.tex @@ -0,0 +1,238 @@ +\hypersection{ch3913}{正規分布} + +\hypersubsection{ch391301}{正規分布(std::normal\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{せいきぶんぷ@正規分布}\index{らんすうぶんぷ@乱数分布!せいきぶんぷ@正規分布} + +\texttt{std::normal\_distribution}\,\index{normal\_distribution@\texttt{normal\_distribution}}は浮動小数点数型の乱数\(x\)を以下の確率密度関数に従って分布する。 +\[ + (x\,|\,\mu,\sigma) + = \frac{1}{\sigma \sqrt{2\pi}} + \cdot + % e^{-(x-\mu)^2 / (2\sigma^2)} + \exp{\left(- \, \frac{(x - \mu)^2} + {2 \sigma^2} + \right) + } + \text{ .} +\] + +分布パラメーターのうちの\(\mu\)と\(\sigma\)は、それぞれ分布の平均(mean)、標準偏差(standard deviation)とも呼ばれている。 + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::normal_distribution d( mean, stddev ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{double}。\texttt{mean}, \texttt{stddev}は浮動小数点数型。\texttt{mean}は平均。\texttt{stddev}は標準偏差で値の範囲は\(0 < stddev\)。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::mt19937 e ; + std::normal_distribution d( 0.0, 1.0 ) ; + d.mean() ; // 0.0 + d.stddev() ; // 1.0 + + for ( int i = 0 ; i != 10 ; ++i ) + { + std::cout << d(e) << ", "sv ; + } +} +\end{lstlisting} + +\hypersubsection{ch391302}{対数正規分布(std::lognormal\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{たいすうせいきぶんぷ@対数正規分布}\index{らんすうぶんぷ@乱数分布!たいすうせいきぶんぷ@対数正規分布} + +\texttt{std::lognormal\_distribution}\,\index{lognormal\_distribution@\texttt{lognormal\_distribution}}は浮動小数点数の乱数\(x > 0\)を以下の確率密度関数に従って分布する。 +\[ +p(x\,|\,m,s) = \frac{1}{s x \sqrt{2 \pi}} + \cdot \exp{\left(-\frac{(\ln{x} - m)^2}{2 s^2}\right)} + \text{ .} +\] + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::lognormal_distribution d( m, s ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{double}。\texttt{m}, \texttt{s}は\texttt{RealType}型。値の範囲は\(0 < s\)。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::mt19937 e ; + std::lognormal_distribution d( 0.0, 1.0 ) ; + d.m() ; // 0.0 + d.s() ; // 1.0 + + for ( int i = 0 ; i != 10 ; ++i ) + { + std::cout << d(e) << ", "sv ; + } +} +\end{lstlisting} + +\hypersubsection{ch391303}{カイ二乗分布(std::chi\texttt{\_}squared\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{かいじじようぶんぷ@カイ二乗分布}\index{らんすうぶんぷ@乱数分布!かいじじようぶんぷ@カイ二乗分布} + +\texttt{std::chi\_squared\_distribution}\,\index{chi\_squared\_distribution@\texttt{chi\_squared\_distribution}}は浮動小数点数型の乱数\(x > 0\)を以下の確率密度関数に従って分布する。 +\[ +p(x\,|\,n) = \frac{x^{(n/2)-1} \cdot e^{-x/2}}{\Gamma(n/2) \cdot 2^{n/2}} \text{ .} +\] + +\ifTombow\pagebreak\else{\vskip 1.0zw}\fi +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::chi_squared_distribution d( n ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{double}。\texttt{n}は\texttt{RealType}型。値の範囲は\(0 < n\)。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::mt19937 e ; + std::chi_squared_distribution d( 1.0 ) ; + d.n() ; // 1.0 + + for ( int i = 0 ; i != 10 ; ++i ) + { + std::cout << d(e) << ", "sv ; + } +} +\end{lstlisting} + +\hypersubsection{ch391304}{コーシー分布(std::cauchy\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{こしぶんぷ@コーシー分布}\index{らんすうぶんぷ@乱数分布!こしぶんぷ@コーシー分布} + +\texttt{std::cauchy\_distribution}\,\index{cauchy\_distribution@\texttt{cauchy\_distribution}}は浮動小数点数型の乱数\(x\)を以下の確率密度関数に従って分布する。 +\[ +p(x\,|\,a,b) = \left(\pi b \left(1 + \left(\frac{x-a}{b} \right)^2 \, \right)\right)^{-1} \text{ .} +\] + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::cauchy_distribution d( a, b ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{double}。\texttt{a}, \texttt{b}は\texttt{RealType}型。値の範囲は\(0 < b\)。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::mt19937 e ; + std::chi_squared_distribution d( 0.0, 1.0 ) ; + d.a() ; // 0.0 + d.b() ; // 1.0 + + for ( int i = 0 ; i != 10 ; ++i ) + { + std::cout << d(e) << ", "sv ; + } +} +\end{lstlisting} + +\hypersubsection{ch391305}{フィッシャーの\(F\)分布(std::fisher\texttt{\_}f\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{ふいつしやのFぶんぷ@フィッシャーの\(F\)分布}\index{らんすうぶんぷ@乱数分布!ふいつしやのFぶんぷ@フィッシャーの\(F\)分布} + +フィッシャーの\(F\)分布(Fisher's \(F\)-distribution)の名前は数学者サー・ロナルド・エイルマー・フィッシャー(Sir Ronald Aylmer Fisher)\index{Fisher, Ronald Aylmer}に由来する。 + +\texttt{std::fisher\_f\_distribution}\,\index{fisher\_f\_distribution@\texttt{fisher\_f\_distribution}}は浮動小数点数の乱数\(x > 0\)を以下の関数密度関数に従って分布する。 +\[ +p(x\,|\,m,n) = \frac{\Gamma\big((m+n)/2\big)}{\Gamma(m/2) \; \Gamma(n/2)} + \cdot \left(\frac{m}{n}\right)^{m/2} + \cdot x^{(m/2)-1} + \cdot \left(1 + \frac{m x}{n}\right)^{-(m + n)/2} + \text{ .} +\] + +\vskip 1.0zw +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::fisher_f_distribution d( m, n ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{dobule}。\texttt{m}, \texttt{n}は\texttt{RealType}型。値の範囲は\(0 < m\) かつ \(0 < n\)。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::fisher_f_distribution d( 1.0 ) ; + d.n() ; // 1.0 + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + +\hypersubsection{ch391306}{スチューデントの\(t\)分布(std::student\texttt{\_}t\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{すちゆでんとのtぶんぷ@スチューデントの\(t\)分布}\index{らんすうぶんぷ@乱数分布!すちゆでんとのtぶんぷ@スチューデントの\(t\)分布} + +スチューデントの\(t\)分布(Student's \(t\)-distribution)はウィリアム・シーリー・ゴセット(William Sealy Gosset)によって考案された。当時、ウィリアムはギネス醸造所で働いていたが、ギネスは従業員に科学論文を発表することを禁じていたために、ウィリアムはスチューデントという偽名で発表した。 + +\texttt{std::student\_t\_distribution}\,\index{student\_t\_distribution@\texttt{student\_t\_distribution}}は浮動小数点数型の乱数\(x\)を以下の確率密度関数に従って分布する。 +\[ +p(x\,|\,n) = \frac{1}{\sqrt{n \pi}} + \cdot \frac{\Gamma\big((n+1)/2\big)}{\Gamma(n/2)} + \cdot \left(1 + \frac{x^2}{n} \right)^{-(n+1)/2} + \text{ .} +\] + +\ifTombow\pagebreak\else{\vskip 1.0zw}\fi +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::student_t_distribution d( n ) ; +\end{lstlisting} + +\texttt{RealType}は浮動小数点数型でデフォルトは\texttt{double}。\texttt{n}は\texttt{RealType}型で、値の範囲は\(0 < n\)。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::student_t_distribution d( 1.0 ) ; + d.n() ; // 1.0 + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + diff --git a/TeX/046-random-part4.tex b/TeX/046-random-part4.tex new file mode 100644 index 0000000..40dd451 --- /dev/null +++ b/TeX/046-random-part4.tex @@ -0,0 +1,569 @@ +\hypersection{ch3914}{サンプリング分布(sampling distributions)} +\index{さんぷりんぐぶんぷ@サンプリング分布}\index{らんすうぶんぷ@乱数分布!さんぷりんぐぶんぷ@サンプリング分布} + +サンプリング分布(sampling distributions)とは、標本から分布の特徴がわかっている場合に、その特徴を指定することにより、望みの分布を作り出す分布のことだ。 + +\hypersubsection{ch391401}{離散分布(std::discrete\texttt{\_}distribution\texttt{<}IntType\texttt{>})} +\index{りさんぶんぷ@離散分布}\index{らんすうぶんぷ@乱数分布!りさんぶんぷ@離散分布} + +\hypersubsubsection{ch39140101}{簡単な説明} + +離散分布(discrete distribution)は整数型の乱数\(i\), \(0 \leq i < n\)を返す分布だ。例えば\(n = 10\)ならば、\(0,1,2,3,4,5,6,7,8,9\)の10個のうちのいずれかの整数値を乱数として返す。この際、乱数値として取りうる整数値1つ1つに、確率を設定できる。確率は\(p_0, \cdots, p_{n-1}\)で設定し、\(p_0\)が\(0\)の確率, \(p_1\)が\(1\)の確率\ldots{}\(p_{n-1}\)が\(n\)の確率となる。それぞれの乱数\(i\)は確率\(\frac{p_i}{S}\)で出現する。このとき\(S\)とはすべての確率の合計、つまり\(S = p_0 + \cdots + p_{n-1}\)となる。確率\(p_i\)は\texttt{double}型で与える。 + +たとえば、\texttt{\{1.0, 1.0, 1.0\}}\,という確率群を渡した場合、離散分布は\(0, 1, 2\)のいずれかの乱数をそれぞれ\(\frac{1.0}{3.0}\)の確率で返す。 + +もし、\texttt{\{1.0, 2.0, 3.0\}}という確率群を渡した場合、離散分布は\(0, 1, 2\)のいずれかの乱数を返す。その時の確率は、\(1\)が\(\frac{1}{6}\)、\(2\)が\(\frac{1}{3}\)、\(3\)が\(\frac{1}{2}\)だ。 + +例えば公平な6面ダイスを作りたい場合、\texttt{\{1.0, 1.0, 1.0, 1.0, 1.0, 1.0\}}\,を指定すると\(0 \leq i \leq 5\)までの6個の乱数\(i\)がそれぞれ\(\frac{1}{6}\)の確率で生成される。この結果に\texttt{+1}すると\(1 \leq i \leq 6\)の乱数を得ることができる。 + +6の目だけ2倍高い確率で出るイカサマ6面ダイスを作りたい場合、\texttt{\{1.0, 1.0, 1.0, 1.0, 1.0, 2.0\}}\,を指定すると、0から4までの5つの目は\(\frac{1}{7}\)の確率で出現し、5だけは\(\frac{2}{7}\)の確率で出る乱数を作ることができる。 + +\(S\)はすべての確率の合計で、それぞれの値は\(\frac{p_i}{S}\)の確率で出る。なので、以下はすべて分布だ。 + +\begin{lstlisting}[style=terminal] +{1.0, 1.0, 1.0, 1.0, 1.0, 2.0} +{0.1, 0.1, 0.1, 0.1, 0.1, 0.2} +{2.0, 2.0, 2.0, 2.0, 2.0, 4.0} +\end{lstlisting} + +\hypersubsubsection{ch39140102}{数学的な説明} + +\texttt{std::discrete\_distribution}\,\index{discrete\_distribution@\texttt{discrete\_distribution}}は整数型の乱数\(i\), \(0 \leq i < n\)を以下の離散確率関数に従って分布する。 +\[ +P(i \,|\, p_0, \cdots, p_{n-1}) = p_i \text{ .} +\] + +別に指定のない場合、分布パラメーターは\(p_k = {w_k / S}\) for \(k = 0, \cdots, n - 1\)として計算され、このとき値\(w_k\)は、一般に\emph{ウエイト(weight)}と呼ばれていて、値は非負数、非NaN、非無限でなければならない。さらに、以下の関係が成り立たねばならない。\(0 < S = w_0 + \dotsb + w_{n - 1}\)。 + +\hypersubsubsection{ch39140103}{変数の宣言} + +\texttt{std::discrete\_distribution}の変数を宣言するには3つの方法がある。いずれも\texttt{double}型の値を\texttt{n}個渡すための方法だ。 + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{イテレーターのペア} + +\noindent +\textsf{変数の宣言:} + +\begin{lstlisting}[style=grammar] +std::discrete_distribution d( firstW, lastW ) ; +\end{lstlisting} + +\texttt{IntType}は整数型でデフォルトは\texttt{int}、\texttt{[firstW, lastW)}はイテレーターのペアで、\texttt{double}型に変換可能な値を参照している。 + +\vskip 1.0zw +\noindent +\textsf{利用例:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array ps = {1.0, 2.0, 3.0} ; + std::discrete_distribution d( std::begin(ps), std::end(ps) ); + + std::mt19937 e ; + d(e) +} +\end{lstlisting} + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{初期化リスト} + +\noindent +\textsf{利用例:} + +\begin{lstlisting}[style=grammar] +std::discrete_distribution d( {...} ) ; +std::discrete_distribution d = {...} ; +\end{lstlisting} + +\texttt{...}には\texttt{double}型の浮動小数点数を指定する + +\vskip 1.0zw +\noindent +\textsf{利用例:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::discrete_distribution d( { 1.0, 2.0, 3.0 } ); + // もしくは + // ... d = { 1.0, 2.0, 3.0 } ; + + std::mt19937 e ; + d(e) +} +\end{lstlisting} + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{個数、デルタ、関数} + +このコンストラクターは以下のように宣言されている。 + +\begin{lstlisting}[style=grammar] +template +discrete_distribution( + size_t nw, + double xmin, double xmax, + UnaryOperation fw +); +\end{lstlisting} + +\texttt{UnaryOperation}は1つの実引数を取る関数オブジェクトで戻り値の型は\texttt{double}型に変換できること。さらに、\texttt{double}型は\texttt{UnaryOperation}の引数に変換可能なこと。もし\(nw = 0\)の場合は、\(n = 1\)とする。それ以外の場合、\(n = {\tt nw}\)とする。このとき、\(0 < \delta = ({\tt xmax} - {\tt xmin}) / n\)となる関係が満たされなければならない。 + +もし\(nw = 0\)ならば\(w_0 = 1\)。それ以外の場合、\(k = 0, \cdots, n - 1\)に対して、\(w_k = {\tt fw}({\tt xmin} + k \cdot \delta + \delta / 2)\)とする。\texttt{fw}は\texttt{n}回を超えて呼ばれることはない。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::discrete_distribution d( 5, 0.0, 1.0, [](auto x){ + std::cout << x << '\n' ; + if ( x < 0.3 ) + x = 0.3 ; + if ( x > 0.8 ) + x = 0.8 ; + return x ; + } ); +} +\end{lstlisting} + +\ifTombow\pagebreak\fi +この\texttt{d}は、 +\begin{lstlisting}[style=grammar] +std::discrete_distribution d = {0.3, 0.3, 0.5, 0.7, 0.8 } ; +\end{lstlisting} +と初期化されたものと同じように初期化される。 + +\hypersubsubsection{ch39140104}{初期化パラメーターの確認} + +\texttt{std::discrete\_distribution}の内部状態はメンバー関数\texttt{probabilities}で取得できる。戻り値の型は\texttt{std::vector}で、指定した確率群が要素になっている。 + +\begin{lstlisting}[language={C++}] +int main() +{ + std::discrete_distribution d = { 1.0, 2.0, 3.0 } ; + auto v = d.probabilities() ; + // vは{1.0, 2.0, 3.0} +} +\end{lstlisting} + +\hypersubsubsection{ch39140105}{応用例} + +以下は6の目が2倍の確率で出るイカサマ6面ダイスの実装だ。 + +\begin{lstlisting}[language={C++}] +template < typename Engine > +int roll_dice( Engine & e ) +{ + std::discrete_distribution d = { 1.0, 1.0, 1.0, 1.0, 1.0, 2.0 } ; + return d(e) + 1 ; +} +\end{lstlisting} + +\hypersubsection{ch391402}{区分定数分布(std::piecewise\texttt{\_}constant\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{くぶんていすうぶんぷ@区分定数分布}\index{らんすうぶんぷ@乱数分布!くぶんていすうぶんぷ@区分定数分布} + +\hypersubsubsection{ch39140201}{簡単な説明} + +区分定数分布(piecewise constant distribution)とは、区分と、区分ごとの確率を指定し、いずれかの区分の範囲の値に一様分布させる分布だ。ここでいう確率は、密度、あるいはウエイトともいう。 + +1つの区分は\texttt{double}型の値2つ\(b_i, b_{i+1}\)で与える。このとき区分の乱数\(x\)の範囲は\([b_i, b_{i+1})\)、もしくは\(b_i \leq x < b_{i+1}\)だ。\(n\)個の値を指定すると、\(n-1\)個の区分を指定したことになる。 + +例えば\,\texttt{\{0.0, 1.0\}}\,という2つの\texttt{double}型の値を使って1つの区分を与えた場合、これは\(0.0 \leq x < 1.0\)という値の範囲の区分である。\texttt{\{0.0, 1.0, 2.0\}}\,という3つの\texttt{double}型の値は2つの区分になり、それぞれ\(0.0 \leq x < 1.0\), \(1.0 \leq x < 2.0\)になる。 + +一般に、\(n\)個の\texttt{double}型の値\(b_0, \cdots, b_n\)で\(n-1\)個の区分を表現する。このとき、\(b_i < b_{i+1}\)が\(i = 0, \cdots, n-1\)までの\(i\)について成り立たなければならない。つまり区分を指定する\texttt{double}型の値は、後続の値より小さくなければならないということだ。 + +以下は正しい区分の指定だ。 + +\begin{lstlisting}[style=terminal] +{1.0, 2.0, 100.0, 999.999} +{-1.0, 1.0, 2.0} +{-5.0, -4.0, -3.1} +\end{lstlisting} + +以下は正しくない区分の指定だ。 + +\begin{lstlisting}[style=terminal] +{1.0, 0.0} +\end{lstlisting} + +これは\(b_0 > b_1\)なので正しくない。 + +それぞれの区分\([b_i, b_{i+1})\)に対して確率\(p_i\)を\texttt{double}型で指定する。\(n\)個の\(b_i\)によって\(n-1\)個の区分を指定し、それぞれに対して1つずつ確率を設定するので、確率の数は\(n-1\)個だ。 + +例えば\,\texttt{\{0.0, 1.0\}}\,という1つの区分と\,\texttt{\{1.0\}}\,という1つの確率を与えた場合、\(0.0 \leq x < 1.0\)の範囲の乱数\(x\)が生成される。 + +\texttt{\{0.0, 1.0, 10.0\}}\,という2つの区分と、\texttt{\{1.0, 2.0\}}\,という2つの確率を与えた場合、\(\frac{1}{3}\)の確率で\(0.0 \leq x < 1.0\)の範囲に一様分布した乱数になり、\(\frac{2}{3}\)の確率で\(1.0 \leq x < 10.0\)の範囲に一様分布した乱数になる。 + +\hypersubsubsection{ch39140202}{数学的な説明} + +\texttt{std::piecewise\_constant\_distribution}\,\index{piecewise\_constant\_distribution@\texttt{piecewise\_constant\_distribution}}は浮動小数点数型の乱数\(x\), \(b_0 \leq x < b_n\)を以下の確率密度関数に従って、それぞれの部分区間(subinterval)\([b_i, b_{i+1})\)の間で一様に分布させる。 +\[ +p(x \,|\, b_0, \cdots, b_n, \; \rho_0, \cdots, \rho_{n-1}) = \rho_i + \text{ , for $b_i \le x < b_{i+1}$.} +\] + +この分布の区間境界(interval boundaries)ともいう\(n+1\)分布パラメーター\(b_i\)はすべての\(i = 0, \cdots, n - 1\)に対して関係\(b_i < b_{i + 1}\)を満たさねばならない。別途指定なき場合、残りの\(n\)分布パラメーターは以下のように計算される。 +\[ +\rho_k = \frac{w_k}{S \cdot (b_{k+1}-b_k)} \text{ for } k = 0, \cdots, n - 1 \text{ ,} +\] + +一般にウエイト(weight)と呼ばれている値\(w_k\)は、非負数、非NaN、非無限でなければならない。さらに、以下の関係を満たさなければならない。 + +\(0 < S = w_0 + \dotsb + w_{n-1}\) + +\hypersubsubsection{ch39140203}{変数の宣言} + +\texttt{std::piecewise\_constant\_distribution}では、\texttt{double}型の値の集合を2つ渡す必要がある。1つは区間を指定するための\(N\)個の\texttt{double}型に変換可能な値で、もう1つは区間ごとの確率を指定するための\(N-1\)個の\texttt{double}型に変換可能な値だ。 + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{イテレーターによる指定} + +イテレーターで区間と確率を指定するコンストラクターは以下のとおり。 + +\begin{lstlisting}[style=grammar] +template +piecewise_constant_distribution( + InputIteratorB firstB, InputIteratorB lastB, + InputIteratorW firstW +); +\end{lstlisting} + +\texttt{[firstB, lastB)}は区間を指定するための\(N\)個の値を参照する入力イテレーターのペアだ。\texttt{firstW}はそれぞれの区間の確率を指定する\(N-1\)個の値を参照する入力イテレーターの先頭だ。\texttt{lastW}がないのは、確率の個数は\(N-1\)個であるとわかっているからだ。 + +もし\texttt{[firstB, lastB)}のサイズが1以下の場合、区間は\texttt{[0.0, 1.0)}になり、確率は\(\frac{1}{1}\)になる。 + +\vskip 1.0zw +\noindent +\textsf{利用例:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::array bs = {-1.0, 1.0, 2.0 } ; + std::array ps = { 1.0, 5.0 } ; + std::piecewise_constant_distribution d( std::begin(bs), std::end(bs), std::begin(ps) ) ; + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + +\texttt{bs}は区間を指定する値の集合、\texttt{ps}は区間ごとの確率だ。 + +区間は\texttt{[-1.0, 1.0)}と\texttt{[1.0, 2.0)}の2つ。確率はそれぞれ\(\frac{1}{6}\)、\(\frac{5}{6}\)だ。 + +区間を表現する値が足りない場合は以下のとおり。 + +\begin{lstlisting}[language={C++}] +int main() +{ + // 区間を指定すべき値が足りない + std::array bs = { 1.0 } ; + std::array ps = { 1.0, 5.0 } ; + // 引数は無視される。 + // 区間は[0.0, 1.0), 確率は100% + std::piecewise_constant_distribution d( std::begin(bs), std::end(bs), std::begin(ps) ) ; +} +\end{lstlisting} + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{初期化リストと関数オブジェクトによる指定} + +初期化リストと関数を指定するコンストラクターは以下のとおり。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=grammar] +template +piecewise_constant_distribution( + initializer_list bl, + UnaryOperation fw +); +\end{lstlisting} + +イテレーターのペアと同じく、区間は\texttt{[bl.begin(), bl.end())}で指定する。 + +確率は\(k = 0, \cdots, n - 1\)について、\(w_k = {\tt fw}\bigl(\bigl(b_{k+1} + b_k\bigr) / 2\bigr)\)とする。 + +\texttt{bl.size()}が1以下の場合、区間は\texttt{[0.0, 1.0)}になり、確率は\(\frac{1}{1}\)になる。 + +\vskip 1.0zw +\noindent +\textsf{利用例:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::piecewise_constant_distribution d( + {1.0, 2.0, 3.0, 4.0, 5.0}, + []( auto x ) + { return x ; } + ) ; +} +\end{lstlisting} + +この場合、区間は\texttt{[1.0, 2.0)}, \texttt{[2.0, 3.0)}, \texttt{[3.0, 4.0)}, \texttt{[4.0, 5.0)}の4個になり、確率は\,\texttt{\{1.5, 2.5, 3.5, 4.5\}}\,となる。 + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{区間数、最小、最大、関数オブジェクトによる指定} + +\noindent +\textsf{コンストラクターの宣言:} + +\begin{lstlisting}[style=grammar] +template +piecewise_constant_distribution( + size_t nw, + RealType xmin, RealType xmax, + UnaryOperation fw +); +\end{lstlisting} + +\texttt{nw}は区間数、\texttt{xmin}は最小値、\texttt{xmax}は最大値、\texttt{fw}は関数オブジェクトで、\texttt{double}型から変換できる型の実引数を取り、\texttt{double}型に変換可能な戻り値を返す。 + +\(nw = 0\)の場合、区間の個数\(n\)は\(1\)になる。それ以外の場合、\(n = nw\)となる。このとき関係、\(0 < \delta = ({\tt xmax} - {\tt xmin}) / n\)が成り立たなければならない。 +\begin{align*} + &\text{Let}\quad b_k = {\tt xmin} + k \cdot \delta \quad\text{for $k = 0, \cdots, n$,}\\ + &\text{and}\quad w_k = {\tt fw}(b_k + \delta / 2) \quad\text{for} . +\end{align*} + +\(k = 0, \cdots, n - 1\)において、区間は\(b_k = {\tt xmin} + k \cdot \delta \text{for $ k = 0, \cdots, n$}\)とし、確率は\(w_k = {\tt fw}(b_k + \delta / 2)\)とする。 + +\vskip 1.0zw +\noindent +\textsf{利用例:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::piecewise_constant_distribution d( 5, 1.0, 5.0, + []( auto x ) { return x ; } ) ; +} +\end{lstlisting} + +この場合、区間の集合は\,\texttt{\{1.0, 1.8, 2.6, 3.4, 4.2, 5.0\}}\,となり、確率は\,\texttt{\{1.4, 2.2, 3.0, 3.8, 4.6\}}\,となる。 + +\hypersubsubsection{ch39140204}{内部状態の取得} + +\texttt{std::piecewise\_constant\_distribution}の内部状態は、メンバー関数\texttt{intervals}と\texttt{densities}で得ることができる。 + +\begin{lstlisting}[style=grammar] +template +class piecewise_constant_distribution { +public : + vector intervals() const; + vector densities() const; +} ; +\end{lstlisting} + +\texttt{intervals}は区間、\texttt{densities}は確率を返す。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto bs = { 1.0, 2.0, 3.0 } ; + auto ps = { 1.0, 2.0 } ; + std::piecewise_constant_distribution d( std::begin(bs), std::end(bs), std::begin(ps) ) ; + + // {1.0, 2.0, 3.0} + auto intervals = d.intervals() ; + // {0.333333, 0.666667} + auto densities = d.densities() ; +} +\end{lstlisting} + +\texttt{densities()}の結果が正規化されているのは、ユーザーが指定した確率は\(w_k\)だが、ここで返すのは\(p_k\)だからだ。 + +\hypersubsection{ch391403}{区分線形分布(std::piecewise\texttt{\_}linear\texttt{\_}distribution\texttt{<}RealType\texttt{>})} +\index{くぶんせんけいぶんぷ@区分線形分布}\index{らんすうぶんぷ@乱数分布!くぶんせんけいぶんぷ@区分線形分布} + +\hypersubsubsection{ch39140301}{簡単な説明} + +区分線形分布(piecewise linear distribution)は区分定数分布と同じく、区間と確率(またの名を密度、ウエイト)を指定する。 + +区間の指定は区分定数分布と同じだ。内部境界の集合で指定する。例えば\,\texttt{\{1.0, 2.0, 3.0\}}\,は2つの区間\texttt{[1.0, 2.0)}と\texttt{[2.0, 3.0)}を指定する。 + +区分線形分布における確率は、区間に対してではなく、内部境界に対して指定する。指定した全区間における値の出現確率は、内部境界から内部境界に向かって指定した確率の差の方向に線形に増加、もしくは減少する。 + +例えば区分\,\texttt{\{0.0, 1.0\}}\,と確率\,\texttt{\{1.0, 2.0\}}\,を指定した場合、これは1つの区間\texttt{[0.0, 1.0)}について、内部境界\texttt{0.0}の確率は\(\frac{1}{3}\)、内部境界\texttt{1.0}の確率は\(\frac{2}{3}\)とし、\(0.0 \leq x < 1.0\)の範囲の乱数\texttt{x}を生成する。内部境界区間の範囲に注意。\texttt{1.0}未満なので、\texttt{1.0}は出ない。 + +そして、区間の間の値は、区間を区切る2つの内部境界の確率の差によって、線形に増加、もしくは減少する。例えば値\texttt{0.25}が出る確率は\(\frac{1.25}{3}\)、\texttt{0.5}が出る確率は\(\frac{1.5}{3}\)、値\texttt{1.75}が出る確率は\(\frac{1.75}{3}\)だ。 + +区分\,\texttt{\{0.0, 1.0, 2.0\}}\,と確率\,\texttt{\{1.0, 2.0, 1.0\}}\,の場合、2つの区間\texttt{[0.0, 1.0)}と\texttt{[1.0, 2.0)}の範囲について、\texttt{0.0}から\texttt{1.0}に向かう区間についての確率は\(\frac{1}{4}\)から\(\frac{1}{2}\)に増加し、\texttt{1.0}から\texttt{2.0}に向かう区間についての確率は\(\frac{1}{2}\)から\(\frac{1}{4}\)に減少する。 + +結果として、乱数値の分布をグラフに描画すると、\texttt{1.0}が最も出やすく、その前後±1.0の範囲で徐々に減少していく山のようなグラフになる。 + +\begin{figure}[htbp] + \centering + \includegraphics[scale=1.0]{fig/fig39-01.eps} + \label{fig:37-01} +\end{figure} + +\hypersubsubsection{ch39140302}{数学的な説明} + +\texttt{std::piecewise\_linear\_distribution}\,\index{piecewise\_linear\_distribution@\texttt{piecewise\_linear\_distribution}}は乱数\(x\), \(b_0 \leq x < b_n\)を以下の確率密度関数に従って分布する。 +\[ +p(x \,|\, b_0, \cdots, b_n, \; \rho_0, \cdots, \rho_n) + = \rho_{i} \cdot {\frac{b_{i+1} - x}{b_{i+1} - b_i}} + + \rho_{i+1} \cdot {\frac{x - b_i}{b_{i+1} - b_i}} + \text{ , for $b_i \le x < b_{i+1}$.} +\] + +一般に\emph{内部境界}とも呼ばれる\(n + 1\)分布パラメーター\(b_i\)は\(i = 0, \cdots, n - 1\)において関係\(b_i < b_{i+1}\) for \(i = 0, \cdots, n - 1\)を満たさねばならない。別記する場合を除いて、残りの\(n + 1\)パラメーターは\(k = 0, \cdots, n\)において\(\rho_k = {w_k / S}\)と計算される。このとき\(w_k\)は一般に境界におけるウエイト(weight at boundaries)と呼ばれ、非負数、非NaN、非無限でなければならない。さらに、以下の関係が成り立たねばならない。 +\[ +0 < S = \frac{1}{2} \cdot \sum_{k=0}^{n-1} (w_k + w_{k+1}) \cdot (b_{k+1} - b_k) \text{ .} +\] + +\hypersubsubsection{ch39140303}{変数の宣言} + +\texttt{piecewise\_linear\_distribution}は区間と確率を指定するために\texttt{n}個の\texttt{double}型に変換可能な値を指定する必要がある。 + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{イテレーターによる指定} + +\begin{lstlisting}[style=grammar] +template +piecewise_linear_distribution( + InputIteratorB firstB, InputIteratorB lastB, + InputIteratorW firstW ); +\end{lstlisting} + +\texttt{[firstB, lastB)}は区間、\texttt{firstW}から区間数までのイテレーターが確率。 + +\texttt{firstB == lastB}もしくは\texttt{++firstB == lastB}の場合、つまり内部境界が1個以下で、空の場合、区間数は1つで\texttt{[0.0, 1.0)}の範囲、確率は\,\texttt{\{0.0, 1.0\}}\,となる。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + auto bs = { 0.0, 1.0, 2.0 } ; + auto ps = { 1.0, 2.0, 1.0 } ; + std::piecewise_linear_distribution d( std::begin(bs), std::end(bs), std::begin(ps) ) ; + + std::mt19937 e ; + d(e) ; +} +\end{lstlisting} + +空の場合。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto bs = { 0.0 } ; + auto ps = { 0.0 } ; + std::piecewise_linear_distribution d( std::begin(bs), std::end(bs), std::begin(ps) ) ; +} +\end{lstlisting} + +これは以下のコードと同じだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +int main() +{ + auto bs = { 0.0, 1.0 } ; + auto ps = { 0.0, 1.0 } ; + std::piecewise_linear_distribution d( std::begin(bs), std::end(bs), std::begin(ps) ) ; +} +\end{lstlisting} + +\vskip 1.0zw +\noindent +\(\bullet\) \textsf{初期化リストと関数オブジェクトによる指定} + +\begin{lstlisting}[style=grammar] +template +piecewise_linear_distribution( + initializer_list bl, + UnaryOperation fw +); +\end{lstlisting} + +区間を指定する内部境界は\texttt{[bl.begin(), bl.end())}、内部境界\(b_k\)に対する確率\(w_k\)は\(k = 0, \cdots, n\)について、\(w_k = {\tt fw}(b_k)\)とする。 + +内部境界が1個以下の場合はイテレーターの場合と同じ。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::piecewise_linear_distribution d( + {0.0, 1.0, 2.0}, + [](auto x){ return x ; } + ) ; +} +\end{lstlisting} + +これは以下のコード同じだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto bs = { 0.0, 1.0, 2.0 } ; + auto ps = { 0.0, 1.0, 2.0 } ; + std::piecewise_linear_distribution d( std::begin(bs), std::end(bs), std::begin(ps) ) ; +} +\end{lstlisting} + +\ifTombow\pagebreak\else{\vskip 1.0zw}\fi +\noindent +\(\bullet\) \textsf{個数、最小値、最大値、関数オブジェクトによる指定} + +\begin{lstlisting}[style=grammar] +template +piecewise_linear_distribution( + size_t nw, + RealType xmin, RealType xmax, + UnaryOperation fw +); +\end{lstlisting} + +\texttt{nw}が個数、\texttt{xmin}が最小値、\texttt{xmax}が最大値、\texttt{fw}が関数オブジェクト。 + +関数オブジェクト\texttt{fw}は\texttt{double}型から変換できる実引数を1つだけ取り、戻り値の型は\texttt{double}型に変換できること。 + +\({\tt nw} = 0\)ならば空であり、イテレーターの場合と同じ。 + +関係\(0 < \delta = ({\tt xmax} - {\tt xmin}) / n\)が成り立つこと。 + +内部境界\(b_k\)は\(k = 0, \cdots, n\)について\(b_k = {\tt xmin} + k \cdot \delta\)とする。確率\(w_k\)は\(k = 0, \cdots, n\)について\(w_k = {\tt fw}(b_k)\)とする。 + +\vskip 1.0zw +\noindent +\textsf{使い方:} + +\begin{lstlisting}[language={C++}] +int main() +{ + std::piecewise_linear_distribution d( + 5, + 1.0, 5.0, + [](auto x){ return x ;} + ) ; +} +\end{lstlisting} + +上のコードは以下のコードと同じだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + auto params = { 1.8, 2.6, 3.4, 4.2, 5.0, 5.8 } ; + std::piecewise_linear_distribution d( std::begin(params), std::end(params), std::begin(params) ) ; +} +\end{lstlisting} + diff --git a/TeX/200-cpp.tex b/TeX/200-cpp.tex new file mode 100644 index 0000000..911cb56 --- /dev/null +++ b/TeX/200-cpp.tex @@ -0,0 +1,884 @@ +\hyperchapter{ch40}{Cプリプロセッサー}{Cプリプロセッサー} +\index{Cぷりぷろせつさ@Cプリプロセッサー} + +CプリプロセッサーはC++がC言語から受け継いだ機能だ。CプリプロセッサーはソースコードをC++としてパースする前に、テキストをトークン単位で変形する処理のことだ。この処理はソースファイルをC++としてパースする前処理として行われる。CプリプロセッサーはC++ではなく別言語として認識すべきで、そもそもプログラミング言語ではなくマクロ言語だ。 + +C++ではCプリプロセッサーが広く使われており、今後もしばらくは使われるだろう。読者がC++で書かれた既存のコードを読むとき、Cプリプロセッサーは避けて通れない。Cプリプロセッサーはいずれ廃止したい機能ではあるが、C++はいまだに廃止できていない。 + +Cプリプロセッサーはプリプロセッシングディレクティブ(preprocessing directive)\index{ぷりぷろせつしんぐでいれくていぶ@プリプロセッシングディレクティブ}を認識し、トークン列を処理する。ディレクティブはソースファイルの文頭に文字\,\texttt{\#}\,\index{\#@\texttt{\#}}から始まり、改行文字で終わる。\texttt{\#}\,とディレクティブの間に空白文字を入れてもよい。 + +\begin{lstlisting}[language={C++}] +#define NOSPACE +# define SPACE +\end{lstlisting} + +\hypersection{ch4001}{\#includeディレクティブ} +\index{\#include@\texttt{\#include}} + +\texttt{\#include}は指定したファイルの内容をその場に挿入する。本質的にはコピペだ。C++では\,\texttt{\#include}はライブラリを利用するのに使われる。 + +\texttt{\#include}は以下のいずれかの文法を持つ。 + +\begin{lstlisting}[style=grammar] +#include <ヘッダーファイルパス> 改行文字 +#include "(@\ifColor\textcolor[rgb]{0,0.533,0}{ヘッダーファイルパス}\else\textcolor{black}{ヘッダーファイルパス}\fi@)" 改行文字 +\end{lstlisting} + +\texttt{\#include}は指定したファイルパスのファイルの内容をその場所に挿入する。このファイルをヘッダーファイル\index{へつだふあいる@ヘッダーファイル}という。\texttt{<>}\,\index{<>@\texttt{<>}}によるファイルパスは、標準ライブラリやシステムのヘッダーファイルを格納したディレクトリーからヘッダーファイルを探す。\texttt{""}\,\index{\protect{""}@\texttt{\protect{""}}}によるファイルパスは、システム以外のディレクトリーからもヘッダーファイルを探す。例えばカレントディレクトリーなどだ。 + +例えば、以下のようなヘッダーファイル\texttt{foo.h}があり、 +\begin{lstlisting}[language={C++}] +// foo.h +foo foo foo +\end{lstlisting} +以下のようなソースファイル\texttt{bar.cpp}がある場合、 +\begin{lstlisting}[language={C++}] +// bar.cpp + +#include "foo.h" + +// end bar.cpp +\end{lstlisting} +\texttt{bar.cpp}をCプリプロセッサーにかけると、以下のようなソースファイルが出力される。 + +\begin{lstlisting}[language={C++}] +// bar.cpp + +// foo.h +foo foo foo + +// end bar.h +\end{lstlisting} + +このソースファイルはC++のソースファイルとしてはエラーとなるが、Cプリプロセッサーは単純にトークン列で分割したテキストファイルとしてソースファイルを処理するため、Cプリプロセッサーとしてはエラーにはならない。 + +冒頭で述べたように、\texttt{\#include}の本質はコンパイラーによるコピペである。あるテキストファイルの内容をその場に挿入するコピペ機能を提供する。 + +\texttt{\#include}は、ほかの言語でモジュール、importなどと呼ばれている機能を簡易的に提供する。C++の標準ライブラリを使うには、\texttt{}\,や\,\texttt{}\,や\,\texttt{}\,のようなヘッダーファイルを\,\texttt{\#include}する必要がある。 + +\begin{lstlisting}[language={C++}] +// iostreamライブラリを使う +#include +// stringライブラリを使う +#include + +int main() +{ +(@\ifTombow\pagebreak\fi@) + // のライブラリ + std::string s("hello") ; + // iostreamのライブラリ + std::cout << s ; +} +\end{lstlisting} + +すでに述べたように\,\texttt{\#include}はファイルの内容をその場に挿入するだけであり、ほかの言語にあるモジュールのための高級な機能ではない。本書を執筆時点で規格策定中のC++20では、より高級なモジュール機能を追加する予定がある。 + +同じヘッダーファイルを複数回\,\texttt{\#include}すると、当然複数回挿入される。 + +以下のような\texttt{val.h}を、 +\begin{lstlisting}[language={C++}] +// val.h +inline int val ; +\end{lstlisting} +以下のように複数回\,\texttt{\#include}すると、 +\begin{lstlisting}[language={C++}] +#include "val.h" +#include "val.h" +\end{lstlisting} +以下のように置換される。 +\begin{lstlisting}[language={C++}] +// val.h +inline int val ; +// val.h +inline int val ; +\end{lstlisting} + +これは\texttt{val}の定義が重複しているためエラーとなる。 + +しかし、ヘッダーファイルを一度しか\,\texttt{\#include}しないようにするのは困難だ。なぜならば、ヘッダーファイルはほかのヘッダーファイルから間接的に\,\texttt{\#include}されることもあるからだ。 + +\begin{lstlisting}[language={C++}] +// lib_f.h + +#include "val.h" + +int f() ; +\end{lstlisting} + +\begin{lstlisting}[language={C++}] +// lib_g.h + +#include "val.h" + +int g() ; +\end{lstlisting} + +\begin{lstlisting}[language={C++}] +// main.cpp + +#include "lib_f.h" +#include "lib_g.h" + +int main() +{ + int result = f() + g() ; +} +\end{lstlisting} + +この\texttt{main.cpp}をCプリプロセッサーにかけると以下のように置換される。 + +\begin{lstlisting}[language={C++}] +// main.cpp + +// lib_f.h + +// val.h +inline int val ; + +int f() ; + +// lib_g.h + +// val.h +inline int val ; + +int g() ; + +int main() +{ + int result = f() + g() ; +} +\end{lstlisting} + +これは\texttt{val}の定義が重複しているためエラーとなる。 + +この問題に対処するためには、複数回\,\texttt{\#include}されると困るヘッダーファイルでは、インクルードガード(include guard)\index{いんくるどがど@インクルードガード}と呼ばれている方法を使う。 + +\begin{lstlisting}[language={C++}] +// val.h + +#ifndef INCLUDE_GUARD_HEADER_VAL_H +#define INCLUDE_GUARD_HEADER_VAL_H + +inline int val ; + +#endif +\end{lstlisting} + +このように記述した\texttt{val.h}を複数回\,\texttt{\#include}しても、最初の\texttt{ifndef}のみがコンパイル対象になるため、問題は起こらない。 + +インクルードガードは以下の様式を持つ。 + +\begin{lstlisting}[style=grammar] +#ifndef 十分にユニークなマクロ名 +#define 十分にユニークなマクロ名 + +// 重複してコンパイルされたくないコードをここに書く + +#endif +\end{lstlisting} + +\texttt{十分にユニークなマクロ名}は全ソースファイル中で衝突しないそのヘッダーに固有のマクロ名を使う。慣習的に推奨される方法としてはすべて大文字を使い、十分に長いマクロ名にすることだ。 + +\hypersection{ch4002}{\#define} +\index{\#define@\texttt{\#define}} + +\texttt{\#define}はマクロ置換を行う。マクロにはオブジェクト風マクロ(object--like macro)と関数風マクロ(function--like macro)がある。風というのは、マクロはオブジェクトでも関数でもないからだ。ただ、文法上オブジェクトや関数の似ているだけで、実態はトークン列の愚直な置換だ。 + +\hypersubsection{ch400201}{オブジェクト風マクロ} +\index{おぶじえくとふうまくろ@オブジェクト風マクロ} + +オブジェクト風マクロの文法は以下のとおり。 + +\begin{lstlisting}[style=grammar] +#define マクロ名 置換リスト 改行文字 +\end{lstlisting} + +\texttt{\#define}以降の行では、マクロ名が置換リストに置き換わる。 + +\begin{lstlisting}[language={C++}] +#define ONE 1 +#define ONE_PLUS_ONE ONE + ONE +#define (@\textcolor{black}{\texttt{GNU GNU's is NOT UNIX}}@) + +ONE +ONE_PLUS_ONE +\end{lstlisting} + +これをプリプロセスすると以下のソースコードになる。 + +\begin{lstlisting}[language={C++}] +1 +1 + 1 +\end{lstlisting} + +マクロ名\texttt{ONE}は\texttt{1}に置換される。 + +マクロ名\texttt{ONE\_PLUS\_ONE}は\texttt{ONE + ONE}に置換される。置換された結果に別のマクロ名があれば、そのマクロ名も置換される。 + +あるマクロ名を置換した結果、そのマクロ名が現れても再帰的に置換されることはない。 + +\begin{lstlisting}[language={C++}] +#define GNU (@\textcolor{black}{\texttt{GNU's NOT UNIX!}}@) + +GNU +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +(@\textcolor{black}{\texttt{GNU's NOT UNIX!}}@) +\end{lstlisting} + +マクロ名\texttt{GNU}を展開するとトークン`GNU'が現れるが、これは置換されたマクロ名と同じなので、再帰的に置換されることはない。 + +\hypersubsection{ch400202}{関数風マクロ} +\index{かんすうふうまくろ@関数風マクロ} + +関数風マクロの文法は以下のとおり。 + +\begin{lstlisting}[style=grammar] +#define マクロ名( 識別子リスト ) 置換リスト 改行文字 +\end{lstlisting} + +関数風マクロはあたかも関数のように記述できる。関数風マクロに実引数として渡したトークン列は、置換リスト内で仮引数としての識別子で参照できる。 + +\begin{lstlisting}[language={C++}] +#define NO_ARGUMENT() No argument +#define ONE_ARGUMENT( ARG ) begin ARG end +#define MAKE_IT_DOUBLE( ARG ) ONE_ARGUMENT( ARG ARG ) + +NO_ARGUMENT() +ONE_ARGUMENT( foo bar ) +MAKE_IT_DOUBLE( foo bar ) +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +No argument +begin foo bar end +begin foo bar foo bar end +\end{lstlisting} + +複数の引数を取るマクロへの実引数は、カンマで区切られたトークン列を渡す。 + +\begin{lstlisting}[language={C++}] +#define TWO( A, B ) A B +#define THREE( A, B, C ) C B A + +TWO( 1 2, 3 4 ) +THREE( 1, 2, 3 ) +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +1 2 3 4 +3 2 1 +\end{lstlisting} + +ただし、括弧で囲まれたトークン列の中にあるカンマは、マクロの実引数の区切りとはみなされない。 + +\begin{lstlisting}[language={C++}] +#define MACRO( A ) A + +MACRO( (a,b) ) +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +(a,b) +\end{lstlisting} + +\hypersubsection{ch400203}{\texttt{\_}\texttt{\_}VA\texttt{\_}ARGS\texttt{\_}\texttt{\_} (可変長引数マクロ)} +\index{\_\_VA\_ARGS\_\_@\texttt{\_\_VA\_ARGS\_\_}}\index{かへんちようひきすう@可変長引数マクロ} + +\texttt{\#define}の識別子リストを\texttt{...}だけにしたマクロは、可変長引数マクロになる。このときマクロの実引数のトークン列は、置換リストの中で\,\texttt{\_\_VA\_ARGS\_\_}\,として参照できる。 + +\begin{lstlisting}[language={C++}] +#define MACRO(...) __VA_ARGS__ + +MACRO( You can write , and ,, or even ,,,, ) +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +You can write , and ,, or even ,,,, +\end{lstlisting} + +カンマも含めてすべてのトークン列がそのまま\,\texttt{\_\_VA\_ARGS\_\_}\,で参照できる。 + +可変長引数マクロの識別子リストに仮引数と\texttt{...}を書いたマクロの置換リストでは、仮引数の数だけの実引数は仮引数で参照され、残りが\,\texttt{\_\_VA\_ARGS\_\_}\,で参照される。 + +\begin{lstlisting}[language={C++}] +#define MACRO( X, Y, Z, ... ) X Y Z and __VA_ARGS__ + +MACRO( 1,2,3,4,5,6 ) +\end{lstlisting} + +これは以下のように置換される + +\begin{lstlisting}[language={C++}] +1 2 3 and 4,5,6 +\end{lstlisting} + +\texttt{X}, \texttt{Y}, \texttt{Z}にそれぞれ\texttt{1}, \texttt{2}, \texttt{3}が入り、\texttt{\_\_VA\_ARGS\_\_}には\texttt{4}, \texttt{5}, \texttt{6}が入る。 + +\hypersubsection{ch400204}{\texttt{\_}\texttt{\_}VA\texttt{\_}OPT\texttt{\_}\texttt{\_}} +\index{\_\_VA\_OPT\_\_@\texttt{\_\_VA\_OPT\_\_}} + +\texttt{\_\_VA\_OPT\_\_}は可変長引数マクロで\,\texttt{\_\_VA\_ARGS\_\_}\,にトークン列が渡されたかどうかで置換結果を変えることができる。 + +\texttt{\_\_VA\_OPT\_\_}は可変引数マクロの置換リストでのみ使える。\texttt{\_\_VA\_OPT\_\_(content)}は\,\texttt{\_\_VA\_ARGS\_\_}\,{\allowbreak}にトークンがない場合はトークンなしに置換され、トークンがある場合はトークン列\texttt{content}に置換される。 + +\begin{lstlisting}[language={C++}] +#define MACRO( X, ... ) f( X __VA_OPT__(,) __VA_ARGS__ ) + +MACRO(1) +MACRO(1,2) +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +f( 1 ) +f( 1, 2 ) +\end{lstlisting} + +\texttt{MACRO(1)}は\texttt{X}が\texttt{1}になり、\texttt{\_\_VA\_ARGS\_\_}にはトークンがないので、\texttt{\_\_VA\_OPT\_\_(,)}は空に置換される。結果として\texttt{f(1)}となる。 + +\texttt{MACRO(1,2)}は、\texttt{X}が\texttt{1}になり、\texttt{\_\_VA\_ARGS\_\_}にはトークン\texttt{2}が入るので、\texttt{\_\_VA\_OPT\_\_(,)}は\,\texttt{,}\,に置換される。結果として\texttt{f(1,2)}となる。 + +\texttt{\_\_VA\_OPT\_\_}は\,\texttt{\_\_VA\_ARGS\_\_}\,に実引数となるトークン列がなければ空に置換されるので、このようにトークン列の有無によってカンマなどの文法上必須のトークン列の有無を切り替えたい場合に使うことができる。 + +\hypersubsection{ch400205}{\#演算子} +\index{\#@\texttt{\#}} + +\texttt{\#}\,はマクロ実引数を文字列リテラルにする。 + +\texttt{\#}\,は関数風マクロの置換リストの中のみで使うことができる。\texttt{\#}\,は関数風マクロの仮引数の識別子の直前に書くことができる。\texttt{\#}\,が直前に書かれた識別子は、マクロ実引数のトークン列の文字列リテラルになる。 + +\begin{lstlisting}[language={C++}] +#define STRING( X ) # X + +STRING( hello ) +STRING( hello world ) +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +"hello" +"hello world" +\end{lstlisting} + +また、可変長マクロと組み合わせた場合、 + +\begin{lstlisting}[language={C++}] +#define STRING( ... ) # __VA_ARGS__ + +STRING() +STRING( hello,world ) +\end{lstlisting} + +以下のように置換される。 + +\begin{lstlisting}[language={C++}] +"" +"hello,world" +\end{lstlisting} + +\hypersubsection{ch400206}{\#\#演算子} +\index{\#\#@\texttt{\#\#}} + +\texttt{\#\#}\,はマクロ実引数の結合を行う。 + +\texttt{\#\#}\,は関数風マクロの置換リストの中にしか書けない。\texttt{\#\#}\,は両端にマクロの仮引数の識別子を書かなければならない。\texttt{\#\#}\,は両端の識別子の参照するマクロ実引数のトークン列を結合した置換を行う。 + +\begin{lstlisting}[language={C++}] +#define CONCAT( A, B ) A ## B + +CONCAT( foo, bar ) +CONCAT( aaa bbb, ccc ddd) +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +foobar +aaa bbbccc ddd +\end{lstlisting} + +結合した結果のトークンはさらにマクロ置換の対象となる。 + +\begin{lstlisting}[language={C++}] +#define CONCAT( A, B ) A ## B +#define FOOBAR hello + +CONCAT( FOO, BAR ) +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +hello +\end{lstlisting} + +\texttt{CONCAT(FOO,BAR)}は\texttt{FOOBAR}に置換され、\texttt{FOOBAR}という名前のマクロ名があるためにさらに\texttt{hello}に置換される。 + +\hypersubsection{ch400207}{複数行の置換リスト} + +\texttt{\#define}ディレクティブの置換リストは複数行に渡って書くことができない。これは文法上の制約によるものだ。\texttt{\#define}ディレクティブは改行文字で終端される。 + +しかし、関数やクラスを生成するような複雑なマクロは、複数行に分けて書きたい。 + +\begin{lstlisting}[language={C++}] +#define LIST_NAME2( PREFIX, TYPE ) PREFIX ## TYPE +#define LIST_NAME( TYPE ) LIST_NAME2( list_, TYPE ) + +#define DEFINE_LIST( TYPE ) struct LIST_NAME(TYPE){TYPE value ;LIST_NAME(TYPE) * prev ;LIST_NAME(TYPE) * next ;} ; + +DEFINE_LIST(int) +DEFINE_LIST(double) +\end{lstlisting} + +この場合、行末にバックスラッシュに続けて改行を書くと、バックスラッシュと改行がプリプロセッサーによって削除される。 + +上の例は以下のように、プリプロセッサーとしては比較的わかりやすく書くことができる。 + +\begin{lstlisting}[language={C++}] +#define LIST_NAME2( PREFIX, TYPE ) PREFIX ## TYPE +#define LIST_NAME( TYPE ) LIST_NAME2( list_, TYPE ) + +#define DEFINE_LIST( TYPE )\ +struct LIST_NAME(TYPE)\ +{\ + TYPE value ;\ + LIST_NAME(TYPE) * prev ;\ + LIST_NAME(TYPE) * next ;\ +} ; + +DEFINE_LIST(int) +DEFINE_LIST(double) +\end{lstlisting} + +C++ではテンプレートがあるために、このようなマクロを書く必要はない。 + +\hypersubsection{ch400208}{\#undefディレクティブ} +\index{\#undef@\texttt{\#undef}} + +\texttt{\#undef}はそれ以前に定義されたマクロを削除する。 + +\begin{lstlisting}[language={C++}] +#define FOO BAR +FOO +#undef FOO +FOO +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +BAR +FOO +\end{lstlisting} + +\hypersection{ch4003}{条件付きソースファイル選択} + +\texttt{\#if}, \texttt{\#elif}, \texttt{\#else}, \texttt{\#endif}, \texttt{\#ifdef}, \texttt{\#ifndef}は条件付きのソースファイルの選択(conditional inclusion)を行う。これは条件付きコンパイル\index{じようけんつきこんぱいる@条件付きコンパイル}に近い機能を提供する。 + +\hypersubsection{ch400301}{プリプロセッサーの定数式} +\index{ぷりぷろせつさ@プリプロセッサー!ていすうしき@定数式} + +プリプロセッサーで使える条件式は、C++の条件式と比べてだいぶ制限がある。基本的には整数定数式で、\texttt{true}, \texttt{false}が使えるほか、\texttt{123}, \texttt{1+1}, \texttt{1 == 1}, \texttt{1 < 1}のような式も使える。ただし、識別子はすべてマクロ名として置換できるものは置換され、置換できない識別子は、\texttt{true}, \texttt{false}以外はキーワードも含めてすべて\texttt{0}に置換される。 + +したがって、プリプロセッサーで以下のように書くと、 +\begin{lstlisting}[language={C++}] +#if UNDEFINED +#endif +\end{lstlisting} +以下のように書いたものと同じになる。 +\begin{lstlisting}[language={C++}] +#if 0 +#endif +\end{lstlisting} + +プリプロセッサーであるので、C++としての\texttt{constexpr}変数や\texttt{constexpr}関数も使えない。 + +\begin{lstlisting}[language={C++}] +constexpr int x = 1 ; + +#if x +hello +#endif +\end{lstlisting} + +これは以下のように置換される。 + +\begin{lstlisting}[language={C++}] +constexpr int x = 1 ; +\end{lstlisting} + +プリプロセッサーはC++の文法と意味を理解しない。単にトークン列として処理する。 + +以下の例はエラーになる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +constexpr int f() { return 1 ; } + +#if f() +#endif +\end{lstlisting} + +なぜならば、\texttt{0()}は整数定数式として合法なコードではないからだ。何度も言うように、プリプロセッサーはC++の文法と意味を理解しない。 + +プリプロセッサーの定数式では、特殊なマクロ風の式を使うことができる。\texttt{defined}\index{defined@\texttt{defined}}と\,\texttt{\_\_has\_include}\index{\_\_has\_include@\texttt{\_\_has\_include}}だ。 + +\texttt{defined}は以下の文法を持つ。 + +\begin{lstlisting}[style=grammar] +defined 識別子 +defined ( 識別子 ) +\end{lstlisting} + +\texttt{defined}は識別子がそれ以前の行で\,\texttt{\#define}でマクロとして定義されていて\,\texttt{\#undef}で取り消されていない場合\texttt{1}になり、それ以外の場合\texttt{0}になる。 + +\begin{lstlisting}[language={C++}] +// #if 0 +#if defined MACRO +#endif + +#define MACRO + +// #if 1 +#if defined MACRO +#endif + +#undef MACRO + +// #if 0 +#if defined MACRO +#endif +\end{lstlisting} + +\texttt{\_\_has\_include}は以下の文法を持つ。 + +\begin{lstlisting}[style=grammar] +__has_include ( < ヘッダーファイル名 > ) +__has_include ( " ヘッダーファイル名 " ) +__has_include ( 文字列リテラル ) +__has_include ( < マクロ > ) +\end{lstlisting} + +1番目と2番目は、指定されたヘッダーファイル名がシステムに存在する場合\texttt{1}に、そうでない場合\texttt{0}になる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// の存在を確認してから#includeする +#if __has_include() +# include +#endif + +// "mylibrary.h"の存在を確認してから#includeする +#if __has_include("mylibrary.h") +# include "mylibrary.h" +#endif +\end{lstlisting} + +3番目と4番目は、1番目と2番目が適用できない場合に初めて考慮される。その場合、まず通常通りにプリプロセッサーのマクロ置換が行われる。 + +\begin{lstlisting}[language={C++}] +#define STDIO "stdio.h" + +#if __has_include( STDIO ) +#endif + +#define STDLIB stdlib.h + +#if __has_include( ) +#endif +\end{lstlisting} + +\hypersubsection{ch400302}{\#ifディレクティブ} +\index{\#if@\texttt{\#if}} + +\texttt{\#if}ディレクティブは以下の文法を持つ。 + +\begin{lstlisting}[style=grammar] +#if 定数式 改行文字 + +#endif +\end{lstlisting} + +もし定数式がゼロの場合、\texttt{\#if}と\,\texttt{\#endif}で囲まれたトークン列は処理されない。定数式が非ゼロの場合、処理される。 + +\begin{lstlisting}[language={C++}] +#if 0 +This line will be skipped. +#endif + +#if 1 +This line will be processed. +#endif +\end{lstlisting} + +これをプリプロセスすると以下のようになる。 + +\begin{lstlisting}[language={C++}] +This line will be processed. +\end{lstlisting} + +\texttt{\#if 0}は処理されないので、\texttt{\#endif}までのトークン列は消える。 + +\hypersubsection{ch400303}{\#elifディレクティブ} +\index{\#elif@\texttt{\#elif}} + +\texttt{\#elif}ディレクティブは、C++でいう\texttt{else if}に相当する。 + +\begin{lstlisting}[style=grammar] +#elif 定数式 改行文字 +\end{lstlisting} + +\texttt{\#elif}ディレクティブは\,\texttt{\#if}ディレクティブと\,\texttt{\#endif}ディレクティブの間に複数書くことができる。\texttt{\#elif}のある\,\texttt{\#if}が処理される場合、\texttt{\#if}から\,\texttt{\#elif}の間のトークン列が処理される、\texttt{\#if}が処理されない場合、\texttt{\#elif}が\,\texttt{\#if}と同じように定数式を評価して処理されるかどうかが判断される。\texttt{\#elif}が処理される場合、処理されるトークン列は次の\,\texttt{\#elif}もしくは\,\texttt{\#endif}までの間のトークン列になる。 + +以下の例は、すべて\texttt{YES}のトークンがある行のみ処理される。 + +\begin{lstlisting}[language={C++}] +#if 1 +YES +#elif 1 +NO +#endif + +#if 0 +NO +#elif 1 +YES +#endif + +#if 0 +NO +#elif 1 +YES +#elif 1 +NO +#endif + +#if 0 +NO +#elif 0 +NO +#elif 1 +YES +#endif +\end{lstlisting} + +プリプロセスした結果は以下のとおり。 + +\begin{lstlisting}[language={C++}] +YES +YES +YES +YES +\end{lstlisting} + +\hypersubsection{ch400304}{\#elseディレクティブ} +\index{\#else@\texttt{\#else}} + +\texttt{\#else}ディレクティブはC++でいう\texttt{else}に相当する。 + +\texttt{\#else}ディレクティブは\,\texttt{\#if}ディレクティブと\,\texttt{\#endif}ディレクティブの間に書くことができる。もし\,\texttt{\#if}と\,\texttt{\#elif}ディレクティブが処理されない場合で\,\texttt{\#else}ディレクティブがある場合、\texttt{\#else}から\,\texttt{\#endif}までのトークン列が処理される。 + +以下の例は、\texttt{YES}のトークンがある行のみ処理される。 + +\begin{lstlisting}[language={C++}] +#if 1 +YES +#else +NO +#endif + +#if 0 +NO +#else +YES +#endif + +#if 0 +NO +#elif 1 +YES +#else +NO +#endif +\end{lstlisting} + +\hypersubsection{ch400305}{\#ifdef, \#ifndefディレクティブ} +\index{\#ifdef@\texttt{\#ifdef}}\index{\#ifndef@\texttt{\#ifndef}} + +\begin{lstlisting}[style=grammar] +#ifdef 識別子 +#ifndef 識別子 +\end{lstlisting} +は、それぞれ以下と同じ意味になる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=grammar] +#if defined 識別子 +#if !defined 識別子 +\end{lstlisting} + +例、 + +\begin{lstlisting}[language={C++}] +#ifdef MACRO +#endif + +// 上と同じ +#if defined MACRO +#endif + + +#ifndef MACRO +#endif + +// 上と同じ +#if !defined MACRO +#endif +\end{lstlisting} + +\hypersection{ch4004}{\#lineディレクティブ} +\index{\#line@\texttt{\#line}} + +\texttt{\#line}ディレクティブはディレクティブの次の行の行番号と、ソースファイル名を変更する。これは人間が使うのではなく、ツールによって生成されることを想定した機能だ。 + +以下の文法の\,\texttt{\#line}ディレクティブは、\texttt{\#line}ディレクティブの次の行の行番号をあたかも数値で指定した行番号であるかのように振る舞わせる。 + +\begin{lstlisting}[style=grammar] +#line 数値 改行文字 +\end{lstlisting} + +数値として0もしくは2147483647より大きい数を指定した場合の挙動は未定義となる。 + +以下の例はコンパイルエラーになるが、コンパイルエラーメッセージはあたかも102行目に問題があるかのように表示される。 + +\begin{lstlisting}[language={C++}] +// 1行目 +// 2行目 +#line 100 // 3行目 +// 100行目 +// 101行目 +ill-formed line // 102行目 +\end{lstlisting} + +以下の例は\texttt{999}を出力するコードだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +#include +int main() +{ +#line 999 + std::cout << __LINE__ ; +} +\end{lstlisting} + +以下の文法の\,\texttt{\#line}ディレクティブは、次の行の行番号を数値にした上で、ソースファイル名をソースファイル名にする。 + +\begin{lstlisting}[style=grammar] +#line 数値 "(@\ifColor\textcolor[rgb]{0,0.533,0}{ソースファイル名}\else\textcolor{black}{ソースファイル名}\fi@)" 改行文字 +\end{lstlisting} + +例、 + +\begin{lstlisting}[language={C++}] +#line 42 "answer.cpp" +\end{lstlisting} + +以下の文法の\,\texttt{\#line}ディレクティブは、プリプロセッサートークン列をプリプロセスし、上の2つの文法のいずれかに合致させる。 + +\begin{lstlisting}[style=grammar] +#line プリプロセッサートークン列 改行文字 +\end{lstlisting} + +例、 + +\begin{lstlisting}[language={C++}] +#define LINE_NUMBER 123 +#line LINE_NUMBER +\end{lstlisting} + +\hypersection{ch4005}{\#errorディレクティブ} +\index{\#error@\texttt{\#error}} + +\texttt{\#error}ディレクティブはコンパイルエラーを引き起こす。 + +\begin{lstlisting}[style=grammar] +#error 改行文字 +#error トークン列 改行文字 +\end{lstlisting} + +\texttt{\#error}によるコンパイラーのエラーメッセージには\,\texttt{\#error}のトークン列を含む。 + +\texttt{\#error}の利用例としては、\texttt{\#if}と組み合わせるものがある。以下の例は\texttt{CHAR\_BIT}が8でなければコンパイルエラーになるソースファイルだ。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +#include + +#if CHAR_BIT != 8 +#error CHAR_BIT != 8 implementation is not supported. +#endif +\end{lstlisting} + +\texttt{\#if}が処理されなければ、その中にある\,\texttt{\#error}も処理されないので、コンパイルエラーにはならない。 + +\hypersection{ch4006}{\#pragma} +\index{\#pragma@\texttt{\#pragma}} + +\texttt{\#pragma}ディレクティブは実装依存の処理を行う。\texttt{\#pragma}はコンパイラー独自の拡張機能を追加する文法として使われている。 + +文法は以下のとおり。 + +\begin{lstlisting}[style=grammar] +#pragma プリプロセッサートークン列 改行文字 +\end{lstlisting} + +C++では属性が追加されたために、\texttt{\#pragma}を使う必要はほとんどなくなっている。 + +\hypersection{ch4007}{Nullディレクティブ} + +\texttt{Null}ディレクティブとは何もしないプリプロセッサーディレクティブだ。 + +\begin{lstlisting}[style=grammar] +# 改行文字 +\end{lstlisting} + +つまり、単に\,\texttt{\#}\,とだけ書いた行はエラーにはならない。 + +\hypersection{ch4008}{定義済みマクロ名} +\index{ていぎずみまくろめい@定義済みマクロ名} + +いくつかのマクロ名がプリプロセッサーによってあらかじめ定義されている。 + +\begin{small} +%\begin{longtable}[l]{@{}lcc@{}} +\begin{longtable}[l]{p{54mm}p{22mm}p{60mm}} +\hline%\toprule +\textsf{マクロ名} & \textsf{値} & \textsf{意味}\tabularnewline +\hline%\midrule +\endhead +\texttt{\_\_cplusplus} & \texttt{201703L} & C++17時点での値{\newline}将来の規格で増やされる\tabularnewline +\texttt{\_\_DATE\_\_} & \texttt{"Mmm dd yyyy"} & ソースファイルがプリプロセスされた日付{\newline}\texttt{Mmm}は月、\texttt{dd}は日、\texttt{yyyy}は年{\newline}月の文字列は\texttt{asctime}が生成するものと同じ{\newline}日が1桁の場合、\texttt{dd}の最初の文字は空白文字\tabularnewline +\texttt{\_\_FILE\_\_} & 文字列リテラル & ソースファイルの名前の文字列リテラル\tabularnewline +\texttt{\_\_LINE\_\_} & 整数リテラル & ソースファイルの現在の行番号\tabularnewline +\texttt{\_\_STDC\_HOSTED\_\_} & 整数リテラル & ホスト実装の場合1{\newline}フリースタンディング実装の場合0\tabularnewline +\texttt{\_\_STDCPP\_DEFAULT\_NEW\_ALIGNMENT\_\_} & 整数リテラル & アライメント\tabularnewline +\hline%\bottomrule +\end{longtable} +\end{small} diff --git a/TeX/300-multiple-source-files.tex b/TeX/300-multiple-source-files.tex new file mode 100644 index 0000000..ea5a588 --- /dev/null +++ b/TeX/300-multiple-source-files.tex @@ -0,0 +1,778 @@ +\hyperchapter{ch41}{分割コンパイル}{分割コンパイル} +\index{ぶんかつこんぱいる@分割コンパイル} + +これまで、プログラムは1つのソースファイルから作っていた。プログラムは複数のソースファイルから作ることもできる。ソースファイルを複数に分割することで、ソースファイルの管理がしやすくなったり、プログラムのビルド時間の短縮にもつながる。 + +\hypersubsection{ch410001}{ソースファイルとコンパイル} +\index{そすふあいる@ソースファイル} + +ソースファイルを分割すると、C++の書き方にも注意が必要になる。だがその前に、複数のソースファイルをコンパイルして1つのプログラムにする方法を学ぶ。 + +\hypersubsection{ch410002}{単一のソースファイルのコンパイル} +\index{そすふあいる@ソースファイル!たんいつの@単一の〜} + +C++のソースファイルをコンパイルして実行可能ファイルを作る方法をいま一度おさらいをしよう。 + +\texttt{source.cpp}という名前のソースファイルがあるとき、ここから\texttt{program}という名前の実行可能ファイルを作るには、 +\begin{lstlisting}[style=terminal] +$ g++ -o program source.cpp +\end{lstlisting} +としていた。毎回このコマンドを入力するのは面倒なので、\texttt{Makefile}を以下のように書いていた。 + +\begin{lstlisting}[language=make] +program: source.cpp + g++ $< -o $@ +\end{lstlisting} + +\hypersubsection{ch410003}{ヘッダーファイルはコピペ} +\index{へつだふあいる@ヘッダーファイル} + +すでに、ソースファイルのほかにヘッダーファイルというファイルも使っている。ヘッダーファイルはソースファイルではない。コンパイル前にソースファイルにコピペされるだけのものだ。 + +例えば以下のような内容の\texttt{header.h}というヘッダーファイルがあるとして、 +\begin{lstlisting}[language={C++}] +// header.h +++i ; +\end{lstlisting} +\texttt{source.cpp}が以下のようであるとき、 +\begin{lstlisting}[language={C++}] +int main() +{ + int i = 0 ; +#include "header.h" +#include "header.h" +#include "header.h" + int result = i ; +} +\end{lstlisting} +\texttt{source.cpp}をコンパイルすると、まずヘッダーファイルが以下のように展開される。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int i = 0 ; +// header.h +++i ; +// header.h +++i ; +// header.h +++i ; + int result = i ; +} +\end{lstlisting} + +ヘッダーファイルとはこれだけのものだ。コンパイラーが\,\texttt{\#include}された場所に、ヘッダーファイルの中身を愚直にコピペするだけだ。 + +\hypersubsection{ch410004}{複数のソースファイルのコンパイル} +\index{そすふあいる@ソースファイル!ふくすうの@複数の〜} + +2つのソースファイル、\texttt{foo.cpp}と\texttt{bar.cpp}からなるプログラムをコンパイルするには、 +\begin{lstlisting}[style=terminal] +$ g++ -o program foo.cpp bar.cpp +\end{lstlisting} +とする。 + +\hypersection{ch4101}{オブジェクトファイル} +\index{おぶじえくとふあいる@オブジェクトファイル} + +単にソースファイルを分割したいだけならば、GCCに分割したソースファイルをすべて指定すればよい。しかしその場合、複数あるソースファイルの1つだけを編集した場合でも、すべてのソースファイルをコンパイルしなければならない。 + +C++では伝統的に、ソースファイルを部分的にコンパイルしてオブジェクトファイルを生成し、オブジェクトファイルをリンクしてプログラムを生成する方法がある。 + +ソースファイルをオブジェクトファイルにコンパイルするのは\texttt{コンパイラー}\index{こんぱいら@コンパイラー}、オブジェクトファイルをプログラムにリンクするのは\texttt{リンカー}\index{りんか@リンカー}の仕事だ。 + +\begin{figure}[htbp] + \centering + \includegraphics[scale=1.0]{fig/fig300-01.eps} + \label{fig:300-01} +\end{figure} + +GCCではC++コンパイラーの名前は\texttt{g++}\index{g++@\texttt{g++}}で、リンカーの名前は\texttt{ld}\index{ld@\texttt{ld}}だ。ただし、C++のオブジェクトファイルをリンクするのにリンカーを直接使うことはない。\texttt{g++}は\texttt{ld}を適切に呼び出してくれるからだ。 + +ソースファイル\texttt{source.cpp}をコンパイルしてオブジェクトファイルを生成するには、\texttt{-c}オプションを使う。 + +\begin{lstlisting}[style=terminal] +$ g++ -c source.cpp +\end{lstlisting} + +生成されるオブジェクトファイルの名前はソースファイルの名前の拡張子を\texttt{.o}に置き換えたものになる。上のコマンドを実行した結果、オブジェクトファイル\texttt{source.o}が生成される。 + +生成したオブジェクトファイルは、\texttt{g++}の入力として使うことで、リンクしてプログラムにすることができる。\texttt{g++}は裏でリンカー\texttt{ld}を適切に呼び出してくれる。 + +\begin{lstlisting}[style=terminal] +$ g++ -o program source.o +\end{lstlisting} + +オブジェクトファイル名を別の名前にしたい場合は、\texttt{-o object-file-name}\index{GCC@GCC(GNU Compiler Collection)!-o object-file-name@\texttt{-o object-file-name}}オプションを使う。 + +\begin{lstlisting}[style=terminal] +$ g++ -o object.o -c source.cpp +\end{lstlisting} + +複数のソースファイル、\texttt{foo.cpp}と\texttt{bar.cpp}からオブジェクトファイルを生成し、リンクして実行可能ファイル\texttt{program}を生成するには以下のようにする。 + +\begin{lstlisting}[style=terminal] +$ ls +bar.cpp foo.cpp +$ g++ -c foo.cpp +$ g++ -c bar.cpp +$ ls +bar.cpp bar.o foo.cpp foo.o +$ g++ -o program foo.o bar.o +$ ls +bar.cpp bar.o foo.cpp foo.o program +\end{lstlisting} + +こうすることによって、1つのソースファイルを編集しただけで、すべてのソースファイルをコンパイルする必要がなくなる。 + +これを\texttt{Makefile}で書くには、出力するファイルと依存するファイルを考える。 + +\begin{itemize} +\item + \texttt{program}は\texttt{foo.o}と\texttt{bar.o}に依存する +\item + \texttt{foo.o}は\texttt{foo.cpp}に依存する +\item + \texttt{bar.o}は\texttt{bar.cpp}に依存する +\end{itemize} + +これを素直に書き出していけばよい。 + +\begin{lstlisting}[style=terminal] +# programはfoo.oとbar.oに依存する +program : foo.o bar.o + g++ -o $@ $^ +# foo.oはfoo.cppに依存する +foo.o : foo.cpp + g++ -c $< +# bar.oはbar.cppに依存する +bar.o : bar.cpp + g++ -c $< +\end{lstlisting} + +\texttt{\${\textasciicircum}}はそのルールの依存するファイル名をすべて空白区切りで得る自動変数だ。この場合、\texttt{foo.o bar.o}に置換される。 + +\hypersection{ch4102}{複数のソースファイルの書き方} +\index{そすふあいる@ソースファイル!ふくすうの@複数の〜} + +C++の1つのソースファイルは、1つの\texttt{翻訳単位}(translation unit)\index{ほんやくたんい@翻訳単位}として扱われる。別の翻訳単位の定義を使うには、さまざまな制約がある。具体的な例で学ぼう。 + +\hypersubsection{ch410201}{関数} +\index{かんすう@関数} + +以下のコードを見てみよう。 + +\begin{lstlisting}[language={C++}] +#include + +void print_int( int x ) +{ + std::cout << x ; +} + +int main() +{ + print_int( 123 ) ; +} +\end{lstlisting} + +このコードには2つの定義がある。\texttt{print\_int}と\texttt{main}だ。 + +関数\texttt{print\_int}を別のソースファイルである\texttt{print\_int.cpp}に分割してみよう。 + +\begin{lstlisting}[language={C++}] +// print_int.cpp +#include + +void print_int( int x ) +{ + std::cout << x ; +} +\end{lstlisting} + +このコードは問題なくコンパイルできる。 + +\begin{lstlisting}[style=terminal] +$ g++ -c print_int.cpp +\end{lstlisting} + +すると残りのソースファイルを\texttt{main.cpp}とすると以下のようになる。 + +\begin{lstlisting}[language={C++}] +// main.cpp +int main() +{ + print_int( 123 ) ; +} +\end{lstlisting} + +このコードはコンパイルできない。なぜならば、C++では名前は使う前に宣言しなければならないからだ。 + +関数を宣言するには、関数の本体以外の部分を書き、セミコロンで終端する。 + +\begin{lstlisting}[language={C++}] +// main.cpp +void print_int( int ) ; + +int main() +{ + print_int( 123 ) ; +} +\end{lstlisting} + +これでコンパイル、リンクができるようになった。 + +\begin{lstlisting}[style=terminal] +$ g++ -c main.cpp +$ g++ -o program main.o print_int.o +\end{lstlisting} + +このとき、\texttt{main.cpp}で関数\texttt{print\_int}を定義することはできない。 + +\begin{lstlisting}[language={C++}] +// エラー、 print_int.cppでも定義されている +void print_int( int ) { } + +int main() +{ + print_int( 123 ) ; +} +\end{lstlisting} + +C++では定義は全翻訳単位に1つしか書くことができないルール、ODR(One Definition Rule、単一定義原則)\index{たんいつていぎげんそく@単一定義原則}\index{ODR(One Definition Rule)}があるからだ。 + +\begin{lstlisting}[language={C++}] +// 宣言 +void f() ; + +// OK、再宣言 +void f() ; + +// 定義 +void f() { } + +// エラー、 再定義 +// ODR違反 +void f() { } +\end{lstlisting} + +なぜODRがあるのか。なぜ定義は1つしか書けないのか。理由は簡単だ。もし定義が複数書けるならば、異なる定義を書くことができてしまうからだ。 + +\begin{lstlisting}[language={C++}] +bool f() { return true ; } +bool f() { return false ; } +\end{lstlisting} + +もし定義を複数書くことができる場合、この関数\texttt{f}は\texttt{true}を返すべきだろうか。それとも\texttt{false}を返すべきだろうか。 + +この問題を防ぐために、C++にはODRがある。 + +複数のソースファイル、つまり複数の翻訳単位からなるプログラムの場合でもODRは適用される。定義はすべての翻訳単位内で1つでなければならない。 + +引数リストが違う関数は別の関数で、別の定義になる。 + +\begin{lstlisting}[language={C++}] +// 定義 +void f() { } + +// OK、別の定義 +void f( int ) { } + +// OK、別の定義 +void f( double ) { } +\end{lstlisting} + +名前は使う前に宣言が必要だが、肝心の定義は別のソースファイルに書いてある。宣言と定義を間違えてしまった場合はエラーになる。 + +\begin{lstlisting}[language={C++}] +// print_int.cpp +// 失敗状態を返す +bool print_int( int x ) +{ + std::cout << x ; + return std::cout.fail() ; +} + +// main.cpp +void print_int( int ) ; + +int main() +{ + // エラー + print_int( 123 ) ; +} +\end{lstlisting} + +このような間違いを防ぐためのお作法として、宣言はヘッダーファイルに書いて\,\texttt{\#include}\index{\#include@\texttt{\#include}}する。 + +\begin{lstlisting}[language={C++}] +// print_int.h +bool print_int( int x ) ; + +// main.cpp +#include "print_int.h" + +int main() +{ + // 間違えない + bool result = print_int( 123 ) ; +} +\end{lstlisting} + +\hypersubsection{ch410202}{変数} +\index{へんすう@変数} + +変数にも宣言と定義がある。通常、変数の宣言は定義を兼ねる。 + +\begin{lstlisting}[style=grammar] +// 宣言かつ定義 +int variable ; +\end{lstlisting} + +そのため、別の翻訳単位の変数を使うために変数を書くと、定義が重複してしまい、ODR違反になる。 + +\begin{lstlisting}[language={C++}] +// global.cpp +int variable ; + +// main.cpp +// エラー、 ODR違反 +int variable ; + +int main() +{ + variable = 0 ; +} +\end{lstlisting} + +変数を定義せずに宣言だけしたい場合は、\texttt{extern}キーワード\index{extern@\texttt{extern}}を使う。 + +\begin{lstlisting}[language={C++}] +// global.cpp +int variable ; + +// main.cpp +// OK +// 別の翻訳単位の定義を参照する +extern int variable ; + +int main() +{ + variable = 123 ; +} +\end{lstlisting} + +\texttt{extern}キーワードを名前空間スコープで宣言された変数に使うと、定義せずに別の翻訳単位の定義を参照する意味になる。 + +変数の場合も、間違いを防ぐためにヘッダーファイルに書いて\,\texttt{\#include}\index{\#include@\texttt{\#include}}するとよい。 + +\begin{lstlisting}[language={C++}] +// global.h +extern int variable ; + +// main.cpp +#include "global.h" + +int main() +{ + variable = 123 ; +} +\end{lstlisting} + +\hypersubsection{ch410203}{インライン関数/インライン変数} +\index{いんらいんかんすう@インライン関数}\index{いんらいんへんすう@インライン変数} + +変数や関数の定義はODRにより重複できない。ということはヘッダーファイルに書いて複数の翻訳単位で\,\texttt{\#include}できないということだ。 + +\begin{lstlisting}[language={C++}] +// library.h +std::string delimiter{"\n"} ; + +void print_int( int x ) +{ std::cout << x << delimiter ; } + +// foo.cpp +// エラー、 ODR違反 +#include "library.h" + +// bar.cpp +// エラー、 ODR違反 +#include "library.h" +\end{lstlisting} + +\texttt{library.h}には宣言だけを書いて、別途翻訳単位となるソースファイル、例えば\texttt{library.cpp}を用意しなければならない。 + +\begin{lstlisting}[language={C++}] +// library.h +void print_int( int x ) ; + +// library.cpp +std::string delimiter{"\n"} ; + +void print_int( int x ) +{ std::cout << x << delimiter ; } +\end{lstlisting} + +小さなライブラリの場合、この制約は煩わしい。できればヘッダーファイルだけで済ませてしまいたい。このためにC++には特別なODRを例外的に回避する方法がある。 + +キーワード\texttt{inline}\index{inline@\texttt{inline}}を付けて定義した関数と変数は、インライン関数、インライン変数となる。 + +\begin{lstlisting}[language={C++}] +// library.h +// インライン変数 +inline std::string delimiter{"\n"} ; +// インライン関数 +inline void print_int( int x ) +{ std::cout << x << delimiter ; } +\end{lstlisting} + +インライン関数とインライン変数は、複数の翻訳単位で重複して定義できる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +// foo.cpp +#include "library.h" + +// bar.cpp +#include "library.h" +\end{lstlisting} + +\texttt{inline}はODRを例外的に回避できるとはいえ、強い制約がある。 + +\vskip 1.0zw +\noindent +\textsf{1. 異なる翻訳単位に限る} + +同じ翻訳単位の中で重複することはできない。 + +\begin{lstlisting}[language={C++}] +// 1つの翻訳単位 +inline int variable ; +// エラー、 再定義 +inline int variable ; +\end{lstlisting} + +\vskip 1.0zw +\noindent +\textsf{2. 同じトークン列である} + +インライン関数、インライン変数の定義のトークン列、つまりソースコードの文字列は完全に同じでなければならない。 + +たとえば以下はトークン列が違う。 + +\begin{lstlisting}[language={C++}] +inline int f( int x ) { return x ; } +inline int f( int y ) { return y ; } +\end{lstlisting} + +インライン関数と変数のトークン列を同じにするには、ヘッダーファイルに書いて\,\texttt{\#include}で取り込むことを徹底する。 + +\vskip 1.0zw +\noindent +\textsf{3. 意味が同じである} + +同じトークン列でも意味が異なることがある。 + +\begin{lstlisting}[language={C++}] +// foo.cpp +void f( int ) { } +inline bool g( ) +{ + return f( 0 ) ; +} + +// bar.cpp +void f( double ) { } +inline bool g() +{ + return f( 0 ) ; +} +\end{lstlisting} + +\texttt{foo.cpp}のインライン関数\texttt{g}は\texttt{f(int)}を呼び出すが、\texttt{bar.cpp}のインライン関数\texttt{g}は\texttt{f(double)}を呼び出す。インライン関数\texttt{g}のトークン列はどちらも同じだが、意味が異なる。 + +ODRの例外的な回避の怖いところは、間違えてしまってもコンパイラーがエラーメッセージを出してくれる保証がないところだ。上の同じトークン列で違う意味のような関数は、そのままコンパイルが通ってリンクされ、実行可能なプログラムが生成されてしまうかもしれない。そのようなプログラムの挙動がどうなるかはわからない。この理由は、ODR違反を完全に発見するコンパイラーの実装が技術的に困難だからだ。ODR違反をしないのはユーザーの責任だ。 + +インライン変数とインライン関数はわざわざ翻訳単位を分けて分割コンパイルするまでもないライブラリに使うとよい。 + +\hypersubsection{ch410204}{クラス} +\index{くらす@クラス} + +クラスにも宣言と定義がある。 +\index{くらす@クラス!せんげん@宣言}\index{くらす@クラス!ていぎ@定義} + +\begin{lstlisting}[style=grammar] +// 宣言 +struct Foo ; + +// 定義 +struct Foo +{ + int data_member ; + int member_function() ; +} ; +\end{lstlisting} + +クラスを複数の翻訳単位で使うには、関数と同じように宣言と定義に分ければよいと考えるかもしれないが、残念ながらクラスの宣言だけでできることは少ない。 + +クラスの宣言だけでできることは、クラス名を型名として使うとか、クラスのポインター型を作るぐらいのものだ。 + +\begin{lstlisting}[language={C++}] +struct Foo ; +using Bar = Foo ; +Foo * ptr = nullptr ; +\end{lstlisting} + +宣言だけされたクラスのオブジェクトを作ることはできないし、ポインターの演算もできない。 + +\begin{lstlisting}[language={C++}] +struct Foo ; + +int main() +{ + // エラー + Foo foo ; + + Foo * ptr = nullptr ; + // エラー + ++ptr ; +} +\end{lstlisting} + +この理由は、宣言だけされたクラスは\texttt{不完全型}(Incomplete type)\index{ふかんぜんかた@不完全型}という特別な扱いの型になるからだ。クラスのオブジェクトを作ったりポインター演算をするには、クラスのオブジェクトのサイズを決定する必要があるが、そのための情報はまだコンパイラーが得ていないために起こる制約だ。 + +クラスの定義では、インライン変数やインライン関数と同じく、ODRの例外的な回避が認められている。条件も同じで、1. 異なる翻訳単位で、2. 同じトークン列で、3. 意味も同じ場合だ。 + +ODR違反を起こさないために、クラス定義はインクルードファイルに書いて\,\texttt{\#include}\index{\#include@\texttt{\#include}}するのがお作法だ。 + +\begin{lstlisting}[language={C++}] +// Foo.h +// クラス定義 +struct Foo +{ + int data_member ; + // メンバー関数の宣言 + int member_function() const noexcept; +} ; + +// Foo.cpp +#include "Foo.h" +// メンバー関数の定義 +int Foo::member_function() const noexcept +{ + return data_member ; +} + +// main.cpp +#include "Foo.h" + +int main() +{ + Foo foo ; + foo.data_member = 42 ; + int value = foo.member_function() ; +} +\end{lstlisting} + +クラス定義の中で定義されたメンバー関数は、自動的にインライン関数になる。 + +\begin{lstlisting}[language={C++}] +// Foo.h +struct Foo +{ + int data_member ; + // インライン関数 + int member_function() const noexcept + { + return data_member ; + } +} ; +\end{lstlisting} + +このように書くと、ヘッダーファイル\texttt{Foo.h}を\,\texttt{\#include}するだけでどこでもクラス\texttt{Foo}が使えるようになる。メンバー関数を定義するための\texttt{Foo.cpp}は必要がなくなる。 + +クラスのデータメンバーは具体的なオブジェクトではないので、インライン変数ではない。 + +\begin{lstlisting}[language={C++}] +struct S +{ + // これはオブジェクトではない + int data_member ; +} ; + +void main() +{ + // オブジェクト + S s ; + // サブオブジェクト + s.data_member ; +} +\end{lstlisting} + +\hyperparagraph{ch4102040001}{staticメンバー} +\index{static@\texttt{static}}\index{くらす@クラス!static@\texttt{static}} + +クラスのメンバーは非\texttt{static}メンバーと\texttt{static}メンバーに分けることができる。\texttt{static}メンバーは\texttt{static}キーワードを付けて宣言する。 + +\begin{lstlisting}[language={C++}] +struct S +{ + // 非staticメンバー + int data_member ; + void member_function() ; + + // staticメンバー + inline static int static_data_member ; + static void static_member_function() ; +} ; +\end{lstlisting} + +\texttt{static}メンバー関数はクラスのオブジェクトには依存していない。そのため、クラスのオブジェクトなしで呼び出すことができる。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +struct S +{ + // 非staticメンバー + void member_function() { } + + // staticメンバー + static void static_member_function() { } +} ; + +int main() +{ + S s ; + // オブジェクトが必要 + s.member_function() ; + + // オブジェクトは不要 + S::static_member_function() ; + // このように呼び出すこともできる + s.static_member_function() ; +} +\end{lstlisting} + +\texttt{static}メンバー関数の呼び出しにクラスのオブジェクトを必要としない。そのため、\texttt{this}も使うことはできない。 + +\begin{lstlisting}[language={C++}] +struct S +{ + int data_member() ; + + void f() + {// thisが使える + this->data_member ; + } + + static void g() + {// thisは使えない + } +} ; +\end{lstlisting} + +\texttt{static}データメンバーはクラスのオブジェクトの外の独立したオブジェクトだ。\texttt{static}データメンバーのクラス定義内での宣言は定義ではないので、クラスの定義外で定義する必要がある。 + +\begin{lstlisting}[language={C++}] +struct S +{ + // 宣言 + static int static_data_member ; +} ; +// 定義 +int S::static_data_member ; + +int main() +{ + S::static_data_member = 123 ; +} +\end{lstlisting} + +複数の翻訳単位からなるプログラムの場合、ODRにより定義は1つしか書けないので、どこか1つのソースファイルだけに定義を書くことになる。 + +\begin{lstlisting}[language={C++}] +// S.h +struct S +{ + // 宣言 + static int static_data_member ; +} ; + +// S.cpp +#include "S.h" +// 定義 +int S::static_data_member ; +\end{lstlisting} + +これは面倒なので、通常は\texttt{static}変数はインライン変数にする。 + +\begin{lstlisting}[language={C++}] +// S.h +struct S +{ + // インライン変数かつstatic変数 + inline static int static_data_member ; +} ; +\end{lstlisting} + +これで\texttt{static}変数を定義するだけのソースファイルを用意する必要はない。ただしインライン変数はC++17以降の機能なので、読者が昔のC++で書かれたコードを読む際には、まだ昔ながらの\texttt{static}データメンバーの定義に出くわすだろうから、覚えておこう。 + +\texttt{static}メンバーはクラススコープの下に関数と変数というだけで、その実態は名前空間スコープ内の関数と変数と同じだ。 + +\begin{lstlisting}[language={C++}] +// 名前空間 +namespace A { + int variable ; + void function() { } +} +(@\ifTombow\pagebreak\fi@) +// クラス +struct B { + inline static int variable ; + static void function() { } +} ; + +int main() +{ + // 名前空間 + A::variable = 1 ; + A::function() ; + // クラス + B::variable = 1 ; + B::function() ; +} +\end{lstlisting} + +\hypersubsection{ch410205}{テンプレート} +\index{てんぷれと@テンプレート} + +テンプレートにもODRの例外が認められている。 + +テンプレートは具体的なテンプレート引数が与えられて実体化する。 + +\begin{lstlisting}[language={C++}] +template < typename T > +struct holder +{ + T value ; +} ; + +holder a ; +holder b ; +\end{lstlisting} + +このため、翻訳単位ごとに、同じトークン列で同じ意味のテンプレートコードが必要だ。インクルードファイルに書いて\,\texttt{\#include}するお作法も同じだ。 + +\begin{lstlisting}[language={C++}] +// holder.h +template < typename T > +struct holder +{ + T value ; +} ; + +holder a ; +holder b ; +\end{lstlisting} + +C++に将来的に追加される予定のモジュールが入るまでは、テンプレートコードはすべてをインクルードファイルに書いて\,\texttt{\#include}して使う慣習が続くだろう。 diff --git a/TeX/400-gdb.tex b/TeX/400-gdb.tex new file mode 100644 index 0000000..6ebe679 --- /dev/null +++ b/TeX/400-gdb.tex @@ -0,0 +1,885 @@ +\hyperchapter{ch42}{デバッガー}{デバッガー} +\index{でばつが@デバッガー} + +読者は複雑なコードを書く際に間違ったコードを書くことだろう。間違ったコードは直せばよい。問題はどこが間違っているのかわからない場合だ。 + +例えば以下のコードは\texttt{1}から\texttt{10}までの整数を標準出力するはずのプログラムだ。 + +\begin{lstlisting}[language={C++}] +int main() +{ + for ( int i = 1 ; i < 10 ; ++i ) + std::cout << i ; +} +\end{lstlisting} + +しかし実際に実行してみると、\texttt{1}から\texttt{9}までの整数しか標準出力しない。なぜだろうか。 + +読者の中にはコード中の問題のある箇所に気が付いた人もいるだろう。これはたったの5行のコードで、問題の箇所も1箇所だ。これが数百行、数千行になり、関数やクラスを複雑に使い、問題の原因は複数の箇所のコードの実行が組み合わさった結果で、しかも自分で書いたコードなので正しく書いたはずだという先入観がある場合、たとえコードとしてはささいな間違いであったとしても、発見は難しい。 + +こういうとき、実際にコードを1行ずつ実行したり、ある時点でプログラムの実行を停止させて変数の値を見たりしたいものだ。 + +そんな夢を実現するのがデバッガーだ。この章ではデバッガーとしてGDB(GNUプロジェクトデバッガー)\index{GDB}の使い方を学ぶ。 + +GDBで快適にデバッグするには、プログラムをコンパイルするときにデバッグ情報を出力する必要がある。そのためには、GCCに\,\texttt{-g}\index{GCC@GCC(GNU Compiler Collection)!-g@\texttt{-g}}オプションを付けてプログラムをコンパイルする。 + +\begin{lstlisting}[style=terminal] +$ g++ -g -o program program.cpp +\end{lstlisting} + +本書の始めに作った入門用の\texttt{Makefile}を使う場合は、\texttt{\$gcc\_options}に\,\texttt{-g}を加えることになる。 + +\begin{lstlisting}[style=terminal] +gcc_options = -std=c++17 -Wall --pedantic-error -g +\end{lstlisting} + +コンパイラーのオプションを変更したあとは、\texttt{make clean}を実行してコンパイル済みヘッダーファイルを生成し直す必要がある。 + +\begin{lstlisting}[style=terminal] +$ make clean +\end{lstlisting} + +\hypersection{ch4201}{GDBのチュートリアル} +\index{GDB} + +では具体的にデバッガーを使ってみよう。以下のようなソースファイルを用意する。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int val = 0 ; + val = 10 ; + val += 1 ; + val *= 2 ; + val *= 2 ; + val /= 4 ; +} +\end{lstlisting} + +このプログラムをコンパイルする。 + +\begin{lstlisting}[style=terminal] +$ g++ -g program.cpp -o program +\end{lstlisting} + +GDBを使ってプログラムのデバッグを始めるには、GDBのオプションとして\,\texttt{-g}\,\index{GCC@GCC(GNU Compiler Collection)!-g@\texttt{-g}}オプション付きでコンパイルしたプログラムのファイル名を指定する。 + +\begin{lstlisting}[style=terminal] +$ gdb program +\end{lstlisting} + +すると以下のように出力される。 + +\begin{lstlisting}[style=terminal] +GNU gdb (Ubuntu 8.2-0ubuntu1) 8.2 +Copyright (C) 2018 Free Software Foundation, Inc. +License GPLv3+: GNU GPL version 3 or later +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +Type "show copying" and "show warranty" for details. +This GDB was configured as "x86_64-linux-gnu". +Type "show configuration" for configuration details. +For bug reporting instructions, please see: +. +Find the GDB manual and other documentation resources online at: + . + +For help, type "help". +Type "apropos word" to search for commands related to "word"... +Reading symbols from program...done. +(gdb) +\end{lstlisting} + +大量のメッセージに戸惑うかもしれないが、最後の行以外はGDBのライセンス表記やドキュメントだ。細部は環境ごとに異なる。 + +ここで重要なのは最後の行だ。 + +\begin{lstlisting}[style=terminal] +(gdb) +\end{lstlisting} + +ここにGDBのコマンドを入力する。ヘルプを表示するコマンド\,\texttt{help}\,\index{DGB!help\texttt{help}}と入力してみよう。 + +\begin{lstlisting}[style=terminal] +(gdb) help +\end{lstlisting} + +ヘルプメッセージが表示される。あるコマンドのヘルプを見たい場合は\,\texttt{help コマンド}\,と入力する。いまから使う予定のコマンドである\,\texttt{list}\,\index{GDB!list@\texttt{list}}のヘルプを見てみよう。 + +\begin{lstlisting}[style=terminal] +(gdb) help list +\end{lstlisting} + +\texttt{list}\,コマンドは現在のソースファイルの前後10行を表示する。 + +\begin{lstlisting}[style=terminal] +(gdb) list +1 int main() +2 { +3 int val = 0 ; +4 val = 10 ; +5 val += 1 ; +6 val *= 2 ; +7 val *= 2 ; +8 val /= 4 ; +9 } +\end{lstlisting} + +さっそく実行してみよう。実行するコマンドは\,\texttt{run}\,\index{GDB!run@\texttt{run}}だ。 + +\begin{lstlisting}[style=terminal] +(gdb) run +Starting program: 実行可能ファイルへのパス +[Inferior 1 (process PID) exited normally] +\end{lstlisting} + +\texttt{run}\,コマンドを使うとデバッガーはプログラムを実行する。 + +プログラムの実行を特定の場所で止めるには\,\texttt{break}\,\index{GDB!break@\texttt{break}}コマンドを使ってブレイクポイントを設定する。 + +\begin{lstlisting}[style=terminal] +(gdb) help break +\end{lstlisting} + +\texttt{break}\,コマンドには関数や行番号を指定できる。 + +\begin{lstlisting}[style=terminal] +(gdb) break main +(gdb) break 4 +(gdb) break 5 +\end{lstlisting} + +これで、\texttt{main}関数、4行目、5行目にブレイクポイントを設定した。さっそくもう一度最初から実行してみよう。 + +\begin{lstlisting}[style=terminal] +(gdb) run +Starting program: プログラムへのファイルパス + +Breakpoint 1, main () at main.cpp:3 +3 int val = 0 ; +\end{lstlisting} + +\texttt{main}関数にブレイクポイントを設定したので、プログラムは\texttt{main}関数が呼ばれたところ、最初のコードである3行目を実行する手前で止まる。 + +プログラムの実行を再開するには\,\texttt{continue}\,\index{GDB!continue@\texttt{continue}}コマンドを使う。 + +\begin{lstlisting}[style=terminal] +(gdb) continue +Continuing. + +Breakpoint 2, main () at main.cpp:4 +4 val = 10 ; +\end{lstlisting} + +4行目にブレイクポイントを設定したので、4行目を実行する手前で止まる。 + +この時点で、変数\texttt{val}が初期化され、その値は0になっているはずだ。確かめてみよう。変数の値を調べるには\,\texttt{print}\,\index{GDB!print@\texttt{print}}コマンドを使う。 + +\begin{lstlisting}[style=terminal] +(gdb) print val +$1 = 0 +\end{lstlisting} + +値が\texttt{0}になっていることが確認できた。実行を再開しよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=terminal] +(gdb) continue +Continuing. + +Breakpoint 3, main () at main.cpp:5 +5 val += 1 ; +\end{lstlisting} + +4行目を実行し、5行目のブレイクポイントで止まる。4行目を実行したということは、変数\,\texttt{val}\,の値は\texttt{10}になっているはずだ。もう一度\,\texttt{print}\,コマンドで調べてみよう。 + +\begin{lstlisting}[style=terminal] +(gdb) print val +$2 = 10 +\end{lstlisting} + +値は\texttt{10}だ。GDBは\texttt{print}の結果の履歴を記録している。\texttt{\$1}や\,\texttt{\$2}というのはその記録を参照するための名前だ。その値は\,\texttt{print}\,コマンドで確認できる。 + +\begin{lstlisting}[style=terminal] +(gdb) print $1 +$3 = 0 +(gdb) print $2 +$4 = 10 +\end{lstlisting} + +現在、プログラムは5行目を実行する手前で止まっている。このまま\,\texttt{continue}\,コマンドを使うとプログラムの終了まで実行されてしまう。もう一度1行だけ実行するには\,\texttt{break 6}\,で6行目にブレイクポイントを設定すればよいのだが、次の1行だけ実行したいときにいちいちブレイクポイントを設定するのは面倒だ。 + +そこで使うのが\,\texttt{step}\,だ。次の5行目を実行すると、変数\texttt{val}の値は\texttt{11}になっているはずだ。 + +\begin{lstlisting}[style=terminal] +(gdb) step +6 val *= 2 ; +(gdb) print val +$5 = 11 +\end{lstlisting} + +さて、残りの行も\texttt{step}して実行を1行ずつ確かめてみよう。 + +GDBの基本的な使い方を覚えたので、これから詳細な使い方を学んでいく。 + +\clearpage +\hypersection{ch4202}{プログラムの実行} + +GDBでプログラムをデバッグするには、GDBの起動時にプログラムのオプションとしてプログラムのファイル名を指定する。プログラムのファイル名が\,\texttt{program}\,の場合、以下のようにする。 + +\begin{lstlisting}[style=terminal] +$ ls +program +$ gdb program +\end{lstlisting} + +起動したGDBでプログラムを実行するには、\texttt{run}\,\index{GDB!run@\texttt{run}}コマンドを使う。 + +\begin{lstlisting}[style=terminal] +(gdb) run +\end{lstlisting} + +このとき、プログラムにオプションを指定したい場合は\,\texttt{run}\,に続けて記述する。例えばプログラムの標準出力を\,\texttt{out.txt}\,にリダイレクトしたいときは以下のようにする。 + +\begin{lstlisting}[style=terminal] +(gdb) run > out.txt +\end{lstlisting} + +\hypersection{ch4203}{プログラムの停止方法} + +デバッガーの機能として一番わかりやすいのが、実行中のプログラムを一時停止させる機能だ。 + +\hypersubsection{ch420301}{ブレイクポイント} +\index{ぶれいくぽいんと@ブレイクポイント} + +コマンド\,\texttt{break}\,\index{GDB!break@\texttt{break}}はブレイクポイントを設定する。プログラムの実行がブレイクポイントに達した場合、GDBはブレイクポイントの直前でプログラムの実行を中断する。 + +ブレイクポイントを設定する場所は\,\texttt{break}\,コマンドへの引数で指定する。省略して\,\texttt{b}\,\index{GDB!b@\texttt{b}}だけでもよい。 + +\begin{lstlisting}[style=terminal] +(gdb) break 場所 +(gdb) b 場所 +\end{lstlisting} + +場所として使えるのは行番号と関数名だ。 + +\hypersubsubsection{ch42030101}{行番号へのブレイクポイント} + +現在のソースファイルの123行目にブレイクポイントを設定する場合は以下のように書く。 + +\begin{lstlisting}[style=terminal] +(gdb) break 123 +\end{lstlisting} + +ソースファイルが複数ある場合は、 +\begin{lstlisting}[style=terminal] +(gdb) break ファイル名:行番号 +\end{lstlisting} +と書く。例えば\,\texttt{foo.cpp}\,の8行目にブレイクポイントを仕掛ける場合は、 +\begin{lstlisting}[style=terminal] +(gdb) break foo.cpp:8 +\end{lstlisting} +と書く。 + +\hypersubsubsection{ch42030102}{ブレイクポイントの確認} + +設定したブレイクポイントの一覧は、\texttt{info breakpoints}\,\index{GDB!info breakpoints@\texttt{info breakpoints}}コマンドで確認できる。 + +\begin{lstlisting}[style=terminal] +(gdb) break 5 +Breakpoint 1 at 0x1150: file main.cpp, line 5. +(gdb) break 6 +Breakpoint 2 at 0x1157: file main.cpp, line 6. +(gdb) break 7 +Breakpoint 3 at 0x115b: file main.cpp, line 7. +(gdb) info breakpoints +Num Type Disp Enb Address What +1 breakpoint keep y 0x0000000000001150 in main() at main.cpp:5 +2 breakpoint keep y 0x0000000000001157 in main() at main.cpp:6 +3 breakpoint keep y 0x000000000000115b in main() at main.cpp:7 +\end{lstlisting} + +これは5, 6, 7行目にそれぞれブレイクポイントを設定したあとの\,\texttt{info breakpoints}\,の結果だ。 + +この表の意味は、左から番号(Num, Number)、種類(Type)、中断後の処理(Disposition), 有効/無効(Enb, Enable/Disable)、アドレス(Address), 内容(What)となっている。 + +ブレイクポイントには作成された順番に番号が振られる。ブレイクポイントの設定を変えるには、この番号でブレイクポイントを参照する。 + +ブレイクポイントには3種類ある。普通のブレイクポイントである\texttt{breakpoint}のほかに、特殊なブレイクポイントであるウォッチポイント(watchpoint)\index{うおつちぽいんtp@ウォッチポイント}、キャッチポイント(catchpoint)\index{きやついちぽいんと@キャッチポイント}がある。 + +中断後の処理と有効/無効の切り替えはあとで説明する。 + +アドレスというのはブレイクポイントを設定した場所に該当するプログラムのコード部分であり、本書では解説しない。 + +内容はブレイクポイントを設定した場所の情報だ。 + +\hypersubsubsection{ch42030103}{ブレイクポイントの削除} + +ブレイクポイントを削除するには\,\texttt{delete}\,\index{GDB!delete@\texttt{delete}}コマンドを使う。削除するブレイクポイントは番号で指定する。 + +\begin{lstlisting}[style=terminal] +(gdb) delete 1 +\end{lstlisting} + +番号を指定しないとすべてのブレイクポイントを削除することができる。 + +\begin{lstlisting}[style=terminal] +(gdb) delete +Delete all breakpoints? (y or n) y +(gdb) info breakpoints +No breakpoints or watchpoints. +\end{lstlisting} + +\hypersubsubsection{ch42030104}{ブレイクポイントの有効/無効} + +ブレイクポイントは有効/無効を切り替えることができる。 + +ブレイクポイントを無効化するには\,\texttt{disable}\,\index{GDB!disable@\texttt{disable}}コマンドを使う。 + +\begin{lstlisting}[style=terminal] +(gdb) disable 1 +\end{lstlisting} + +ブレイクポイントを有効化するには\,\texttt{enable}\,\index{GDB!enable@\texttt{enable}}コマンドを使う。 + +\begin{lstlisting}[style=terminal] +(gdb) enable 1 +\end{lstlisting} + +ブレイクポイントは発動したあとに自動で無効化させることができる。 + +\texttt{enable [breakpoints] once}\index{GDB!enable once@\texttt{enable onece}}コマンドで、ブレイクポイントが一度発動すると自動的に無効化されるブレイクポイントを設定できる。 + +\begin{lstlisting}[style=terminal] +(gdb) enable 1 once +\end{lstlisting} + +このコマンドは、ブレイクポイント番号1が一度発動したら自動的に無効化する設定をする。 + +ブレイクポイントは\(n\)回発動したあとに自動的に無効化することもできる。そのためのコマンドは\,\texttt{enable [breakpoints] count n}\,\index{GDB!enable count@\texttt{enable count}}だ。 + +\begin{lstlisting}[style=terminal] +(gdb) enable 1 count 10 +\end{lstlisting} + +上のコマンドは、ブレイクポイント番号1が10回発動したら自動的に無効化されるよう設定している。 + +\hypersubsubsection{ch42030105}{関数名へのブレイクポイント} + +ブレイクポイントの場所として関数名を書くと、その関数名が呼び出されたあと、関数の本体の1行目が実行されるところにブレイクポイントが設定される。 + +現在のソースファイルの関数\texttt{main}にブレイクポイントを設定する場合は以下のように書く。 + +\begin{lstlisting}[style=terminal] +(gdb) break main +\end{lstlisting} + +ソースファイルが複数ある場合は、 +\begin{lstlisting}[style=terminal] +(gdb) ファイル名:関数名 +\end{lstlisting} +と書く。 + +C++では異なる引数で同じ名前の関数が使える。 + +\begin{lstlisting}[language={C++}] +void f() { } +void f(int) { } +void f(double) { } + +int main() +{ + f() ; + f(0) ; + f(0.0) ; +} +\end{lstlisting} + +このようなプログラムで関数\texttt{f}にブレイクポイントを設定すると、\texttt{f}という名前の関数すべてにブレイクポイントが設定される。 + +ブレイクポイントの一覧を表示する\,\texttt{info breakpoints}\,\index{GDB!info breakpoints@\texttt{info breakpoints}}コマンドで確かめてみよう。 + +\begin{lstlisting}[style=terminal] +(gdb) break f +Breakpoint 1 at 0x1149: f. (3 locations) +(gdb) info breakpoints +Num Type Disp Enb Address What +1 breakpoint keep y +1.1 y 0x0000000000001149 in f() at main.cpp:1 +1.2 y 0x0000000000001153 in f(int) at main.cpp:2 +1.3 y 0x000000000000115f in f(double) at main.cpp:3 +\end{lstlisting} + +関数名\texttt{f}に該当するすべての関数に、ブレイクポイント番号1としてブレイクポイントが設定される。関数にはそれぞれサブの番号が振られる。 + +この状態でブレイクポイント番号1を削除すると、1.1, 1.2, 1.3はすべて削除される。 + +\begin{lstlisting}[style=terminal] +(gdb) delete 1 +(gdb) info breakpoints +No breakpoints or watchpoints. +\end{lstlisting} + +もし、オーバーロードされた同名の関数のうちの一部だけにブレイクポイントを仕掛けたい場合、曖昧性を解決するメニューを表示する設定にすることで、一部の関数だけを選ぶことができる。メニューを表示する設定にするには、 +\begin{lstlisting}[style=terminal] +(gdb) set multiple-symbols ask +\end{lstlisting} +というコマンドを使う。これ以降の\texttt{break}コマンドが名前が曖昧であることを検出した場合、以下のようなメニューを表示する。 + +\begin{lstlisting}[style=terminal] +(gdb) break f +[0] cancel +[1] all +[2] run.cpp:f() +[3] run.cpp:f(double) +[4] run.cpp:f(int) +> +\end{lstlisting} + +ここで\texttt{0}を入力するとキャンセル。\texttt{1}を入力するとすべての関数にブレイクポイントを設定する。 + +特定の関数だけにブレイクポイントを設定したい場合、その関数に対応する番号を入力する。例えば、\texttt{f()}と\texttt{f(int)}だけにブレイクポイントを設定したい場合は、 +\begin{lstlisting}[style=terminal] +> 2 4 +\end{lstlisting} +と入力する。 + +\hypersubsection{ch420302}{条件付きブレイクポイント} + +\begin{lstlisting}[style=terminal] +(gdb) break ... if 条件 +\end{lstlisting} +と入力すると、\texttt{条件}が\texttt{true}となるときのみブレイクポイントが発動する。 +\index{GDB!break if@\texttt{break if}} + +例えば以下のようなコードで、 +\begin{lstlisting}[language={C++}] +int main() +{ + int i { } ; + while ( i != 1000 ) + { + ++i ; + std::cout << i ; + } +} +\end{lstlisting} +以下のように7行目に変数\texttt{i}が\texttt{500}に等しい条件を設定すると +\begin{lstlisting}[style=terminal] +(gdb) break 7 if i == 500 +\end{lstlisting} +変数\texttt{i}が\texttt{500}でありかつ7行目が実行される直前でブレイクポイントが発動する。 + +\hypersection{ch4204}{プログラムの実行再開とステップ実行} + +以下のようなプログラムがあるとする。 + +\begin{lstlisting}[language={C++}] +int f( int x ) +{ + return x + 1 ; +} + +int main() +{ + int i = 0 ; + i = f(i) ; + i = f(i) ; + i = f(i) ; +} +\end{lstlisting} + +このプログラムを\texttt{main}関数から1行ずつ実行してその挙動を確かめたい。その場合に、すべての行にブレイクポイントを設定するのは面倒だ。GDBではこのような場合に、現在中断している場所から1行だけ実行する方法がある。 + +\hypersubsection{ch420401}{実行再開(continue)} +\index{GDB!continue@\texttt{continue}} + +\texttt{continue}コマンドは実行を再開する。省略して\texttt{c}\index{GDB!c@\texttt{c}}でもよい + +\begin{lstlisting}[style=terminal] +(gdb) continue +(gdb) c +\end{lstlisting} + +実行を再開すると、次のブレイクポイントが発動するか、プログラムが終了するまで実行が続く。 + +\hypersubsection{ch420402}{ステップ実行(step)} +\index{GDB!step@\texttt{step}} + +\texttt{step}コマンドは現在実行が中断している場所から、ソースファイルで1行分の実行をして中断する。 + +\begin{lstlisting}[style=terminal] +(gdb) step +(gdb) s +\end{lstlisting} + +\texttt{step}コマンドは省略して\texttt{s}\index{GDB!s@\texttt{s}}でもよい。 + +先ほどのソースファイルで、まず\texttt{main}関数にブレイクポイントを設定して実行すると、 +\begin{lstlisting}[style=terminal] +(gdb) break main +(gdb) run +\end{lstlisting} +\texttt{main}関数に入った直後で実行が中断する。 + +\begin{lstlisting}[style=terminal] +int main() +{ +>> int i = 0 ; + i = f(i) ; + i = f(i) ; +... +\end{lstlisting} + +この状態で\texttt{step}コマンドを使うと +\begin{lstlisting}[style=terminal] +(gdb) step +\end{lstlisting} +1行分にあたる実行が行われ、また中断される。 + +\begin{lstlisting}[style=terminal] +int main() +{ + int i = 0 ; +>> i = f(i) ; + i = f(i) ; +... +\end{lstlisting} + +もう一度\texttt{step}コマンドを使うと、今度は関数\texttt{f}の中で実行が中断する。 + +\begin{lstlisting}[style=terminal] +int f( int x ) +{ +>> return x + 1 ; +} + +int main() +... +\end{lstlisting} + +このまま\texttt{step}コマンドを続けていくと、また\texttt{main}関数に戻り、また次の行が実行され、また関数\texttt{f}が実行される。 + +1行ずつ実行するのではなく\(n\)行実行したい場合は、\texttt{step}コマンドに\(n\)を指定する。 + +\begin{lstlisting}[style=terminal] +(gdb) step n +\end{lstlisting} + +するとソースファイルの\(n\)行分実行される。例えば以下のように書くと、 +\begin{lstlisting}[style=terminal] +(gdb) step 3 +\end{lstlisting} +3行分実行される。 + +\hypersubsection{ch420403}{ネクスト実行(next)} +\index{GDB!next@\texttt{next}} + +\texttt{step}コマンドはソースファイルの1行分を実行してくれるが、途中に関数呼び出しが入る場合、その関数のソースファイルがある場合はその関数の中も1行とカウントする。\texttt{next}コマンドは現在実行が中断しているソースファイルの次の行を1行として扱い、次の行まで実行して中断する。 + +例えばプログラムが以下の状態で中断しているとする。 + +\ifTombow\enlargethispage{3mm}\fi +\begin{lstlisting}[style=terminal] +int main() +{ + int i = 0 ; +>> i = f(i) ; + i = f(i) ; +... +\end{lstlisting} + +このまま\texttt{step}コマンドを実行すると、関数\texttt{f}の中の1行で実行が中断する。一方\texttt{next}コマンドを使うと、 +\begin{lstlisting}[style=terminal] +(gdb) next +\end{lstlisting} +現在止まっているソースファイルの次の1行の手前まで実行して中断する。途中の関数呼び出しはすべて実行される。 + +\begin{lstlisting}[style=terminal] +int main() +{ + int i = 0 ; + i = f(i) ; +>> i = f(i) ; +... +\end{lstlisting} + +\texttt{step}コマンドと同じく、\texttt{next}コマンドも\(n\)行分一度に実行することができる。 + +\begin{lstlisting}[style=terminal] +(gdb) next n +\end{lstlisting} + +\hypersubsection{ch420404}{関数から抜けるまで実行(finish)} +\index{GDB!finish@\texttt{finish}} + +\texttt{finish}コマンドは現在の関数から\texttt{return}するまで実行する。 + +\clearpage +\hypersection{ch4205}{バックトレース} + +バックトレースは中断しているプログラムの情報を得るとても強力なコマンドだ。 + +\begin{lstlisting}[style=terminal] +(gdb) backtrace +(gdb) bt +\end{lstlisting} + +バックトレースを表示するには\texttt{backtrace}\index{GDB!backtrace@\texttt{backtrace}}もしくは\texttt{bt}\index{GDB!bt@\texttt{bt}}というコマンドを使う。 + +例えば以下のようなソースファイルがある。 + +\begin{lstlisting}[language={C++}] +void f() { } + +void apple() { f() ; } +void banana() { f() ; } +void cherry() { apple() ; } + +int main() +{ + f() ; + apple() ; + banana() ; + cherry() ; +} +\end{lstlisting} + +ここで関数\texttt{f}に注目してみよう。関数\texttt{f}はさまざまな関数から呼ばれる。関数\texttt{main}から呼ばれるし、関数\texttt{apple}や\texttt{banana}からも呼ばれる。特に、関数\texttt{cherry}は関数\texttt{apple}を呼び出すので、間接的に関数\texttt{f}を呼ぶ。 + +関数\texttt{f}にブレイクポイントを仕掛けて実行してみよう。 + +\begin{lstlisting}[style=terminal] +(gdb) break f +(gdb) run +(gdb) continue +(gdb) continue +(gdb) continue +(gdb) continue +\end{lstlisting} + +関数\texttt{f}が呼ばれるたびに実行が中断するが、関数\texttt{f}がどこから呼ばれたのかがわからない。 + +こういうときにバックトレースが役に立つ。 + +上のコマンドを実行しながら、関数\texttt{f}のブレイクポイントが発動するたびに、\texttt{backtrace}コマンドを入力してみよう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[style=terminal] +(gdb) break f +(gdb) run +(gdb) backtrace +#0 f () at main.cpp:2 +#1 0x0000555555556310 in main () at main.cpp:10 +\end{lstlisting} + +\texttt{\#0}がバックトレースの最も深い現在のスタックフレームだ。これは関数\texttt{f}でソースファイル\texttt{main.cpp}の2行目だ。\texttt{\#1}が\,\texttt{\#0}の上のスタックフレームで、これは関数\texttt{main}で10行目にある。 + +実行を再開して、次の関数\texttt{f}のバックトレースを見よう。 + +\begin{lstlisting}[style=terminal] +(gdb) continue +(gdb) backtrace +#0 f () at main.cpp:2 +#1 0x00005555555562ec in apple () at main.cpp:4 +#2 0x0000555555556315 in main () at main.cpp:11 +\end{lstlisting} + +今回はスタックフレームが3つある。最も外側のスタックフレームは関数\texttt{main}で、そこから関数\texttt{apple}が呼び出され、そして関数\texttt{f}が呼び出される。 + +さらに進めよう。 + +\begin{lstlisting}[style=terminal] +(gdb) continue +(gdb) backtrace +#0 f () at main.cpp:2 +#1 0x00005555555562f8 in banana () at main.cpp:5 +#2 0x000055555555631a in main () at main.cpp:12 +\end{lstlisting} + +今度は\texttt{main}→\texttt{banana}→\texttt{f}になった。次はどうだろうか。 + +\begin{lstlisting}[style=terminal] +(gdb) continue +(gdb) backtrace +#0 f () at main.cpp:2 +#1 0x00005555555562ec in apple () at main.cpp:4 +#2 0x0000555555556304 in cherry () at main.cpp:6 +#3 0x000055555555631f in main () at main.cpp:13 +\end{lstlisting} + +最後は\texttt{main}→\texttt{cherry}→\texttt{apple}→\texttt{f}だ。 + +このようにバックトレースを使うことでプログラムの状態を調べることができる。 + +\clearpage +\hypersection{ch4206}{変数の値を確認} + +変数の値を確認するには\texttt{print}\index{GDB!print@\texttt{print}}コマンドを使う。 + +\begin{lstlisting}[style=terminal] +(gdb) print 式 +\end{lstlisting} + +\texttt{print}コマンドは式を評価した結果を出力する。 + +例えば以下のようなソースファイルがある。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x = 1 ; + x += 1 ; + x *= 2 ; +} +\end{lstlisting} + +この変数\texttt{x}の値を見ていこう。 + +まず変数\texttt{x}が初期化されるところまで実行する。 + +\begin{lstlisting}[style=terminal] +(gdb) break main +(gdb) run +(gdb) step +(gdb) print x +$1 = 1 +\end{lstlisting} + +1行ずつ実行して値を見ていこう。 + +\begin{lstlisting}[style=terminal] +(gdb) step +(gdb) print x +$2 = 2 +(gdb) step +(gdb) print x +$3 = 4 +\end{lstlisting} + +\texttt{print 式}コマンドで注意すべき点としては、\texttt{式}の副作用もプログラムに反映されるということだ。例えば以下のように変数\texttt{x}を変更する式も使えるし、変数\texttt{x}は実際に変更されてしまう。 + +\begin{lstlisting}[style=terminal] +(gdb) print ++x +(gdb) print x = 0 +\end{lstlisting} + +式では関数まで呼べてしまう。 + +\ifTombow\pagebreak\fi +\begin{lstlisting}[language={C++}] +void hello() +{ + std::cout << "hello" +} + +int main() { } +\end{lstlisting} + +このプログラムは関数\texttt{hello}を呼ばないし標準出力には何も出力しない。しかしこのプログラムをGDBでロードし、\texttt{main}関数にブレイクポイントを設定してから実行し、ブレイクポイントが発動したら\texttt{print hello()}コマンドを使ってみると、 + +\begin{lstlisting}[style=terminal] +(gdb) break main +(gdb) run +(gdb) print hello() +\end{lstlisting} + +なんと関数\texttt{hello}が呼び出され、標準出力に\,\texttt{hello}\,と出力されるではないか。 + +\texttt{print}コマンドの式のもたらす副作用には注意しよう。 + +\hypersection{ch4207}{シグナルによるプログラムの中断} +\index{しぐなる@シグナル}\index{GDB!しぐなる@シグナル} + +プログラムはさまざまな理由によりシグナルを出して実行を強制的に終了する。このシグナルはGDBによってトラップされ、ブレイクポイントと同じくプログラムの中断として扱われる。 + +プログラムが実行を終了するようなシグナルは、プログラムの不具合によって生じる。具体的な不具合は実行環境に依存するが、たいていの環境で動く不具合は、nullポインターを経由した間接アクセスと、ゼロ除算だ。 + +\begin{lstlisting}[language={C++}] +// nullポインターを経由した間接アクセス +int * ptr = nullptr ; +*ptr = 0 ; +// ゼロ除算 +1 / 0 ; +\end{lstlisting} + +実際にそのようなプログラムを作ってGDBで実行し、プログラムが中断されることを確認してみよう。 + +\begin{lstlisting}[language={C++}] +int main() +{ + int x { } ; + std::cin >> x ; + std::cout << 1 / x ; +} +\end{lstlisting} + +このプログラムはユーザーが標準入力から\texttt{0}を入力するとゼロ除算となり強制的に終了する。GDBで実行してみよう。 + +\begin{lstlisting}[style=terminal] +$ gdb program +(gdb) run +Starting program: +0 + +Program received signal SIGFPE, Arithmetic exception. +0x0000555555556336 in main () at main.cpp:5 +5 std::cout << 1 / x ; +\end{lstlisting} + +ちょうどゼロ除算を起こした箇所でプログラムの実行が中断する。 + +このとき中断した状態でプログラムのさまざまな状態を観測できる。例えばバックトレースを表示したり、変数の値を確認したりできる。 + +\hypersection{ch4208}{コアダンプを使ったプログラムの状態の確認} +\index{こあだんぷ@コアダンプ} + +プログラムがシグナルによって強制的に終了したときに、たまたまデバッガーで動かしていたならばプログラムの状態を調べられる。しかし都合よくデバッガーで実行していない場合はどうすればいいのか。 + +まずプログラムを普通に実行してみよう。 + +\begin{lstlisting}[style=terminal] +$ program +0 +Floating point exception (core dumped) +\end{lstlisting} + +\texttt{core dumped}という文字が気になる。プログラムはシグナルで強制的に実行を終了するときコアファイルをダンプする。このファイル名は通常\texttt{core}だ。通常はカレントディレクトリーに\texttt{core}\index{core@\texttt{core}}という名前のファイルが生成されているはずだ。 + +もしカレントディレクトリーに\texttt{core}という名前のファイルがない場合、以下のコマンドを実行する。 + +\begin{lstlisting}[style=terminal] +$ ulimit -c unlimited +\end{lstlisting} + +これにより\texttt{core}ファイルが生成されるようになる。 + +すでにコアファイルが存在する場合に上書きされるかどうかは環境により異なる。昔のコアファイルがいらないのであれば消しておこう。 + +\begin{lstlisting}[style=terminal] +$ rm ./core +$ ./program +0 +Floating point exception (core dumped) +$ find core +core +\end{lstlisting} + +このコアファイルはデバッガーに読み込ませることで、プログラムが強制的に終了するに至った瞬間のプログラムの状態を調べるのに使える。 + +使い方はGDBにプログラムファイルと一緒に指定するだけだ。 + +\begin{lstlisting}[style=terminal] +$ gdb program core +... +Core was generated by `./program'. +Program terminated with signal SIGFPE, Arithmetic exception. +#0 0x000055dcbfd3d336 in main () at main.cpp:5 +5 std::cout << 1 / x ; +(gdb) backtrace +#0 0x000055dcbfd3d336 in main () at main.cpp:5 +(gdb) print x +$1 = 0 +\end{lstlisting} + +デバッガーはとても役に立つ。本書では少しだけしか解説しなかったが、このほかにも強力な機能がたくさんある。 diff --git a/TeX/about-author.tex b/TeX/about-author.tex new file mode 100644 index 0000000..ed55aa2 --- /dev/null +++ b/TeX/about-author.tex @@ -0,0 +1,19 @@ +\thispagestyle{empty} + +\hypersubsubsection{about-the-author}{著者プロフィール} +\addcontentsline{toc}{chapter}{著者プロフィール} + +\vskip 1.0zw + +\noindent{}{\normalsize {\sffamily\mdseries 江添 亮}(えぞえ りょう)} \vspace{-2mm} + +\begin{tabbing} +123456789\=01234567890\kill +Mail \>: boostcpp@gmail.com\\ +Blog \>: \url{http://cpplover.blogspot.jp/}\\ +GitHub \>: \url{https://github.com/EzoeRyou/}\\ +2010年\ C++標準化委員会 エキスパートメンバー\\ +2014年\ 株式会社ドワンゴ入社\\ +2015年\ C++標準化委員会 委員\\ +\end{tabbing} + diff --git a/TeX/asciibook.cls b/TeX/asciibook.cls new file mode 100755 index 0000000..ef280db --- /dev/null +++ b/TeX/asciibook.cls @@ -0,0 +1,289 @@ +\ProvidesClass{asciibook}[2015/01/30] +\LoadClassWithOptions{jsbook} + +\iftombow +% B5変形判 234 × 182 mm +\setlength\paperheight{234truemm} +\setlength\paperwidth{182truemm} +% 1行 46文字 +\setlength{\textwidth}{46zw} +% 1ページ 38行 +\setlength{\textheight}{38\baselineskip} +% 版面のアキ 小口から20ミリ +\setlength{\evensidemargin}{-3truemm} +\setlength{\oddsidemargin}{0truemm} +% ヘッダの空き調整 +\addtolength{\topmargin}{-55pt} +\addtolength{\headsep}{10pt} +% フッタの空き調整 +\setlength\footskip{2.5\baselineskip} +\addtolength{\textheight}{\baselineskip} +\else +% A4判 364 × 257 mm +\setlength\paperheight{364mm} +\setlength\paperwidth{257mm} +% 1行 46文字 +\setlength{\textwidth}{46zw} +% 1ページ 45行 +\setlength{\textheight}{45\baselineskip} +% 版面のアキ 左右中央 +\setlength{\evensidemargin}{10.8truemm} +\setlength{\oddsidemargin}{10.8truemm} +% ヘッダの空き調整 +\addtolength{\topmargin}{-55pt} +\addtolength{\headsep}{10pt} +% フッタの空き調整 +\setlength\footskip{2.5\baselineskip} +\addtolength{\textheight}{\baselineskip} +\fi +% +% 印刷位置の調整 +\advance\hoffset 0.0in +\advance\voffset 0.0in +% +% 見出しフォントを太ゴシック(新ゴ)にする +\renewcommand{\headfont}{\sffamily\bfseries\ebseries} +% +% \bfseries, \textbfのデフォルトをb(Bold)に再定義する +\renewcommand{\bfdefault}{b} +% +% 本文のヘッダ、フッタ +\def\ps@asciiheadings{% + \def\@evenhead{% + {\hskip 0truemm} + \textcolor{black}{ + \underline{\hbox to 136truemm{\autoxspacing + \small \textcolor{black}{\thepage}\ \ \ \ \leftmark \hfil} + }% + } + }% + \def\@oddhead{% + {\hskip 0truemm} + \textcolor{black}{ + \underline{\hbox to 136truemm{\autoxspacing + \small \hfil \rightmark\ \ \ \ \textcolor{black}{\thepage}} + }% + } + }% +} +% 前付けのヘッダ、フッタ +\def\ps@frontheadings{% + \def\@evenhead{}% + \def\@oddhead{}% + \def\@oddfoot{\hfil \thepage}% + \def\@evenfoot{\thepage \hfil}% +} +% 章トビラのヘッダ、フッタの指定 +\def\ps@chapterheadings{% + \def\@evenhead{}% + \def\@oddhead{}% + \def\@oddfoot{}% + \def\@evenfoot{}% +} +% 索引のスタイル +\makeatletter +\def\ps@indexfoot{% + \def\@oddfoot{% + {\hbox to \fullwidth{\hfil \thepage}}}% + \let\@oddhead\@empty + \def\@evenfoot{% + \hss {\hbox to \fullwidth{\thepage \hfil}}}% + \let\@evenhead\@empty} +\makeatother +% +% 見出しのレイアウト +% +% 章 +\makeatletter +\renewcommand{\chapter}{% + % 章扉は改丁 + \cleardoublepage + \plainifnotempty % 元: \thispagestyle{plain} + \global\@topnum\z@ + \if@english \@afterindentfalse \else \@afterindenttrue \fi + \secdef + {\@omit@numberfalse\@chapter}% + {\@omit@numbertrue\@schapter}} +\def\@makechapterhead#1{% + \vbox to 88mm{ % 小見出しの天地:88ミリ + \vspace*{20mm} % 章番号の前は45mmあき + { + \parindent \z@ % 段落の字下げをゼロにする + \raggedright % 見出しの位置を決める: 左揃え + \normalfont % フォント設定をリセット + % 章番号 + \ifnum \c@secnumdepth >\m@ne + \if@mainmatter + {\hskip 6.0mm}\Large\headfont \@chapapp\thechapter\@chappos + \par\nobreak + \vskip 5mm % 章番号の後ろは5mmあき + \fi + \fi + \interlinepenalty\@M % ペナルティの設定 + % 章見出し +% {\hskip 6.0mm}\huge \headfont #1\par\nobreak + \hbox{{\hskip 6.0mm}\vbox{\huge \headfont #1\vss}}\par\nobreak + \vskip 3\Cvs % 見出しの後ろは3行あき + } + \vss + } +} +\def\@makeschapterhead#1{% + %\vspace*{77mm} % 章番号の前は85mmあき + { + \parindent \z@ % 段落の字下げをゼロにする + \raggedright % 見出しの位置を決める: 左揃え + \normalfont % フォント設定をリセット + \interlinepenalty\@M % ペナルティの設定 + % 章見出し + \huge \headfont #1\par\nobreak + \vskip 3\Cvs}} % 見出しの後ろは3行あき +\makeatother +% +% 節 +\makeatletter +\renewcommand{\section}{% + \if@slide\clearpage\fi + \@startsection{section}{1}{\z@}% + {\Cvs \@plus.5\Cdp \@minus.2\Cdp}% 前アキ + {.5\Cvs \@plus.3\Cdp}% 後アキ + {\normalfont\Large\headfont\raggedright}} +\makeatother +% +% 小節 +\makeatletter +\renewcommand{\subsection}{\@startsection{subsection}{2}{\z@}% + {\Cvs \@plus.5\Cdp \@minus.2\Cdp}% 前アキ + {.2\Cvs \@plus.3\Cdp}% 後アキ + {\normalfont\large\headfont}} +\makeatother +% +% 小小節 +\makeatletter +\renewcommand{\subsubsection}{\@startsection{subsubsection}{3}{\z@}% + {\Cvs \@plus.5\Cdp \@minus.2\Cdp}% + {\if@slide .5\Cvs \@plus.3\Cdp \else \z@ \fi}% + {\normalfont\normalsize\headfont}} +\makeatother +% +% パラグラフ +\makeatletter +\newcommand{\jsParagraphMark}{■} +\renewcommand{\paragraph}{\@startsection{paragraph}{4}{\z@}% + {0.5\Cvs \@plus.5\Cdp \@minus.2\Cdp}% + %{\if@slide .5\Cvs \@plus.3\Cdp \else -1zw\fi}% 改行せず 1zw のアキ + {\if@slide .5\Cvs \@plus.3\Cdp \else \z@ \fi}% + %{\normalfont\normalsize\headfont\jsParagraphMark}} + {\normalfont\normalsize\sffamily\bfseries}} +\makeatother +% +% ハイパーリンク付きの見出し +% +% 部 +\newcommand{\hyperpart}[2]{ + \hyperdef{}{#1}{ + \part{\texorpdfstring{\hyperref[#1]{#2}}{#2} + }\label{#1} + } +% \thispagestyle{chapterheadings} +} +% +% 章 +\newcommand{\hyperchapter}[3]{ + \hyperdef{}{#1}{ + \chapter[#3]{\texorpdfstring{\hyperref[#1]{#2}}{#2} + }\label{#1} + } + \thispagestyle{chapterheadings} +} +% +% 節 +\newcommand{\hypersection}[2]{ + \hyperdef{}{#1}{ + \section{\texorpdfstring{\hyperref[#1]{#2}}{#2} + }\label{#1} + } +} +\newcommand{\hypersectionn}[2]{ + \hyperdef{}{#1}{ + \section*{\texorpdfstring{\hyperref[#1]{#2}}{#2} + }\label{#1} + } +} +% +% 小節 +\newcommand{\hypersubsection}[2]{ + \hyperdef{}{#1}{ + \subsection{\texorpdfstring{\hyperref[#1]{#2}}{#2} + }\label{#1} + } +} +% +% 小々節 +\newcommand{\hypersubsubsection}[2]{ + \hyperdef{}{#1}{ + \subsubsection{\texorpdfstring{\hyperref[#1]{#2}}{#2} + }\label{#1} + } +} +\newcommand{\hypersubsubsectionn}[2]{ + \hyperdef{}{#1}{ + \subsubsection*{\texorpdfstring{\hyperref[#1]{#2}}{#2} + }\label{#1} + } +} +% +% パラグラフ +\newcommand{\hyperparagraph}[2]{ + \hyperdef{}{#1}{ + \paragraph{\texorpdfstring{\hyperref[#1]{#2}}{#2} + }\label{#1} + } +} +% +% 目次のスタイルを少し変更  +\makeatletter +\newdimen\jsc@mpt +\newdimen\jsc@tocl@width +\renewcommand{\tableofcontents}{% + \settowidth\jsc@tocl@width{\headfont\prechaptername\postchaptername}% + \settowidth\@tempdima{\headfont\appendixname}% + \ifdim\jsc@tocl@width<\@tempdima \setlength\jsc@tocl@width{\@tempdima}\fi + \ifdim\jsc@tocl@width<2zw \divide\jsc@tocl@width by 2 \advance\jsc@tocl@width 1zw\fi + \if@twocolumn + \@restonecoltrue\onecolumn + \else + \@restonecolfalse + \fi + %\chapter*{\contentsname}% + \section*{\contentsname}% + \@mkboth{\contentsname}{}% + \@starttoc{toc}% + \if@restonecol\twocolumn\fi +} +\renewcommand*{\l@chapter}[2]{% + \ifnum \c@tocdepth >\m@ne + \addpenalty{-\@highpenalty}% + \addvspace{1.0em \@plus\jsc@mpt} + \begingroup + \parindent\z@ + \rightskip\@tocrmarg + \parfillskip-\rightskip + \leavevmode\headfont + % \if@english\setlength\@lnumwidth{5.5em}\else\setlength\@lnumwidth{4.683zw}\fi + \setlength\@lnumwidth{\jsc@tocl@width}\advance\@lnumwidth 2.683zw + \advance\leftskip\@lnumwidth \hskip-\leftskip + #1\nobreak\hfil\nobreak\hbox to\@pnumwidth{\hss#2}\par + \penalty\@highpenalty + \endgroup + \fi} +\renewcommand*{\l@section}{% + \@tempdima\jsc@tocl@width \advance\@tempdima -1zw + %\@dottedtocline{1}{\@tempdima}{3.683zw}} + \@dottedtocline{1}{\@tempdima}{3zw}} +\renewcommand*{\l@subsection}{% + %\@tempdima\jsc@tocl@width \advance\@tempdima 2.683zw + \@tempdima\jsc@tocl@width \advance\@tempdima 1zw + \@dottedtocline{2}{\@tempdima}{3.5zw}} +\makeatother + diff --git a/TeX/cpp-intro.tex b/TeX/cpp-intro.tex new file mode 100644 index 0000000..c67a01e --- /dev/null +++ b/TeX/cpp-intro.tex @@ -0,0 +1,249 @@ +% +% 江添亮のC++入門 +% +% 備考: +% RyuminPro-Light, Gothic-BBBでは出力できない漢字? +% 隙(CID20273)--> \CID{1850} : +% 030-pointer-details + +% +% +\newif\ifColor +\Colortrue +%\Colorfalse +\newif\ifTombow +\Tombowtrue +%\Tombowfalse +% +\ifTombow +% トンボあり +\documentclass[a4paper,12Q,papersize,uplatex,b5var,openany,tombow]{asciibook} +%\documentclass[a4paper,12Q,papersize,uplatex,b5var,openany]{asciibook_pdf} +\else +% トンボなし +\documentclass[a4paper,12Q,papersize,uplatex,openany]{asciibook} +\fi +% +\usepackage[uplatex,expert,deluxe,jis2004]{otf} % OpenTypeフォントを利用 +\usepackage{lmodern} % Latin Modernフォントを利用 +\usepackage[T1]{fontenc} % フォントエンコーディングをT1に変更 +\usepackage{textcomp} % テキストシンボルの利用 +\usepackage[utf8]{inputenc} % UTF-8で欧文文字を入力 +\usepackage{setspace} % 行間の調整 +\usepackage{layout} % レイアウトの詳細 +\usepackage{comment} % comment関連コマンド +\usepackage{okumacro} % 奥村先生のおまけパッケージ +\usepackage{bm} % ベクトルをボールドで表示 +\usepackage{wrapfig} % 文字の回り込みをする図の配置 +\usepackage{longtable} % Longtable +\usepackage{tabularx} % 表組みの横幅指定、セルの書式指定 +\usepackage{ltablex} % テーブルのネスト +\usepackage{colortbl} % 表組みの色指定 +\usepackage{scrextend} % 脚注のカスタマイズ +\usepackage[dvipdfmx]{hyperref,graphicx} % PDFにハイパーリンクの機能を加える +\usepackage{pxjahyper} % PDFの日本語のしおりに対応 +\hypersetup{% + bookmarksnumbered=true,% + linktocpage=true,% + linktoc=all,% + colorlinks=true,% + %colorlinks=false,% % 印刷所渡し + linkcolor=BrickRed,% + citecolor=OliveGreen,% + urlcolor=NavyBlue,% + setpagesize=false,% + pdfkeywords={TeX; dvipdfmx; hyperref; xcolor;}} +% +\usepackage[dvipsnames]{xcolor} % 色の指定 +\usepackage{amssymb, amsmath} % 数式関連 +% +% キャプション +\usepackage{caption} +\captionsetup{ + singlelinecheck=off, % 左寄せ + labelsep=colon, % ラベルのセパレータ + labelfont={sf, small}, % ラベルのフォント + font={sf, small}, % キャプションのフォント + format=hang, % 折り返し +} +\renewcommand{\figurename}{図} % 図のキャプションのタイトル +\renewcommand{\tablename}{表} % 表のキャプションのタイトル +\makeatletter +\renewcommand \thefigure + {\ifnum \c@chapter>\z@ \thechapter.\fi \@arabic\c@figure} +\renewcommand \thetable + {\ifnum \c@chapter>\z@ \thechapter.\fi \@arabic\c@table} +\makeatother +% +% プログラムリスト +\usepackage{listings,jlisting} +% terminal +\lstdefinestyle{terminal} {% + %language=bash,% + language=,% + basicstyle={\small\ttfamily},% + identifierstyle={\small},% + frame={single}, + frameround={tttt}, + xleftmargin=2zw,% + framexleftmargin=2zw, +} +% grammar +\lstdefinestyle{grammar} {% + language=C++,% + basicstyle={\small\ttfamily},% + identifierstyle={\small},% + frame={single}, + xleftmargin=2zw,% + framexleftmargin=2zw, +} +% JavaScript +\lstdefinelanguage{JavaScript}{ + keywords={typeof, new, true, false, catch, function, return, null, catch, switch, var, if, in, while, do, else, case, break}, + ndkeywords={class, export, boolean, throw, implements, import, this}, + comment=[l]{//}, + morecomment=[s]{/*}{*/}, +} +% common +\lstset{% + language={C++}, + basicstyle={\small\ttfamily},% + identifierstyle={\small},% + frame={tb},%frame={none}, +% frameround={tttt}, + breaklines=true, + columns=[l]{fullflexible},% + xrightmargin=0zw,% + xleftmargin=2zw,% + framexleftmargin=2zw, +% numberstyle={\scriptsize},% +% stepnumber=1, + aboveskip=1.5zw, + belowskip=1.5zw, + showstringspaces=false, + keepspaces=true, + upquote=true, + breakindent={0pt}, + escapeinside={(@}{@)} +} +% color +\ifColor +\lstset{% + commentstyle={\small\bfseries \color[rgb]{0.533,0,0}},% + keywordstyle={\small\bfseries \color[rgb]{0,0,0.533}},% + ndkeywordstyle={\small\bfseries \color[rgb]{0,0,0.533}},% + stringstyle={\small\ttfamily \color[rgb]{0,0.533,0}}, +} +\else +\lstset{% + commentstyle={\small\bfseries},% + keywordstyle={\small\bfseries},% + ndkeywordstyle={\small\bfseries},% + stringstyle={\small\ttfamily}, +} +\fi +% +% \textttのクォーテーションタイプを変更する +\usepackage{expl3} +\usepackage{xparse} +\ExplSyntaxOn +\RenewDocumentCommand{\texttt}{m} + { + \tl_set:Nn \l_nemgathos_upquotes_tl { #1 } + \tl_replace_all:Nnn \l_nemgathos_upquotes_tl { '' } { \textquotedbl } + \tl_replace_all:Nnn \l_nemgathos_upquotes_tl { `` } { \textquotedbl } + \tl_replace_all:Nnn \l_nemgathos_upquotes_tl { ' } { \textquotesingle } + \tl_replace_all:Nnn \l_nemgathos_upquotes_tl { ` } { \textquotesingle } + { \ttfamily \tl_use:N \l_nemgathos_upquotes_tl } + } +\tl_new:N \l_nemgathos_upquotes_tl +\ExplSyntaxOff +% +% \emphの再定義 +\renewcommand{\emph}[1]{\textsf{\textgt{#1}}} +% +% ハイフネーション +%\hyphenation{} +% +% ルビ +\usepackage{pxrubrica} +% +% 索引 +\usepackage{makeidx} +\makeindex +% +% ヘッダー +\pagestyle{asciiheadings} +% +% ドキュメント +\begin{document} +%\layout +% +\frontmatter +\include{front} +%\include{front_pdf} +\input{000-preface} +\clearpage +{\thispagestyle{empty} +\setcounter{tocdepth}{2} +\tableofcontents} +% +\mainmatter +\input{001-intro} +\input{002-build} +\input{003-guide-to-c++} +\input{004-debug-compile-error} +\input{005-the-restaurant-at-the-end-of-the-branch} +\input{006-debug-compile-warning} +\input{007-standard-input} +\input{008-loop} +\input{009-vector} +\input{010-debug-printf} +\input{011-integer} +\input{012-floating-point} +\input{013-names} +\input{014-iterator} +\input{015-reference} +\input{016-algorithm} +\input{017-lambda} +\input{018-class} +\input{019-operator-overloading} +\input{020-array} +\input{021-three-virtues-of-a-programmer} +\input{022-implement-array} +\input{023-template} +\input{024-more-array} +\input{025-array-iterator} +\input{026-exception} +\input{027-pointer} +\input{028-pointer-semantics} +\input{029-pointer-syntax} +\input{030-pointer-details} +\input{031-iterator-operations} +\input{032-memory-allocation} +\input{033-vector-implementation} +\input{034-vector-memory-allocation} +\input{035-copy} +\input{036-move} +\input{037-rvalue-reference} +\input{038-move-semantics} +\input{039-disable-copy} +\input{040-smart-pointer} +\input{041-move-support} +\input{042-string-intro} +\input{043-random} +\input{044-random-part2} +\input{045-random-part3} +\input{046-random-part4} +\input{200-cpp} +\input{300-multiple-source-files} +\input{400-gdb} +% +\backmatter +\printindex +\input{about-author} +\cleardoublepage +\include{okuduke} +%\include{okuduke_pdf} + +\end{document} diff --git a/TeX/foreword.tex b/TeX/foreword.tex new file mode 100644 index 0000000..8601d0c --- /dev/null +++ b/TeX/foreword.tex @@ -0,0 +1,10 @@ +% +% Foreword +\hyperchapter{foreword}{まえがき}{まえがき} + +前書き。 + +{\vskip 1.0zw} +\begin{flushright} +江添亮 +\end{flushright} diff --git a/TeX/front.tex b/TeX/front.tex new file mode 100644 index 0000000..2839a7e --- /dev/null +++ b/TeX/front.tex @@ -0,0 +1,23 @@ +% +% 本扉 +\thispagestyle{empty} +\vbox to 0mm{\vspace*{-28.0truemm} +\hbox to 0mm{ +\hspace*{-27.3truemm}\includegraphics[width=188truemm]{fig/C++_tobira.eps} +}\vss} + +\clearpage +% +% コピーライト +\thispagestyle{empty} + +\null\vfill +% +% ## 商標 +\begin{small} +\noindent +本文中に記載されている社名および商品名は,一般に開発メーカーの登録商標です.\\ +なお,本文中では\texttrademark ・\copyright ・\textregistered 表示を明記しておりません. +\end{small} + +\clearpage diff --git a/TeX/indexstyle.ist b/TeX/indexstyle.ist new file mode 100644 index 0000000..2d82f4a --- /dev/null +++ b/TeX/indexstyle.ist @@ -0,0 +1,15 @@ +preamble "\\begin{theindex}\n\\thispagestyle{empty}\n +\\ifColor +\\markboth{\\color{BrickRed}{索引}}{\\color{BrickRed}{索引}} +\\else +\\markboth{索引}{索引} +\\fi +\n" + +lethead_flag 1 +symbol "数字・記号" +letter_head 2 + +lethead_prefix "\n{\\textsf{\\color[gray]{0.6} ■}~\\sffamily " +lethead_suffix "}\\par\\nobreak" + diff --git a/TeX/jcompile.sh b/TeX/jcompile.sh new file mode 100644 index 0000000..9cd9669 --- /dev/null +++ b/TeX/jcompile.sh @@ -0,0 +1,12 @@ + +# Clear out junk +rm -f *.aux +rm -f *.toc + +uplatex -kanji=utf8 cpp-intro + +mendex -U -s indexstyle.ist -o cpp-intro.ind cpp-intro.idx +uplatex -kanji=utf8 cpp-intro + +uplatex -kanji=utf8 cpp-intro +dvipdfmx cpp-intro diff --git a/TeX/okuduke.tex b/TeX/okuduke.tex new file mode 100644 index 0000000..5e58360 --- /dev/null +++ b/TeX/okuduke.tex @@ -0,0 +1,56 @@ +\thispagestyle{empty} + +\begin{spacing}{0.7} +\noindent \textgt{\footnotesize ●本書に対するお問い合わせは、電子メール(\texttt{info@asciidwango.jp})にてお願いいたします。\\ +但し、本書の記述内容を越えるご質問にはお答えできませんので、ご了承ください。} +\end{spacing} + +\null\vfill + +\noindent \textgt{\bfseries\large 江添亮のC++入門} + +\vspace{2mm} + +\noindent {\small 2019年9月20日 初版発行} +\vspace{4mm} + +\noindent {\footnotesize 著 者}\hspace{3zw}\jruby{江添}{え|ぞえ} \jruby{亮}{りょう}\\ +\noindent {\footnotesize 発行者}\hspace{3zw}夏野 剛 \\ +\noindent {\footnotesize 発 行}\hspace{3zw}株式会社ドワンゴ +\begin{spacing}{0.7} +\noindent \hspace{5.5zw}{\footnotesize 〒 104--0061} \\ +\noindent \hspace{5.5zw}{\footnotesize 東京都中央区銀座4--12--15歌舞伎座タワー} \\ +\noindent \hspace{5.5zw}{\footnotesize 編集 03--3549--6153} \\ +\noindent \hspace{5.5zw}{\footnotesize 電子メール \texttt{info@asciidwango.jp}} \\ +\noindent \hspace{5.5zw}{\footnotesize \texttt{https://asciidwango.jp/}} \\ +\end{spacing} +\noindent {\footnotesize 発 売}\hspace{3zw}株式会社KADOKAWA +\begin{spacing}{0.7} +\noindent \hspace{5.5zw}{\footnotesize 〒 102--8177} \\ +\noindent \hspace{5.5zw}{\footnotesize 東京都千代田区富士見2--13--3} \\ +\noindent \hspace{5.5zw}{\footnotesize 営業 0570--002--301(カスタマーサポート・ナビダイヤル)}\\ +\noindent \hspace{5.5zw}{\footnotesize 受付時間 11:00~13:00, 14:00~17:00(土日 祝日 年末年始を除く)}\\ +\noindent \hspace{5.5zw}{\footnotesize \texttt{https://www.kadokawa.co.jp/}} \\ +\end{spacing} + +\noindent {\footnotesize 印刷・製本}\hspace{1.5zw}株式会社リーブルテック \\ +\noindent {\footnotesize Printed in Japan} \\ + +\vspace{4mm} + +\begin{spacing}{0.8} +\noindent {\footnotesize 落丁・乱丁本はお取り替えいたします。下記KADOKAWA読者係までご連絡ください。}\\ +\noindent {\footnotesize 送料小社負担にてお取り替えいたします。}\\ +\noindent {\footnotesize 但し、古書店で本書を購入されている場合はお取り替えできません。}\\ +\noindent {\footnotesize 電話 049--259--1100(10:00--17:00/土日、祝日、年末年始を除く)}\\ +\noindent {\footnotesize 〒354--0041 埼玉県入間郡三芳町藤久保550--1}\\ +\noindent {\footnotesize 定価はカバーに表示してあります。} +\end{spacing} + +\vspace{2mm} + +\noindent {\footnotesize ISBN978--4--04--893071--0 C3004} \\ +\begin{spacing}{0.7} +\noindent {\footnotesize アスキードワンゴ編集部}\\ +\noindent {\footnotesize 編 集  星野浩章} +\end{spacing}