🖥️

【C++言語入門】 第13回 参照の引数

2025/03/08に公開

https://youtu.be/rIsJKEqfGpk

四国めたん
\textcolor{pink}{四国めたん: }教師役ですわ

ずんだもん
\textcolor{lime}{ずんだもん: }生徒役なのだ

\footnotesize \textcolor{pink}{四国めたん:} こんにちは。四国めたんです

\footnotesize \textcolor{lime}{ずんだもん:} ずんだもんなのだ。こんにちはなのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回は前回に続いて 参照 についてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} 前回はいい感じのタイミングで終わってしまったのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、今回は 参照 を関数の引数や戻り値に使う場合についてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} よろしくお願いするのだ

参照を関数の引数に

\footnotesize \textcolor{pink}{四国めたん:} 前回は同じ関数内で 参照 を宣言して使っていましたわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかに...

\footnotesize \textcolor{pink}{四国めたん:} ただ、 参照 はアリアスですので、同じ関数内で使う必要性はほとんどありませんわ

\footnotesize \textcolor{lime}{ずんだもん:} どうしてなのだ?

\footnotesize \textcolor{pink}{四国めたん:} 同じ関数内であれば、 参照 ではなく、元の変数をそのまま使えば良いのですから...

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

\footnotesize \textcolor{pink}{四国めたん:} 実は 参照 が特に威力を発揮するのは、関数の引数に使った時ですわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、関数の引数として 参照 を指定すると、引数にはコピーではなく、本体そのものが渡されますわ

\footnotesize \textcolor{lime}{ずんだもん:} 引数として渡されたインスタンスに、引数名というアリアスを与えるような感じなのか...

\footnotesize \textcolor{pink}{四国めたん:} はい、ただポインタの引数と異なるのは、絶対にnullptrが渡されることはないということですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、必ず本体が渡されることが保証されるので、取り扱いが容易になるのだ

\footnotesize \textcolor{pink}{四国めたん:} それでは 参照 を引数にした関数を使ったプログラムを見てみましょう

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>

const double kPI = 3.14159265358979323846;

/// @brief 円
class Circle {
  double diameter_;      // 直径

 public:
  Circle(double diameter) : diameter_(diameter) {}
  ~Circle() {};

  double Diameter() { return diameter_; }
  void Diameter(double diameter) { diameter_ = diameter; }

  double Area() {
    double radius = Diameter() / 2.0;
    double a = radius * radius * kPI;
    return a;
  }
};

#endif  // CIRCLE_H
hello_world.cpp
#include <iostream>
#include "circle.h"

void Display(Circle& c) {
  std::cout << "円の面積は" << c.Area() << "cm^2です" << std::endl;
  return;
}

int main(int argc, char* argv[]) {
  Circle c(10.0);
  Display(c);
  return 0;
}

参照の引数

\footnotesize \textcolor{lime}{ずんだもん:} 関数Displayの引数にはCircleの参照を指定しているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、関数内では 参照 を通してCircleAreaメソッドにアクセスできていますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかに

変更しない引数

参照 で渡したインスタンスを関数内で変えられたくない場合には、引数にconst修飾子を付加します

hello_world.cpp
#include <iostream>
#include "circle.h"

void Display(const Circle& c) {
  std::cout << "円の面積は" << c.Area() << "cm^2です" << std::endl;
  return;
}

int main(int argc, char* argv[]) {
  Circle c(10.0);
  Display(c);
  return 0;
}

ただ、単に引数にconstを付加しただけではc.Area()の部分でエラーとなってしまいます

これは、CircleクラスのAreaメソッドが、メンバ変数などを変更しないことを保証していないからです

回避するためには以下のように、メンバ変数などを変更しないことを保証するconstキーワードを指定します

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>

const double kPI = 3.14159265358979323846;

/// @brief 円
class Circle {
  double diameter_;      // 直径

 public:
  Circle(double diameter) : diameter_(diameter) {}
  ~Circle() {};

  double Diameter() const { return diameter_; }
  void Diameter(double diameter) { diameter_ = diameter; }

  double Area() const {
    double radius = Diameter() / 2.0;
    double a = radius * radius * kPI;
    return a;
  }
};

#endif  // CIRCLE_H

参照による戻り値は...

\footnotesize \textcolor{pink}{四国めたん:} 以前、関数内で宣言した変数へのポインタを関数の戻り値とすることはできないことはお話ししましたわ

\footnotesize \textcolor{lime}{ずんだもん:} おぼえているのだ

\footnotesize \textcolor{pink}{四国めたん:} これは関数内で宣言した変数は、関数を抜けると無効になってしまうからですわ

\footnotesize \textcolor{lime}{ずんだもん:} そうだったのだ

\footnotesize \textcolor{pink}{四国めたん:} そのため、関数の戻り値としてポインタを使う場合には、引数として与えられたポインタや外部変数などのポインタである必要がありますわ

\footnotesize \textcolor{lime}{ずんだもん:} もしくはmallocなどで動的に確保されたメモリ領域などでもOKなのだ

\footnotesize \textcolor{pink}{四国めたん:} この辺りはC言語もC++言語も同じですわ

\footnotesize \textcolor{lime}{ずんだもん:} それがどうかしたのか?

\footnotesize \textcolor{pink}{四国めたん:} 実は 参照 についても同様の制限があるのですわ

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

\footnotesize \textcolor{pink}{四国めたん:} とりあえず 参照 を戻り値にした関数を作成してみましょう

hello_world.cpp
#include <iostream>
#include "circle.h"

Circle& MakeCircle() {
  Circle c(10.0);
  return c;
}

int main(int argc, char* argv[]) {
  Circle& c = MakeCircle();
  std::cout << "Circleクラスのインスタンスを作成しました" << std::endl;
  std::cout << "円の面積は" << c.Area() << "cm^2です" << std::endl;
  return 0;
}

参照返しエラー

\footnotesize \textcolor{lime}{ずんだもん:} なんか例外が発生しているのだ

\footnotesize \textcolor{pink}{四国めたん:} そうですわね

\footnotesize \textcolor{pink}{四国めたん:} とりあえずMakeCircle内で宣言、生成したCircleのインスタンスを 参照 で返していますわ

\footnotesize \textcolor{lime}{ずんだもん:} MakeCircle関数を抜けたらCircleのインスタンス"c"は無効になるのでは?

\footnotesize \textcolor{pink}{四国めたん:} そのとおりですわ

\footnotesize \textcolor{pink}{四国めたん:} なので、メイン関数でAreaメソッドをコールした際に例外が発生しているのですわ

\footnotesize \textcolor{lime}{ずんだもん:} 回避する方法はないのか?

\footnotesize \textcolor{pink}{四国めたん:} それではnew演算子を使って動的にメモリを確保してみましょう

hello_world.cpp
#include <iostream>
#include "circle.h"

Circle& MakeCircle() {
  Circle* pc = new Circle(10.0);
  return *pc;
}

int main(int argc, char* argv[]) {
  Circle& c = MakeCircle();
  std::cout << "Circleクラスのインスタンスを作成しました" << std::endl;
  std::cout << "円の面積は" << c.Area() << "cm^2です" << std::endl;
  delete &c;
  return 0;
}

参照返し

\footnotesize \textcolor{lime}{ずんだもん:} こんどはちゃんとした値が返っているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、とりあえずMakeCircle関数内ではnew演算子を使ってインスタンスを生成していますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかに...

\footnotesize \textcolor{pink}{四国めたん:} new演算子を使って生成したインスタンスは、関数を抜けたとしても残りますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしか、delete演算子で開放するまでは残るのだ

\footnotesize \textcolor{pink}{四国めたん:} そして生成したインスタンスはCircle参照 として関数の戻り値としていますわ

\footnotesize \textcolor{lime}{ずんだもん:} メイン関数内では、 参照 を通してAreaメソッドに正常にアクセスできているのだ

\footnotesize \textcolor{pink}{四国めたん:} なお 参照 はエイリアスなので、アドレス取得演算子"&"によりポインタとしてdelete演算子に渡すことができますわ

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

配列の参照とポインタの参照

\footnotesize \textcolor{lime}{ずんだもん:} ところで、 配列の参照ポインタの参照 は可能なのか?

\footnotesize \textcolor{pink}{四国めたん:} あまり使い道を思いつかないのですが、理論上は可能ですわ

\footnotesize \textcolor{lime}{ずんだもん:} どのようにするのだ?

\footnotesize \textcolor{pink}{四国めたん:} 配列の参照 はこのようにしますわ

型 (&参照名)[サイズ] = 配列名;

\footnotesize \textcolor{lime}{ずんだもん:} 参照名にアンパサンド"&"を付加して括弧"()"で括るのか...

\footnotesize \textcolor{pink}{四国めたん:} はい、そして配列のサイズは元の配列のサイズと同じでなければなりませんわ

\footnotesize \textcolor{lime}{ずんだもん:} 結構、面倒なのだ

\footnotesize \textcolor{pink}{四国めたん:} 次に ポインタの参照 はこのようになりますわ

型*& 参照名 = ポインタ;

\footnotesize \textcolor{lime}{ずんだもん:} 参照名にアスタリスク"*"とアンパサンド"&"を付加するだけなのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、ただし順番を入れ替えてはいけませんわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} とりあえずプログラムを書いて見ましょう

#include "circle.h"

int main(int argc, char* argv[]) {
  int a[3] = {0, 1, 2};
  int(&a0)[3] = a;
  Circle* pc = new Circle(10.0);
  Circle*& pc0 = pc;
  delete pc;
  pc = nullptr;
  return 0;
}

\footnotesize \textcolor{lime}{ずんだもん:} なんか複雑でわかりにくいのだ

\footnotesize \textcolor{pink}{四国めたん:} まぁ、特に使うメリットはありませんわね

まとめ

\footnotesize \textcolor{pink}{四国めたん:} お疲れさまでした。

\footnotesize \textcolor{lime}{ずんだもん:} おつかれさまなのだ

\footnotesize \textcolor{pink}{四国めたん:} 以上で 参照の引数 を終わりますわ

Discussion