|
CPP06 1.0
|
C++ Module 06は、静的キャストから動的キャスト、さらには危険な再解釈キャストまで、C++の多様な「型変換(キャスト)」に焦点を当てています。このモジュールを通じて、コンパイル時と実行時における型の扱い方と、それに伴う強力さ、そして危険性について深く学びます。
文字列として与えられたリテラルを、
char,int,float,doubleの4つの基本的なスカラ型に変換し、その結果を表示する。変換不可能な場合や特殊な値(nan,inf)も正しく扱う。
static_cast: コンパイル時に型チェックが行われる、比較的安全なキャスト。主に数値型間の変換や、voidポインタからの変換などに使われる。今回の課題では、基準となるdouble型から他の数値型への変換に用いた。文字列から数値への変換 :
std::strtod(): 文字列をdouble型に変換する標準ライブラリ関数。変換できなかった部分のポインタを第二引数で受け取れるため、厳密なパースチェックが可能。特殊な浮動小数点数 :
nan(Not a Number),inf(Infinity) といった疑似リテラルを扱う。<cmath>ヘッダのstd::isnan(),std::isinf()を使って判定する。数値の限界値 :
<limits>ヘッダのstd::numeric_limitsを使い、各型の最大値・最小値を取得してオーバーフローをチェックする。文字の表示判定 :
<cctype>ヘッダのstd::isprint()を使い、char型に変換した結果が表示可能な文字であるかを判定する。
変換処理の基本的な流れ:
- 入力が
nan,infなどの疑似リテラルか判定する。std::strtod()でdouble型に変換してみる。パースが成功したか(文字列の末尾まで読めたか)をチェックする。double型の値を基準に、static_castを使ってchar,int,floatへ変換する。- 各型へ変換する際に、オーバーフローや表現不可能な値(
nanをintにするなど)にならないかチェックし、結果を表示する。
*
std::strtod()の第二引数endptrのチェックは非常に重要。これが入力文字列の先頭を指したままなら、全く数値として解釈できなかったことを意味する。
float型のリテラルとして表示する際のfの付け忘れに注意。std::fixedとstd::setprecision(1)を使うと、小数点以下の表示を揃えやすい。
浮動小数点数における「桁数」は、単に小数点以下の長さではなく、 「有効数字(significant digits)」 、つまり その型がどれだけ正確に数値を表現できるか という精度を表します。
* 精度: 有効数字 約6〜7桁
- 意義:
floatは4バイトのメモリしか使用しないため、軽量です。しかし、その代償として精度には限界があります。例えば、1.23456789fという数値をfloat型の変数に格納しようとしても、 7桁目あたりで情報が丸められ 、正確な値を保持することはできません。- 用途: 大量の数値を扱うグラフィックス(頂点座標など)や、それほど高い精度が要求されない計算で、メモリ使用量を節約したい場合に適しています。
* 精度: 有効数字 約15〜17桁
- 意義:
doubleは8バイトのメモリを使用し、floatの2倍以上の精度を持ちます。これにより、より広範囲の数値を、より正確に表現できます。科学技術計算や金融計算など、わずかな誤差が大きな問題につながる分野では、doubleの使用が標準です。- 用途: C++では、特に理由がない限り浮動小数点数には
doubleを使うのが一般的です。floatよりも精度が高く、意図しない計算誤差を減らすことができます。Exercise 00 における意義は、
"3.141592653589793"のような精度の高いリテラルが与えられたときに、doubleではその値をほぼ保持できるのに対し、floatにキャストすると 情報が失われる(精度が落ちる) という事実を、出力結果を通じて確認することにあります。これは、各データ型が持つ本質的な限界を理解するための重要な演習
データ構造体へのポインタを、整数型
uintptr_tに変換(シリアライズ)し、その整数から元のポインタへ復元(デシリアライズ)する。
*
reinterpret_cast: 最も危険で強力なキャスト。ある型を、全く無関係な別の型として「再解釈」する。ビットパターンをそのままに型情報だけを書き換えるイメージ。
uintptr_t: ポインタの値を欠損なく格納できることが保証されている符号なし整数型。<stdint.h>または<cstdint>で定義されている。アーキテクチャ(32bit/64bit)に関わらず、ポインタを整数として扱いたい場合に最適。- シリアライズ : メモリ上のオブジェクト(データ構造)を、ファイル保存やネットワーク転送が可能な形式(バイト列や文字列など)に変換すること。今回はその最も単純な形として、メモリアドレスを整数に変換した。
*
reinterpret_castは型の安全性を完全に破壊する。コンパイラは何も保証してくれず、プログラマの自己責任で使う必要がある。
- 異なる型のポインタ間での
reinterpret_castは、アライメントやオブジェクトのサイズの違いから未定義動作を引き起こす可能性が非常に高い。- 今回の課題のように「ポインタ ⇔ 整数」の変換は
reinterpret_castの数少ない正当な用途の一つだが、それでも使用は慎重に行うべき。
typeinfoを使わずに、基底クラスのポインタまたは参照が指しているオブジェクトの「実際の型(派生クラスの型)」を特定する。
*
dynamic_cast: 実行時に、安全なダウンキャスト(基底クラスから派生クラスへのキャスト)を行うためのキャスト。
- ポリモーフィズム (多態性) :
dynamic_castが機能するための大前提。基底クラスに少なくとも1つの仮想関数(通常は 仮想デストラクタ )を持つ必要がある。- 実行時型情報 (RTTI) :
dynamic_castは、RTTIを利用して実行時の型をチェックする。
dynamic_castは、対象がポインタか参照かで挙動が異なる。
* キャストが成功すれば、派生クラスへの有効なポインタを返す。
- キャストが失敗すれば、
NULL**(ヌルポインタ) を返す** 。
* キャストが成功すれば、派生クラスへの有効な参照を返す。
- キャストが失敗すれば、
catch (...) {}の処理へつまり他の派生クラスへのキャストを処理する。std::cout << "identify(reference): Actual type is ";try {std::cout << "A";} catch (...) {try {std::cout << "B";} catch (...) {try {std::cout << "C";} catch (...) {std::cout << "Unknown";}}}}
- 覚え書き
- 上記の手法が通常行われるが、
std::bad_castのエラーを使うためには<typeinfo>をヘッダーにインクルードする必要がある。- ただし、課題要件として、
<typeinfo>をヘッダーにインクルードすることは禁止されているため参考までに。
* 仮想デストラクタの重要性 : 基底クラスに仮想デストラクタ(または他の仮想関数)がないと、
dynamic_castはコンパイルエラーになる。ポリモーフィックなクラス階層でないと RTTI が有効にならないため。
- 参照の
dynamic_castはtry-catchブロックで囲む必要があることを忘れないようにする。
- GitHub - CPP06 (非公開)
- My Documents : CPP06 / CPP_Modules
Doxygenで作成された学習ノートです。
学習の振り返りや、レビューでも活用しています。
main: 安定版
feature/ex00: 各課題の機能開発を行う。
docs: ここにpushすると、GitHub Actions workflowが動作してドキュメントが公開されます。
C++ Moduleのプロジェクト毎にDoxygenを使ったドキュメントをまとめています。
doxygen用のcssテーマは、Doxygen Awesomeを使用。