https://youtu.be/OAebFIJvA_U
\textcolor{pink}{四国めたん: } 教師役ですわ
\textcolor{lime}{ずんだもん: } 生徒役なのだ
\footnotesize \textcolor{pink}{四国めたん:} こんにちは。四国めたんです
\footnotesize \textcolor{lime}{ずんだもん:} ずんだもんなのだ。こんにちはなのだ
\footnotesize \textcolor{pink}{四国めたん:} 今回は動的メモリ配置についてお話ししますわ
\footnotesize \textcolor{lime}{ずんだもん:} 動的メモリ配置というとmalloc
とかfree
とかか?
\footnotesize \textcolor{pink}{四国めたん:} C++言語ではmalloc
やfree
よりもnew
やdelete
を使うのが一般的ですわね
\footnotesize \textcolor{lime}{ずんだもん:} new
?delete
?
\footnotesize \textcolor{pink}{四国めたん:} はい、その辺りを中心にお話ししますわ
\footnotesize \textcolor{lime}{ずんだもん:} なるほど、さっそくお願いするのだ
動的メモリ確保はnewで
\footnotesize \textcolor{pink}{四国めたん:} 前回はクラスの コンストラクタ と デストラクタ についてお話ししましたわ
\footnotesize \textcolor{lime}{ずんだもん:} おぼえているのだ
\footnotesize \textcolor{pink}{四国めたん:} その中で、char
型のポインタをメンバ変数として、 コンストラクタ 内でヒープから動的にメモリを割り当てていましたわ
\footnotesize \textcolor{lime}{ずんだもん:} C言語と同じくmalloc
関数を用いていたのだ
\footnotesize \textcolor{pink}{四国めたん:} はい、C++言語でもC言語と同様に、動的にメモリを割り当てるための関数malloc
やfree
を使うことができますわ
\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ
\footnotesize \textcolor{pink}{四国めたん:} ですが、C++言語では動的にメモリを割り当てるための演算子new
が追加されましたわ
\footnotesize \textcolor{lime}{ずんだもん:} 関数ではなく演算子になったのか?!
\footnotesize \textcolor{pink}{四国めたん:} はい、new
演算子の使い方は簡単で、このようにしますわ
\footnotesize \textcolor{lime}{ずんだもん:} たしかに簡単なのだ
\footnotesize \textcolor{lime}{ずんだもん:} でもmalloc
関数でも問題ないのではないか?
\footnotesize \textcolor{pink}{四国めたん:} new
がmalloc
よりも優れている点はいくつかありますわ
確保するメモリのサイズを計算する必要がない
戻り値は指定した型へのポインタなので、キャストする必要がない
コンストラクタが呼ばれるので、自動的に初期化される
\footnotesize \textcolor{lime}{ずんだもん:} なるほど、たしかに型そのものを指定するので、sizeof
などでサイズを計算する必要がないのだ
\footnotesize \textcolor{pink}{四国めたん:} 特に最後の「コンストラクタが呼ばれる」のがnew
の最大の特徴ですわ
\footnotesize \textcolor{lime}{ずんだもん:} 引数のある コンストラクタ を呼ぶことはできるのか?
\footnotesize \textcolor{pink}{四国めたん:} はい、引数のある コンストラクタ を呼ぶようにする場合には、型の後ろに括弧で引数を指定しますわ
\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ
\footnotesize \textcolor{pink}{四国めたん:} ちなみに括弧が無かったり引数の指定がなければ、 デフォルトのコンストラクタ が呼ばれますわ
配列のメモリ確保new[]
\footnotesize \textcolor{lime}{ずんだもん:} ところでmalloc
では配列の割り当てもできたが、new
でも可能なのか?
\footnotesize \textcolor{pink}{四国めたん:} はい、配列のメモリ確保はnew[]
演算子で可能ですわ
\footnotesize \textcolor{pink}{四国めたん:} 形式としてはこんな感じですわね
\footnotesize \textcolor{pink}{四国めたん:} 型の後ろに角括弧"[]"で配列のサイズを指定しますわ
\footnotesize \textcolor{lime}{ずんだもん:} C言語での配列のサイズ指定に使えたのはこれらだけだったのだ
整数リテラル
#define
ディレクティブを使った定数
列挙子
\footnotesize \textcolor{lime}{ずんだもん:} new[]
演算子の配列でサイズ指定に使えるのも同じなのか?
\footnotesize \textcolor{pink}{四国めたん:} 追加で幾つかサイズ指定に使うことができるものがありますわ
const
キーワードを使った整数の定数
整数型の変数
\footnotesize \textcolor{lime}{ずんだもん:} サイズ指定に変数を使えるのか⁉
\footnotesize \textcolor{pink}{四国めたん:} はい、ちなみに通常の配列の宣言では 整数型の変数 をサイズに指定した場合にはエラーとなりますわ
const int size_const = 5;
int size = 5;
int array_const[size_const] = {0}; // (1) OK
int array[size] = {0}; // (2) エラー
int* parray = new int[size]; // (3) OK
\footnotesize \textcolor{lime}{ずんだもん:} つまり(1)はconst
キーワードを使った整数の定数を配列のサイズに指定しているのでOKということか
\footnotesize \textcolor{pink}{四国めたん:} はい、でも(2)は変数を配列のサイズに指定しているのでNGですわ
\footnotesize \textcolor{pink}{四国めたん:} そして(3)はnew[]
演算子で配列を確保していますので、サイズに変数を指定してもOKとなっていますわ
\footnotesize \textcolor{lime}{ずんだもん:} ところでnew[]
演算子で配列を確保する際に、引数を指定するにはどうしたらいいのだ?
\footnotesize \textcolor{pink}{四国めたん:} new[]
演算子での配列の確保時には、引数を指定する方法が提供されていませんわ
\footnotesize \textcolor{lime}{ずんだもん:} え~、引数を指定できないのか⁉
\footnotesize \textcolor{pink}{四国めたん:} はい、ですので デフォルトのコンストラクタ は呼ばれますが、それ以外の コンストラクタ を呼ぶことができませんわ
\footnotesize \textcolor{lime}{ずんだもん:} つまり デフォルトのコンストラクタ の実装は必須と云うことか...
\footnotesize \textcolor{pink}{四国めたん:} そして配列の各要素に引数を指定して初期値を与えたい場合には、初期化のためのメソッド、例えばInit
やInitialize
などのメソッドが必要ですわね
\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ
動的メモリの開放はdeleteで
\footnotesize \textcolor{pink}{四国めたん:} 次に、new
演算子で動的に割り当てられたメモリ領域を開放するための方法をお話ししますわ
\footnotesize \textcolor{lime}{ずんだもん:} free
関数のようなものか?
\footnotesize \textcolor{pink}{四国めたん:} はい、new
演算子で動的に割り当てられたメモリ領域を開放する場合には、free
関数ではなく、新たに追加された演算子delete
を使いますわ
\footnotesize \textcolor{lime}{ずんだもん:} どんな形式なのだ?
\footnotesize \textcolor{pink}{四国めたん:} こんな感じですわね
\footnotesize \textcolor{lime}{ずんだもん:} 指定するのは インスタンスへのポインタ なのか?
\footnotesize \textcolor{pink}{四国めたん:} はい、new演算子は **インスタンスへのポインタ** を返すので、
delete`演算子には インスタンスへのポインタ を指定しますわ
\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ
\footnotesize \textcolor{pink}{四国めたん:} なお、delete
にnullptr
を指定しても何もしないので、delete
の前に インスタンスへのポインタ がnullptr
かどうかをチェックする必要はありませんわ
\footnotesize \textcolor{lime}{ずんだもん:} nullptr
とは何なのだ?
\footnotesize \textcolor{pink}{四国めたん:} nullptr
はC++11から導入されたキーワードで、NULL
と概ね同じですわ
\footnotesize \textcolor{lime}{ずんだもん:} なるほど、わざわざ追加してくれたキーワードなので積極的に使っていくのだ
nullptrとNULL
!
C言語では、NULL
の値や型はコンパイラや環境依存の部分がありますが、概ね#define NULL ((void*)0)
と定義されています
C++言語では、C言語とは異なりNULL
は#define NULL 0
として定義されています
つまり整数です
ポインタ型に対して整数の割り当てや比較対象にするのは、たとえ0であっても問題が発生する可能性があります
nullptr
は、その解決手段としてC++11から導入されました
基本的にはインスタンスを含むオブジェクトを指していないことを表すキーワードです
なお、NULL
との互換性の関係上だと思われますが、0との比較なども可能となっています
ただ、バグの元となる可能性もありますので、nullptr
はnullptr
として使うほうが良いと思います
new[]で確保された配列を開放しましょう
\footnotesize \textcolor{lime}{ずんだもん:} ところでnew[]
演算子で確保されたメモリ領域を開放する場合もdelete
演算子でOKなのか?
\footnotesize \textcolor{pink}{四国めたん:} new[]
演算子を使って確保された動的メモリ領域の開放はdelete
ではなくdelete[]
演算子で行いますわ
\footnotesize \textcolor{pink}{四国めたん:} つまりこんな感じですわね
int size = 5;
int* parray = new int[size];
:
:
delete[] parray;
\footnotesize \textcolor{lime}{ずんだもん:} なるほど、通常のインスタンスと配列とでは区別する必要があるのか
\footnotesize \textcolor{pink}{四国めたん:} そうですわね
\footnotesize \textcolor{pink}{四国めたん:} ベテランのプログラマーでも角括弧"[]"を忘れがちで、メモリリークや最悪、暴走などを起こしていますわ
\footnotesize \textcolor{lime}{ずんだもん:} そうなのか?
\footnotesize \textcolor{pink}{四国めたん:} はい、角括弧"[]"を忘れてもワーニングが出るくらいでエラーが出るわけではないので、充分な注意が必要ですわ
newとdeleteの実際
\footnotesize \textcolor{pink}{四国めたん:} それでは、new
とdelete
を使って、前回のプログラム例を書き換えてみましょう
\footnotesize \textcolor{lime}{ずんだもん:} お願いするのだ
#include <iostream>
const double kPI = 3.14159265358979323846;
const int kMessageSize = 50;
/// @brief 円
class Circle {
double diameter_; // 直径
double border_width_; // 境界線の幅
char* pmessage_;
public:
Circle(double diameter, double border_width)
: diameter_(diameter), border_width_(border_width), pmessage_(nullptr) {
pmessage_ = new char[kMessageSize];
}
~Circle() {
std::cout << "デストラクタが呼ばれました" << std::endl;
delete[] pmessage_;
pmessage_ = nullptr;
}
double Diameter() { return diameter_; }
void Diameter(double diameter) { diameter_ = diameter; }
double BorderWidth() { return border_width_; }
void BorderWidth(double border_width) { border_width_ = border_width; }
double Area() {
double radius = Diameter() / 2.0;
double a = radius * radius* kPI;
return a;
}
const char* Message() {
double area = Area();
sprintf_s(pmessage_, kMessageSize, "円の面積は%fです。", area);
return pmessage_;
}
};
int main(int argc, char* argv[]) {
Circle* pc = new Circle(10.0, 1.0);
std::cout << "円の直径は" << pc->Diameter() << "cmです。" << std::endl;
std::cout << pc->Message() << std::endl;
delete pc;
pc = nullptr;
return 0;
}
\footnotesize \textcolor{lime}{ずんだもん:} さっそくプログラムの解説をお願いするのだ
定数はconstを使いましょう
\footnotesize \textcolor{pink}{四国めたん:} まず、前回#define
ディレクティブで定義していた定数についてはconst
キーワードを使って書き直していますわ
\footnotesize \textcolor{lime}{ずんだもん:} まぁ、配列のサイズ指定に使えるなら#define
ディレクティブよりも使い易いのだ
\footnotesize \textcolor{pink}{四国めたん:} そして定数名については最初に"k"を付加したキャメルケース にしていますわ
\footnotesize \textcolor{lime}{ずんだもん:} 全部大文字ではなく、キャメルケースにする意味があるのか?
\footnotesize \textcolor{pink}{四国めたん:} これはC++言語におけるGoogleのコーディングスタイルなので、特にこだわる必要はありませんわ
\footnotesize \textcolor{lime}{ずんだもん:} まぁ、コーディングスタイルと云うなら、合わせた方がいいのだ
\footnotesize \textcolor{pink}{四国めたん:} なおC++言語での定数定義は、#define
ディレクティブを使うよりもconst
キーワードを使用した方が推奨されますわね
\footnotesize \textcolor{lime}{ずんだもん:} そうなのか?
\footnotesize \textcolor{pink}{四国めたん:} はい、C言語ではconst
キーワードで定義した定数は使える場所が限られていましたが、C++言語では制限が大幅に緩和されていますわ
\footnotesize \textcolor{pink}{四国めたん:} また、#define
ディレクティブよりもconst
キーワードを使用したほうが、エディタやコンパイラ、デバッガーの恩恵を受けやすくなりますわ
\footnotesize \textcolor{lime}{ずんだもん:} なるほど、C++言語での定数定義はconst
キーワードを使うほうが良さそうなのだ
配列のメモリを確保しましょう
\footnotesize \textcolor{pink}{四国めたん:} 次に コンストラクタ 内でchar
型の配列をnew[]
演算子により確保していますわ
\footnotesize \textcolor{lime}{ずんだもん:} 角括弧"[]"を使って配列のサイズを指定しているのだ
\footnotesize \textcolor{pink}{四国めたん:} はい、配列のサイズは"kMessageSize"定数で指定していますわ
\footnotesize \textcolor{lime}{ずんだもん:} C言語とは違ってconst
キーワードを使用した定数での配列のサイズ指定が許されているのだ
配列のメモリを開放しましょう
\footnotesize \textcolor{pink}{四国めたん:} デストラクタ 内では、 コンストラクタ 内でnew[]
により確保したchar
型の配列をdelete[]
で開放していますわ
\footnotesize \textcolor{pink}{四国めたん:} new
とdelete
、new[]
とdelete[]
は対になっていますので、間違わないようにしましょう
\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ
Circleのインスタンスを生成しましょう
\footnotesize \textcolor{pink}{四国めたん:} 次にメイン関数の最初でCircle
クラスのインスタンスをnew
を使って生成していますわ
\footnotesize \textcolor{lime}{ずんだもん:} 単体のインスタンスなので引数を指定することができるのだ
\footnotesize \textcolor{pink}{四国めたん:} はい、インスタンス生成時に呼び出されるコンストラクタは引数に応じたものになりますわ
\footnotesize \textcolor{lime}{ずんだもん:} 今回はnew Circle(10.0, 1.0);
としているから、整数型の引数が2つのCircle(int diameter, int border_width)
と云うコンストラクタが呼ばれているのだ
\footnotesize \textcolor{pink}{四国めたん:} その通りですわ
メソッドはアロー演算子を使います
\footnotesize \textcolor{pink}{四国めたん:} 生成されたCircle
クラスのインスタンスはポインタ"pc"に代入されますわ
\footnotesize \textcolor{lime}{ずんだもん:} 通常のポインタと使い方は一緒なのか?
\footnotesize \textcolor{pink}{四国めたん:} はい、ポインタ"pc"を使ってCircle
クラスのメンバ変数だけではなく、Diameter()
やArea()
などのメソッドにもアクセスすることも可能ですわ
\footnotesize \textcolor{lime}{ずんだもん:} どのようにアクセスするのだ?
\footnotesize \textcolor{pink}{四国めたん:} 構造体のポインタからメンバにアクセスする場合と同様にアロー演算子"->"を使用することができますわ
\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ
\footnotesize \textcolor{pink}{四国めたん:} なお、pc->Diameter()
の代わりにドット演算子"."を使って(*pc).Diameter()
とすることもできますわ
\footnotesize \textcolor{lime}{ずんだもん:} なんか面倒なのだ
\footnotesize \textcolor{pink}{四国めたん:} まぁ、あまり一般的ではありませんわね
Circleのインスタンスを開放しましょう
\footnotesize \textcolor{pink}{四国めたん:} Circle
クラスのインスタンスを使い終わったらdelete
によりメモリの開放を行いますわ
\footnotesize \textcolor{lime}{ずんだもん:} この時点で デストラクタ が呼ばれるのだ
\footnotesize \textcolor{pink}{四国めたん:} はい、 デストラクタ 中で内部の配列pmessage
も同時に開放しますわ
\footnotesize \textcolor{lime}{ずんだもん:} 忘れないようにするのだ
まとめ
\footnotesize \textcolor{pink}{四国めたん:} お疲れさまでした
\footnotesize \textcolor{lime}{ずんだもん:} おつかれさまなのだ
\footnotesize \textcolor{pink}{四国めたん:} 以上で 動的メモリ確保 の説明を終わりますわ
Discussion