🛠️

C++ MODULE01 : Fixedクラスの実装

2024/05/09に公開

リアルタイムで動くアクションゲームを想像してみてください。ゲーム内でキャラクターの動きや物理計算を正確に行うためには、計算速度が非常に重要です。ここで登場するのが、「Fixedクラス」です。このクラスは、ゲームの世界でキャラクターがジャンプする高さや、敵との距離を計算する際に使われる固定小数点数を扱うものです。固定小数点数は、その名の通り小数点の位置が固定された数値表現を用いることで、浮動小数点数よりも高速な計算が可能となり、ゲームのパフォーマンスを向上させることができます。

この記事では、C++を用いてFixedクラスをどのように実装するか、そしてそのクラスがゲーム開発にどう役立つのかを、コードの各部分を解析しながら詳しく説明します。具体的なプログラミングの概念に加えて、メモリ割り当てやメンバーへのポインタ、参照の扱い、switchステートメントの使用方法について簡単に説明します。
(メモリ割り当て, メンバーへのポインタ, 参照, switch ステートメント)

1. プリプロセッサディレクティブ

#ifndef FIXED_HPP
# define FIXED_HPP
# include <iostream>

#ifndef FIXED_HPPと# define FIXED_HPPは、このファイルが複数回インクルードされることを防ぐためのインクルードガードです。これにより、同じヘッダーファイルが複数回読み込まれることによる定義の重複を避けます。
#include <iostream>は、入出力に関連する機能を利用するためにiostreamライブラリをインクルードしています。このコードでは、コンストラクタ、デストラクタ、およびメンバ関数内での標準出力にstd::coutを使用しています。

2. クラス定義

class Fixed
{
	private:
		int					_value;
		static const int	_frac;
	public:
		Fixed(void);
		~Fixed(void);
		Fixed(Fixed const &copy);
		Fixed &operator=(Fixed const &copy);
		int getRawBits(void) const;
		void setRawBits(int const raw);
};
  • class Fixedは、固定小数点数を表すクラスです。
  • メンバ変数_valueは、固定小数点数の実際の値を保持します。
  • 静的メンバ変数_fracは、小数点以下のビット数を表します。この例では、その値は8に設定されており、これは固定小数点表現の一部として使用されます。
  • Fixed(void);はデフォルトコンストラクタで、新しいFixedオブジェクトを初期化します。
  • ~Fixed(void);はデストラクタで、Fixedオブジェクトが破棄されるときに呼び出されます。
  • Fixed(Fixed const &copy);はコピーコンストラクタで、既存のFixedオブジェクトをコピーして新しいオブジェクトを作成します。
  • Fixed &operator=(Fixed const &copy);は代入演算子のオーバーロードで、一方のFixedオブジェクトを別のFixedオブジェクトに代入します。
  • getRawBits関数は、_valueの値を返します。
  • setRawBits関数は、_valueの値を設定します。

3. 実装部分

const int Fixed::_frac = 8;
  • _fracの実際の値を定義しています。これは、クラスの外で行う必要があります。
  • コンストラクタ、デストラクタ、およびメンバ関数
  • コンストラクタは、オブジェクトが作成されたときに_valueを0に初期化し、メッセージを出力します。
  • デストラクタは、オブジェクトが破棄されるときにメッセージを出力します。
  • コピーコンストラクタは、オブジェクトがコピーされるときにメッセージを出力し、代入演算子を使用して値をコピーします。
  • 代入演算子は、_valueをコピーし、自身の参照を返します。
  • getRawBitsは、_valueの値を取得するための関数です。
  • setRawBitsは、_valueに新しい値を設定するための関数です。

4. メイン関数

int main(void)
{
	Fixed a;
	Fixed b(a);
	Fixed c;

	c = b;
	std::cout << a.getRawBits() << std::endl;
	std::cout << b.getRawBits() << std::endl;
	std::cout << c.getRawBits() << std::endl;
	return (0);
}

この部分では、Fixedクラスのオブジェクトを作成し、コピーコンストラクタと代入演算子の動作をテストしています。また、getRawBits関数を使用して、各オブジェクトの_valueの値を出力しています。
このコードは、クラスの基本的な概念、メモリ管理、およびC++の演算子オーバーローディングの基本を理解するのに役立つ良い例です。

&operator=は、C++における代入演算子(=)のオーバーロードを表します。オーバーロードとは、演算子や関数に対して、既定の動作とは異なる特定の振る舞いを定義することです。この文脈での&は、関数の戻り値が参照であることを示しています。つまり、Fixed &Fixed::operator=(const Fixed &copy)は、Fixed型のオブジェクトを代入する際に特定の動作をするように定義しており、結果として自身の参照を返します。

5. 具体例

以下の例を考えてみましょう。あるFixedクラスのオブジェクトaとbがあり、bにaを代入したいとします。代入演算子をオーバーロードすることで、この代入の際に特定の処理を行うことができます。

Fixed a;  // ここでデフォルトコンストラクタが呼ばれる
Fixed b;  // ここでもデフォルトコンストラクタが呼ばれる
b = a;    // ここで代入演算子のオーバーロードが呼ばれる
b = a;

を実行すると、Fixedクラスのoperator=が呼ばれます。この関数は、aのプライベートメンバ変数_valueの値をbの_valueにコピーし、その後bの参照を返します。これにより、bにaの状態がコピーされます。

代入演算子のオーバーロードの実装

Fixed &Fixed::operator=(const Fixed &copy)
{
    std::cout << "Assignment operator called" << std::endl;
    if (this != &copy) { // 自己代入をチェック
        this->_value = copy.getRawBits(); // _valueをコピー
    }
    return *this; // 自身の参照を返す
}

この実装では、まず自己代入をチェックしています(this != &copy)。自己代入とは、同じオブジェクトを自身に代入しようとするケースのことで、これをチェックしないと不要な操作やエラーの原因になることがあります。次に、_valueをコピーし、最後に自身の参照を返しています。

代入演算子をオーバーロードすることで、オブジェクト間の代入時にカスタム動作を定義できるため、C++の強力な機能の一つです。これにより、ディープコピーのような特別な処理が必要な場合や、特定のリソース管理が必要な場合に、それを実現できます。

0

6. 実行してみる

int	main (void)
{
	Fixed a;
	Fixed const b(10);
	Fixed const c(42.42f);
	Fixed const d(b);

	a = Fixed (1234.4321f);
	std::cout << "a is " << a << std::endl;
	std::cout << "b is " << b << std::endl;
	std::cout << "c is " << c << std::endl;
	std::cout << "d is " << d << std::endl;
	std::cout << "a is " << a.toInt() << " as integer" << std::endl;
	std::cout << "b is " << b.toInt() << " as integer" << std::endl;
	std::cout << "c is " << c.toInt() << " as integer" << std::endl;
	std::cout << "d is " << d.toInt() << " as integer" << std::endl;
	return (0);	
}
./fixed
->
Fixed object created with default constructor
Fixed object creted woth int constructor
Fixed object created with float constructor
Fixed object copied
Assignment operator called
Fixed object created with float constructor
Assignment operator called
Fixed object destroyed
a is 1234.43
b is 10
c is 42.418
d is 10
a is 1234 as integer
b is 10 as integer
c is 42 as integer
d is 10 as integer
Fixed object destroyed
Fixed object destroyed
Fixed object destroyed
Fixed object destroyed

このコードスニペットがa is 1234.43と出力する理由は、Fixedクラスの浮動小数点数を引数とするコンストラクタとoperator<<のオーバーロードによるものです。具体的な動作は以下の通りです:

Fixed (1234.4321f);は、Fixedクラスの浮動小数点数を引数に取るコンストラクタを呼び出します。このコンストラクタは、与えられた浮動小数点数(1234.4321f)を内部表現に変換します。この内部表現は、固定小数点数表現に基づいており、クラス内部での値の扱い(例えば、精度の制御や演算の実装)に使用されます。

a << std::cout << "a is " << a << std::endl;の部分で、operator<<がオーバーロードされているため、Fixedオブジェクトaをstd::coutに直接挿入できます。このオーバーロードされた演算子は、Fixedオブジェクトの浮動小数点数への変換を行い、その結果を出力ストリームに渡します。

Fixed::toFloat関数は、内部の固定小数点数表現を元の浮動小数点数の値に戻します。この変換過程で、元の値1234.4321fは内部表現の精度の制約により、出力時には1234.43として近似されます。この近似は、内部で使用される固定小数点数のビット数(この例では8ビットの小数部)によって影響を受けます。

最終的に、std::coutを使った出力では、aオブジェクトが1234.43として表示されます。これは、Fixedオブジェクトの浮動小数点数への変換と、標準出力への挿入による結果です。

参考:
https://cpprefjp.github.io/international-standard.html
https://c-lang.sevendays-study.com/column-6.html

Discussion