CPP08 1.0
|
C++ Module 08 では、テンプレート化されたコンテナ、イテレータ、アルゴリズムがテーマとなります。
EX00では、テンプレート型を受け取り整数コンテナから特定の整数を最初に見つける関数テンプレートeasyfind
を作成します。
EX01では、最大N個の整数を格納できる Span
クラスの実装を通して、安全性・パフォーマンス性・リソース管理に堅牢なテクニックを学びます!
EX02では、 std::stack
コンテナを反復可能にする MutantStack
クラスを作成することで、C++のSTL(標準テンプレートライブラリ)がどのように設計されているか学びます!
Doxygenで作成されたソースコードドキュメントです。
クラスの連携図やソースコードの説明に関する情報がまとめられています。
関数テンプレートの基礎を学ぶ課題
実装する関数:Iterator : ::iterator easyfind()
Iterator + Template function + Template Container
Iterator :
template <typename T> typename T::iterator easyfind(T &container, int value);
機能 find(), push_back() リストやvectorでも共通の操作ができる。
安全面 [x], at(x) indexにアクセス可能かチェックする。
速度が必要にシビアで、安全な範囲(index)が保証されている場合は[]を使う。
基本はatを使う。
まとめ 用途に合わせてコンテナを選定する。頻繁なアクセスだとvector、途中の挿入削除が必要ならlist 抽象化:イテレーターとstd::findなどアルゴリズムを使ってコンテナの種類に依存しない汎用的なコードを書く。 安全性:基本的には[]ではなくat()を使ってコードを書くこと。 STLの基本:コンテナ、イテレータ、アルゴリズムを理解して、使いこなすことが効率化つ安全・保守性の高いC++コードの基礎につながる。
easyfind.hpp , easyfind.tpp , main.cpp , Makefile
この課題では、指定された最大数の整数を保持し、それらの数値間の最短および最長の距離(スパン)を計算できる Span クラスを実装します。
Class : Span クラス
このテンプレートクラスを実装する上で、安全性(堅牢生)とパフォーマンス(効率性)をよくするとより汎用的に使えるようなクラスを実現するためのテクニックを学びます。
パフォーマンス面
std::vector reserve関数 を使うことで、効率よくパフォーマンスを発揮することができる。(予測可能なパフォーマンスのためには必要)
最初にreserve関数を呼び出し、ある程度使うだろうという十分なメモリ確保すれば、コストの高い際割り当てとコピーを避けることができる。
std::vectorは要素を追加するたびに、メモリの再割り当てとコピーをし直す。(処理が重くなる)
安全性
explicit コンパイラが自動的な型変換を防ぐための安全装置。(引数がクラスのオブジェクトを求められる場合、)
後から見直しや、予期しない変換によるバグにつながる可能性がある。
リソース管理と例外安全性
Copy&Swap Idiom :関数本体の前に、引数Otherのコピーが先に実行さレル。処理の途中で例外が発生しても、オブジェクトが変な状態・リソースが漏れたりしないようにする
代入演算子を値渡しにする。C++の仕組みとして、関数本体よりコピーが先に実行される。コピーが成功するまで元のオブジェクト操作をしない。コピー成功したらコピーの中身とと自分自身の中身をSwapを行うだけ。Swap(ポインタを入れ替えるだけなので、例外を投げずに高速な処理が実現できる)さえ無事に終われば実質代入は成功。一次オブジェクトに古いデータが入り、リソース解放される。
自己代入ではなくセルフチェック(if文)などしなくても正しく
現在は他にもムーブセマンティクスもっと効率的な仕組みもあるが、複雑なデータ構造においても今でも基本的な考え方。
Iterator範囲を使った一括追加
コンテナに複数の要素を一度に追加したい時に非常に効率的な実装。vector , list の一部をごそっとまとめて追加したい。
Iteratorというコンテナの中の要素を指し示すものを使って範囲を指定する。(push_backなど一個ずつ追加ではなく)
追加しようとしている要素の数を調べる。std::distance で計算できる。調べて、内部で持っているvectorの容量が足りるか調べる。
足りなければ、reserveを使いまず容量確保。その後、Vectorが持っているInsert関数を使って、指定範囲の要素を一気に挿入する。
ループで1つづつpush_backするのと比べて、内部的処理の最適化が効きやすい 多くの要素を扱う時にパフォーマンスの向上につながりやすい。
まとめ:堅牢なクラスを作るための要点
パフォーマンスを上げるためのreserve関数による事前のメモリ確保
意図しない型変換を防ぐための explicitで安全性を高めること。
例外が起きても大丈なようにする、Copy&Swap iDiom
複数の要素を効率よく追加するためのiterator 範囲の活用
C++ でリソースを自分で管理するためのクラスの、基本的な非常に重要な考え方(原則)。
応用(さらなる追求)
std::vector でしたが、管理する対象がファイルハンドルやネットワーク接続やそういった簡単にコピーできない、コピーするべきではない、Swapも簡単にできないようなリソースだったら?
Copy&Swapのような例外安全のための原則をどのように適用できるか?どのように考え方を修正する必要があるか?
Class : MutantStack
この課題では、XXXXXX
std::deque がほとんどのケースは最適な理由
MutantStack.hpp , main.cpp , Makefile ,
→ 自作テンプレートライブラリ事例, voice
→ C++リファレンス
main
: 安定版
feature/ex00
: 各課題の機能開発を行う。
docs
: ここにpushすると、GitHub Actions workflowが動作してドキュメントが公開されます。
C++ Moduleのプロジェクト毎にDoxygenを使ったドキュメントをまとめています。
doxygen用のcssテーマは、Doxygen Awesomeを使用。
find . -type d -name "ex*" -exec sh -c 'cd "{}" && echo "Generating tags in {}" && ctags *' \;