
マイケル
みなさんこんばんは!
マイケルです!
マイケルです!

エレキベア
クマ〜〜〜

マイケル
今日から新しく C++を使ったゲーム開発 企画を始めるよ!

エレキベア
C++クマか〜〜
何か難しそうなイメージがあるクマ〜〜
何か難しそうなイメージがあるクマ〜〜

マイケル
今回は手をつけてみたいけど難しそうという人達に向けて、
C++を使うきっかけとなるよう書いていこうと思うよ!
C++を使うきっかけとなるよう書いていこうと思うよ!
対象読者
- 普段JavaやC#といった高水準言語を使用している人
- ゲームエンジンを使った開発を行ったことがあり、より深い知識を得たい人

マイケル
というわけで、プログラミング言語が触ったことがあり、
オブジェクト指向もある程度は理解しているという前提で書いていきます!
今回もそれらの言語と比較した場合のC++の特徴や注意点 という観点での説明となるのでご容赦ください。
オブジェクト指向もある程度は理解しているという前提で書いていきます!
今回もそれらの言語と比較した場合のC++の特徴や注意点 という観点での説明となるのでご容赦ください。

エレキベア
オブジェクト指向ならカンペキクマ〜〜〜

マイケル
また、今後の予定としては下記を計画しています!
第一回 C++の基礎知識
第二回 SDLを使った2Dゲーム開発
第三回 OpenGLを使った3Dゲーム開発
今回は第一回ということで C++の基礎知識編 です!
第一回 C++の基礎知識
第二回 SDLを使った2Dゲーム開発
第三回 OpenGLを使った3Dゲーム開発
今回は第一回ということで C++の基礎知識編 です!

エレキベア
やったるクマ・・・!!
参考書籍

マイケル
今回参考にした書籍は下記になります!

エレキベア
C++の本じゃないクマね

マイケル
どちらもゲームプログラミングに関する本だけど、
C++の特徴の部分はざっと書いてあるので、触ったことがない人にもおすすめです!
C++の特徴の部分はざっと書いてあるので、触ったことがない人にもおすすめです!

マイケル
何よりゲームプログラミングに関して幅広く触れられているので、
ゲームエンジンでの開発しかやったことがなければ確実に力がつく本だと思います・・・!
ゲームエンジンでの開発しかやったことがなければ確実に力がつく本だと思います・・・!

エレキベア
読み終わったら貸してくれクマ・・・
使用するIDE

マイケル
まずC++を動作させる環境についてですが、使用するIDEは主に
VisualStudio、VisualStudioCode、CLionの3択になると思います。
Mac版のVisualStudioはC++はサポートしていないようなのでご注意ください!
VisualStudio、VisualStudioCode、CLionの3択になると思います。
Mac版のVisualStudioはC++はサポートしていないようなのでご注意ください!
IDE | Windows | Mac |
VisualStudio | ◯ | × |
VisualStudioCode | ◯ | ◯ |
CLion(有償) | ◯ | ◯ |
↑各IDEでのC++サポート(2021年8月現在)

マイケル
CLionは有償というのもあるので、VSCodeに「C/C++」「CodeRunnner」等の拡張機能を入れて使うのが一番手っ取り早そうですね。

エレキベア
VSCodeは便利クマ〜〜〜
C++の基礎知識

マイケル
それではC++を触っていきましょう!
型

マイケル
まずは使用できる型についていくつか見ていきます!

エレキベア
型なんて余裕クマ〜〜
数値

マイケル
数値型について、int、floatなどが使えるのはC#やJavaと同じですが、
C/C++では一部の型にunsignedを指定することができます。
C/C++では一部の型にunsignedを指定することができます。
↑int型にunsigned指定が可能

マイケル
unsignedを指定すると、正の値の範囲のみとなります。
しかし、0以下になると最大値に巻き戻る現象が起きてしまうので、unsignedを付与するのはビット演算や不変な値の場合のみとした方がよいです。
しかし、0以下になると最大値に巻き戻る現象が起きてしまうので、unsignedを付与するのはビット演算や不変な値の場合のみとした方がよいです。

エレキベア
正の値だけの表現にすることで表現できる範囲も広がっているクマね

マイケル
またintの他、char型にもunsignedを指定することができます。
char型は文字を格納しますが、実質は1バイトの整数型です。
こちらもunsignedを付与するのはビット演算や不変な数値として扱いたい場合のみとした方がよいです。
char型は文字を格納しますが、実質は1バイトの整数型です。
こちらもunsignedを付与するのはビット演算や不変な数値として扱いたい場合のみとした方がよいです。
↑char型にもunsigned指定が可能
文字列

マイケル
次に文字列の扱いについてですが、C言語ではstringが無く、char型の配列として表現することしかできませんでしたが、C++では標準ライブラリとしてstringが追加されました。
文字列の長さが決まっているか固定の場合はchar配列を使ってもよいですが、可変な場合はstringを使用した方がよいと思います。
文字列の長さが決まっているか固定の場合はchar配列を使ってもよいですが、可変な場合はstringを使用した方がよいと思います。

エレキベア
stringは元々無かったのクマね
真偽値

マイケル
真偽値に関してはC#やJavaとも変わりなくtrue、falseのみですね。

マイケル
以上、よく使用する型について解説しましたが、その他の型については
下記の公式ドキュメントを参照してください!
下記の公式ドキュメントを参照してください!

エレキベア
大きな違いはunsignedクマね
参照とポインタ

マイケル
次に参照とポインタについて解説します!
よくポインタは難しいと聞くかもしれませんが、参照型やメモリ構造を理解していればそれほど難しい話ではありません。
よくポインタは難しいと聞くかもしれませんが、参照型やメモリ構造を理解していればそれほど難しい話ではありません。

エレキベア
ポインタ怖いクマ・・・
参照

マイケル
参照は既に存在する変数を参照する変数のことです。
型の後ろに&を付けて代入することでその変数の参照を示すようになります。
型の後ろに&を付けて代入することでその変数の参照を示すようになります。
↑参照の挙動

マイケル
上記の例では、aとbどちらも同じメモリアドレスを参照しているため、aの値が変わるとbの値も変わるといった挙動になります。

エレキベア
これはC#やJavaでも同じクマね
ポインタ

マイケル
そしてポインタもメモリアドレスを参照する点では同じですが、ポインタ変数自体はアドレスを示しています。
そのため、そのまま出力すると「0x7ffee93d07cc」といったアドレスが出力されます。
そのため、そのまま出力すると「0x7ffee93d07cc」といったアドレスが出力されます。

マイケル
ポインタ変数は型の後ろに*を付与して宣言し、変数の前に&を付けてアドレスを返して代入します。
そしてアドレスでなく値にアクセスする際には変数の前に*を付与します。
そしてアドレスでなく値にアクセスする際には変数の前に*を付与します。
↑ポインタの挙動

エレキベア
*や&が少しややこしいクマ・・・

マイケル
最初は混乱するかもしれないけど、そういうルールだと思って覚えておこう!

マイケル
更に参照と異なる点として、ポインタは参照するアドレスを移動できるという点があります。
ポインタ変数をインクリメント(+1)することで参照先を移動することができ、メモリアドレスを連続で確保する配列のループとしてよく使われます。
ポインタ変数をインクリメント(+1)することで参照先を移動することができ、メモリアドレスを連続で確保する配列のループとしてよく使われます。
↑参照するアドレスの変更

エレキベア
より柔軟な操作ができる参照みたいなイメージクマね
引数としての利用

マイケル
参照とポインタは関数の引数としても利用することができます。
例えば次のスワップ関数を見ていきましょう!
例えば次のスワップ関数を見ていきましょう!
↑値渡しのNG例

マイケル
上記の例ではa,bを値渡ししているため呼び出し元の変数は変わりません。
この引数を参照型やポインタ型にすることで、呼び出し元の変数にも反映させることができます。
この引数を参照型やポインタ型にすることで、呼び出し元の変数にも反映させることができます。
↑参照渡し、ポインタ渡しの例

マイケル
これはどちらも同じ挙動となりますが、ポインタ変数で渡すと呼び出し元にも「&」を付けないといけないため、「変数が変わること」を予測しやすくなります。

エレキベア
呼び出し側からも変わるのが分かった方が間違いが起こりにくそうクマね

マイケル
また、オブジェクトを値渡ししてしまうとその度にコピーが発生してしまうため効率的にはよくないです。
そのため、オブジェクトは参照かポインタで渡すのが基本となりますが、不変な場合にはconstを付けることで値が変更されることを防ぐことができます!
そのため、オブジェクトは参照かポインタで渡すのが基本となりますが、不変な場合にはconstを付けることで値が変更されることを防ぐことができます!
↑引数にconstを付けることで値の変更を防ぐかつ効率よくする

マイケル
以上のことから、
・可変なオブジェクトを渡す場合はポインタ渡し
・不変なオブジェクトを渡す場合にはconst参照渡し
というルールを設定することで可読性がよく効率もよい記述ができます!
・可変なオブジェクトを渡す場合はポインタ渡し
・不変なオブジェクトを渡す場合にはconst参照渡し
というルールを設定することで可読性がよく効率もよい記述ができます!

エレキベア
これで参照とポインタはマスタークマ〜〜〜
メモリ管理
メモリの動的確保

マイケル
そして次はメモリ管理についてです!
下記はint型の値と配列を宣言する処理ですが、通常はスタックメモリと呼ばれる領域に確保されます。
下記はint型の値と配列を宣言する処理ですが、通常はスタックメモリと呼ばれる領域に確保されます。
↑メモリの静的確保

マイケル
このような確保を静的確保といいますが、
・スタックメモリは利用できる量が少ない
・スコープを抜けるとメモリが破棄されるため存続期間が限られる
といったデメリットがあります。
・スタックメモリは利用できる量が少ない
・スコープを抜けるとメモリが破棄されるため存続期間が限られる
といったデメリットがあります。

マイケル
そこで下記のようにnewを使用して宣言することで、ヒープメモリという別領域に動的確保することができます。
↑メモリの動的確保

マイケル
動的確保したらポインタが返され、好きなタイミングでメモリを開放(delete)することができます。
逆に言えばメモリ管理の責任を負うということで、メモリを確保したら開放処理も書かなければなりません。
逆に言えばメモリ管理の責任を負うということで、メモリを確保したら開放処理も書かなければなりません。

エレキベア
開放を忘れてしまったら恐ろしいクマ・・・

マイケル
また、配列のメモリ開放時はdelete[]を使用する必要がある
ことには気をつけましょう!
ことには気をつけましょう!
補足:メモリアロケータ

マイケル
メモリ管理について補足ですが、確保と開放を繰り返しているとメモリの断片化が発生することがあります。

マイケル
断片化とは下記のように、メモリの空領域が散りばることで空容量はあるのに確保できないという状態になることです。
↑メモリの断片化

エレキベア
メモリがぐちゃぐちゃになってるクマね・・・

マイケル
そこでメモリを管理するクラス(メモリアロケータ)を自作して対処する方法があります。

マイケル
実装方法については割愛しますが、調べたら分かりやすい解説記事がいくつかあったので、下記にいくつか紹介しておきます!
興味がある方は勉強してみてください!
興味がある方は勉強してみてください!
[自作メモリアロケータの例]
・Stack Allocator
先頭ブロックから順にメモリを取得するが、開放は全て開放かある状態に戻すしかできない。
高速かつ無駄なメモリを使用しないが、自由に開放できない欠点もある。
参考:プログラミング備忘録 スタックアロケータ
・Pool Allocator
8、16、32、64、128byteといったそれぞれのプール領域を作成し、確保するサイズに応じて割り当てる方法。
無駄が少なく実装も比較的シンプル。
参考:C++の簡単なメモリアロケータ実装 – Qiita
・Linked List Allocator
開放時に空ブロックをリンクリストで繋げる方法。
参考:Linked List Allocator – Qiita

エレキベア
いろんなやり方があって面白いクマね〜〜
クラス定義

マイケル
次はクラス定義について!
オブジェクト指向の言語を触ったことがあれば基本は同じです。
オブジェクト指向の言語を触ったことがあれば基本は同じです。
基本的な書き方

マイケル
C++では、ヘッダファイルに変数や関数の宣言を書いてcppファイルに処理を書く、というように分けるのが一般的です。
下記はいくつかの変数と関数を定義したシンプルなクラスの例です。
下記はいくつかの変数と関数を定義したシンプルなクラスの例です。

マイケル
書き方を覚えれば基本はJavaやC#と同じように書けるかと思います!

エレキベア
余裕クマ〜〜〜
継承

マイケル
継承も基本的にJavaやC#と同じような実装になります。
例えば今回ElekiBearクラスの他にGoroyanクラスを作成しようとした場合、各オブジェクトの共通的な処理を管理するクラスが欲しくなると思います。
例えば今回ElekiBearクラスの他にGoroyanクラスを作成しようとした場合、各オブジェクトの共通的な処理を管理するクラスが欲しくなると思います。

マイケル
そこで下記のように親クラスとしてMonsterクラスを定義することで、ElekiBearクラスとGoroyanクラスには固有の処理のみ持たせることができます。
↑親クラスを定義

エレキベア
(モンスター扱いされたクマ・・・)

ゴロヤン
ゴロ・・・

マイケル
C++ではこれを下記のように実装します!
まずMonsterクラスには共通処理を全て定義して、固有の処理を定義してほしいGetVoice関数にはvirtualを指定するようにします。
まずMonsterクラスには共通処理を全て定義して、固有の処理を定義してほしいGetVoice関数にはvirtualを指定するようにします。

マイケル
この時、子クラスを破棄した時にデストラクタが確実に呼ばれるよう、デストラクタにもvirtual指定するようにしてください。

マイケル
そしてElekiBearクラスとGoroyanクラスは下記の通り!
コンストラクタ、デストラクタの他、親クラスの上書きしたい処理を定義(オーバーライド)しましょう!
コンストラクタ、デストラクタの他、親クラスの上書きしたい処理を定義(オーバーライド)しましょう!

マイケル
このように書くことで、子オブジェクトそれぞれの鳴き声で喋るようになったはずです!
↑それぞれ固有の処理になっている

エレキベア
モンスター扱いは納得できないクマ
インクルードガード

マイケル
気になっていた方もいるかもしれませんが、ヘッダークラスの頭には「#pragma once」という指定をしていました。

エレキベア
この呪文は何なのクマ?

マイケル
これはインクルードガードといって、インクルードされた時に何回も展開されるのを防ぐための指定のことです!
これを付けないと重複でインクルードされてエラーになってしまいます。
これを付けないと重複でインクルードされてエラーになってしまいます。

エレキベア
そんな罠があったクマね・・・

マイケル
インクルードガードには、今回紹介した「#pragma once」指定の他にも「#ifndef」指定をする方法もあります。
↑pragma onceでの指定
↑ifndefでの指定

マイケル
「#pragma once」の方が新しく行数も少ないため、こちらを使用で問題ないかと思います!

エレキベア
ヘッダファイルには忘れず付けるクマ
演算子のオーバーロード

マイケル
またC++特有の使用として、演算子のオーバーロードがあるので紹介しておきます!
下記のように「operator【演算子】」で関数を定義することで、演算子を使った固有の処理を指定することができます!
下記のように「operator【演算子】」で関数を定義することで、演算子を使った固有の処理を指定することができます!

マイケル
これはベクトルクラスの例になりますが、このように記述することで直接演算子で計算できるようになります!
↑直接計算できるようになる

エレキベア
これは便利クマ〜〜〜
補足:STL

マイケル
最後に補足になりますが、これまで度々出てきたstd::coutやstd::stringといったものは、STL(Standard Template Library)というC++ライブラリの一部分になります。
下記リファレンスに他の機能も載っているので目を通して見てください!
下記リファレンスに他の機能も載っているので目を通して見てください!

マイケル
std::vectorは動的配列クラスとして便利で次回も使うと思うので、一度使い方を調べて見るとよいと思います。
(JavaでいうArrayListみたいなイメージですが挙動は違う部分があるので注意!)
(JavaでいうArrayListみたいなイメージですが挙動は違う部分があるので注意!)

エレキベア
いろいろ用意されてて楽しいクマね
おわりに

マイケル
というわけで今回はC++の基礎知識についてでした!
どうだったかな?
どうだったかな?

エレキベア
C#やJavaと比べるとやっぱりメモリ周りの考慮が必要になるクマね

マイケル
低レベルな処理まで行うのは大変だけど、
その分無駄な処理を省いて効率化するのが醍醐味になりそうだね。
その分無駄な処理を省いて効率化するのが醍醐味になりそうだね。

マイケル
それでは次はC++を使って簡単な2Dゲームを作っていくよ!
お楽しみに〜〜!!
お楽しみに〜〜!!

エレキベア
クマ〜〜〜〜
【C++】第一回 C++を使ったゲーム開発 〜C++基礎知識(型、ポインタ、メモリ管理)編〜 〜完〜
※続きはこちら!