🖥️

【C++言語入門】 第5回 動的メモリ配置

2025/02/19に公開

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++言語ではmallocfreeよりもnewdeleteを使うのが一般的ですわね

\footnotesize \textcolor{lime}{ずんだもん:} newdelete

\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言語と同様に、動的にメモリを割り当てるための関数mallocfreeを使うことができますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} ですが、C++言語では動的にメモリを割り当てるための演算子newが追加されましたわ

\footnotesize \textcolor{lime}{ずんだもん:} 関数ではなく演算子になったのか?!

\footnotesize \textcolor{pink}{四国めたん:} はい、new演算子の使い方は簡単で、このようにしますわ

new 型;

\footnotesize \textcolor{lime}{ずんだもん:} たしかに簡単なのだ

\footnotesize \textcolor{lime}{ずんだもん:} でもmalloc関数でも問題ないのではないか?

\footnotesize \textcolor{pink}{四国めたん:} newmallocよりも優れている点はいくつかありますわ

  • 確保するメモリのサイズを計算する必要がない
  • 戻り値は指定した型へのポインタなので、キャストする必要がない
  • コンストラクタが呼ばれるので、自動的に初期化される

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、たしかに型そのものを指定するので、sizeofなどでサイズを計算する必要がないのだ

\footnotesize \textcolor{pink}{四国めたん:} 特に最後の「コンストラクタが呼ばれる」のがnewの最大の特徴ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 引数のある コンストラクタ を呼ぶことはできるのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、引数のある コンストラクタ を呼ぶようにする場合には、型の後ろに括弧で引数を指定しますわ

new 型(引数1, 引数2, ...);

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに括弧が無かったり引数の指定がなければ、 デフォルトのコンストラクタ が呼ばれますわ

配列のメモリ確保new[]

\footnotesize \textcolor{lime}{ずんだもん:} ところでmallocでは配列の割り当てもできたが、newでも可能なのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、配列のメモリ確保はnew[]演算子で可能ですわ

\footnotesize \textcolor{pink}{四国めたん:} 形式としてはこんな感じですわね

new 型[size];

\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}{四国めたん:} そして配列の各要素に引数を指定して初期値を与えたい場合には、初期化のためのメソッド、例えばInitInitializeなどのメソッドが必要ですわね

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

動的メモリの開放はdeleteで

\footnotesize \textcolor{pink}{四国めたん:} 次に、new演算子で動的に割り当てられたメモリ領域を開放するための方法をお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} free関数のようなものか?

\footnotesize \textcolor{pink}{四国めたん:} はい、new演算子で動的に割り当てられたメモリ領域を開放する場合には、free関数ではなく、新たに追加された演算子deleteを使いますわ

\footnotesize \textcolor{lime}{ずんだもん:} どんな形式なのだ?

\footnotesize \textcolor{pink}{四国めたん:} こんな感じですわね

delete インスタンスへのポインタ;

\footnotesize \textcolor{lime}{ずんだもん:} 指定するのは インスタンスへのポインタ なのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、new演算子は **インスタンスへのポインタ** を返すので、delete`演算子には インスタンスへのポインタ を指定しますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} なお、deletenullptrを指定しても何もしないので、deleteの前に インスタンスへのポインタnullptrかどうかをチェックする必要はありませんわ

\footnotesize \textcolor{lime}{ずんだもん:} nullptrとは何なのだ?

\footnotesize \textcolor{pink}{四国めたん:} nullptrはC++11から導入されたキーワードで、NULLと概ね同じですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、わざわざ追加してくれたキーワードなので積極的に使っていくのだ

nullptrとNULL

new[]で確保された配列を開放しましょう

\footnotesize \textcolor{lime}{ずんだもん:} ところでnew[]演算子で確保されたメモリ領域を開放する場合もdelete演算子でOKなのか?

\footnotesize \textcolor{pink}{四国めたん:} new[]演算子を使って確保された動的メモリ領域の開放はdeleteではなく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}{四国めたん:} それでは、newdeleteを使って、前回のプログラム例を書き換えてみましょう

\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;
}

new. deleteを使った結果

\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}{四国めたん:} newdeletenew[]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