🖥️

【C++言語入門】 第18回 修飾子

に公開

https://youtu.be/e47Aa0LX4iw

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

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

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

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

\footnotesize \textcolor{pink}{四国めたん:} 今回は 修飾子 についてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} 修飾子

\footnotesize \textcolor{pink}{四国めたん:} はい、constとかのキーワードのことですわね

\footnotesize \textcolor{lime}{ずんだもん:} よろしくなのだ

const

\footnotesize \textcolor{pink}{四国めたん:} まず最初はconstですわね

\footnotesize \textcolor{lime}{ずんだもん:} 今までも定数を表すのに使ってきたのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、constについては、基本的に内容の変更を行わない、もしくは行えないことを示唆しますわ

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

変数の変更は禁止です

\footnotesize \textcolor{pink}{四国めたん:} constの基本的な使い方は、「変数の内容の変更を禁止する」使い方ですわ

\footnotesize \textcolor{lime}{ずんだもん:} うむ、前回までのプログラム例でのπの指定が、いい例なのだ

const double kPI = 3.14159265358979323846;

\footnotesize \textcolor{pink}{四国めたん:} これは、実際にはdouble型の変数"kPI"に対してconstで値の変更を禁止することで定数の役割としていますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかにその通りなのだ

\footnotesize \textcolor{pink}{四国めたん:} あと、関数の引数にconstを付加するのもこれにあたりますわ

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

\footnotesize \textcolor{pink}{四国めたん:} 引数については、前回のCircleクラスのコピーコンストラクタや代入演算子のオーバーライドが判りやすいと思いますわ

class Circle {
    :
    :
  Circle(const Circle& c) : Circle(c.diameter_) {
    memcpy_s(pmessage_, kMessageSize, c.pmessage_, kMessageSize);
  }
    :
    :
  Circle& operator=(const Circle& c) {
    diameter_ = c.diameter_;
    memcpy_s(pmessage_, kMessageSize, c.pmessage_, kMessageSize);
    return *this;
  }
};

\footnotesize \textcolor{pink}{四国めたん:} 引数"c"を参照で渡していますが、constが付加されているので"c"の内容の変更はできませんわ

ポインタにconst

\footnotesize \textcolor{pink}{四国めたん:} ところで、ポインタの宣言時にconstを付加すると、一般的にはポインタが指すメモリ領域の値の変更が禁止されますわ

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

\footnotesize \textcolor{pink}{四国めたん:} とりあえず例を上げてみますわね

int a = 10;
const int* pa = &a;
*pa = 0; // NG

\footnotesize \textcolor{pink}{四国めたん:} このような場合には*pa = 0;のような代入によって"a"の値を変更することはできませんわ

\footnotesize \textcolor{lime}{ずんだもん:} ふむふむ

\footnotesize \textcolor{pink}{四国めたん:} では次のような場合にはどうなるでしょうか?

int a = 10;
int b = 5;
int* const pa = &a;
*pa = 0; // OK
pa = &b; // NG

\footnotesize \textcolor{lime}{ずんだもん:} constが変数名の前に付加されているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、この場合にはポインタの値の変更が禁止されますわ

\footnotesize \textcolor{lime}{ずんだもん:} つまりpa = &b;のように、ポインタの指す位置を変更することができなくなるのか...

\footnotesize \textcolor{pink}{四国めたん:} はい、逆に*pa = 0;のようにポインタが指すメモリ領域の値の変更はOKとなりますわ

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

メンバ変数の変更は禁止です

\footnotesize \textcolor{pink}{四国めたん:} 次に、クラスのメソッドからメンバ変数の変更を禁止する使い方ですわ

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

\footnotesize \textcolor{pink}{四国めたん:} 使い方はメソッドの宣言や定義の後にconstを付加しますわ

型 メソッド名(引数...) const;

\footnotesize \textcolor{lime}{ずんだもん:} とても簡単なのだ

\footnotesize \textcolor{pink}{四国めたん:} なお、宣言にconstを付加した場合には、定義にもconstを付加する必要がありますわ

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

\footnotesize \textcolor{pink}{四国めたん:} このように、constを付加したメソッドでは、メソッド内でメンバ変数の内容が変わるような処理は禁止となりますわ

\footnotesize \textcolor{lime}{ずんだもん:} 変更するとどうなるのだ?

\footnotesize \textcolor{pink}{四国めたん:} エラーとなりますわね

\footnotesize \textcolor{lime}{ずんだもん:} アクセス指定子がpublic:に指定されているメンバ変数でも変更禁止なのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、変更禁止ですわね

\footnotesize \textcolor{pink}{四国めたん:} ちなみに他のメソッドを呼び出す場合でも、const指定されたメソッド以外は呼び出し禁止となりますわ

\footnotesize \textcolor{lime}{ずんだもん:} 結構、厳しいのだ

\footnotesize \textcolor{pink}{四国めたん:} Circleクラスを例に書き換えてみましょう

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>

const double kPI = 3.14159265358979323846;
const int kMessageSize = 50;

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

 public:
  Circle(double diameter) : diameter_(diameter), pmessage_(nullptr) {
    pmessage_ = new char[kMessageSize];
  }
  virtual ~Circle() {
    delete[] pmessage_;
    pmessage_ = nullptr;
  }

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

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

  virtual const char* Message() const;
};

#endif  // CIRCLE_H
circle.cpp
#include "circle.h"

const char* Circle::Message() const {
  double area = Area();
  sprintf_s(pmessage_, kMessageSize, "円の面積は%fです。", area);
  return pmessage_;
}
hello_world.cpp
#include <iostream>
#include "circle.h"

int main(int argc, char* argv[]) {
  Circle c(10.0);
  std::cout << "円の直径は" << c.Diameter() << "cmです。" << std::endl;
  std::cout << c.Message() << std::endl;
  return 0;
}

constを付加

\footnotesize \textcolor{lime}{ずんだもん:} Messageメソッドにconstが付加されているのだ

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

\footnotesize \textcolor{lime}{ずんだもん:} そして、Messageメソッド内からAreaメソッドやDiameterゲッターを呼び出しているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、どちらもconstを付加しないとエラーとなりますわ

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

\footnotesize \textcolor{pink}{四国めたん:} 試しにconstを取り除いてみましょう

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>

const double kPI = 3.14159265358979323846;
const int kMessageSize = 50;

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

 public:
  Circle(double diameter) : diameter_(diameter), pmessage_(nullptr) {
    pmessage_ = new char[kMessageSize];
  }
  ~Circle() {
    delete[] pmessage_;
    pmessage_ = nullptr;
  }

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

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

  const char* Message() const;
};

#endif  // CIRCLE_H

constエラー

\footnotesize \textcolor{lime}{ずんだもん:} お~、エラーになったのだ

\footnotesize \textcolor{pink}{四国めたん:} メソッドに対してconstを付加しなくても、メソッド自体はエラーとはなりませんし、問題なく動作しますわ

\footnotesize \textcolor{lime}{ずんだもん:} 当然なのだ

\footnotesize \textcolor{pink}{四国めたん:} ただ次のようなメリットがあるので、積極的に使っていってください。

  • コンパイラが最適化をし易くなる
  • メソッドを呼ぶ際にプログラマが副作用を気にせず安心して使えるようになる
  • constを指定したインスタンスや引数から呼び出せるようになる

\footnotesize \textcolor{pink}{四国めたん:} constを付加していないメソッドは、constを指定されたインスタンスや引数から呼び出せませんわ

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

メッセージの変更

Messageメソッド内ではsprintf_spmessage_内の文字列を変更しています

Messageメソッドはconstが指定されているのに、メンバ変数の内容が書き換えられているのは、おかしいと思われるかもしれません

ただMessageメソッド中ではpmessage_の値そのものが変更されていません

pmessage_の指すメモリ領域の内容が変わっているのです

少しややこしくて判りにくいのですが、「constを使っているから変更できない」と油断していると、知らないうちに内容を変更してしまっていることもあるので注意が必要です

noexcept

\footnotesize \textcolor{pink}{四国めたん:} 次はnoexceptですわね

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

\footnotesize \textcolor{pink}{四国めたん:} はい、例外が発生しないことを示すための修飾子ですわ

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

\footnotesize \textcolor{pink}{四国めたん:} 使い方は、関数やメソッドの宣言や定義の後にnoexceptを付加するのですわ

型 関数名(引数...) noexcept { 処理 };

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

\footnotesize \textcolor{pink}{四国めたん:} なお、宣言にnoexceptを付加した場合には、定義にもnoexceptを付加する必要がありますわ

\footnotesize \textcolor{lime}{ずんだもん:} 片方だけにnoexceptを付加したらどうなるのだ?

\footnotesize \textcolor{pink}{四国めたん:} エラーとなりますわね

\footnotesize \textcolor{lime}{ずんだもん:} ちなみにnoexceptを付加すると、どのようなメリットがあるのだ?

\footnotesize \textcolor{pink}{四国めたん:} 関数やメソッドにnoexceptが付加された場合には、関数やメソッドから例外が投げられないことが保証されますわ

\footnotesize \textcolor{lime}{ずんだもん:} それだけなのか...

\footnotesize \textcolor{pink}{四国めたん:} まぁ、例外が投げられないことが保証されれば、try~catchで処理する手間が省けますわね

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

\footnotesize \textcolor{lime}{ずんだもん:} ちなみに、noexceptが付加された関数やメソッドの内部で、例外が発生した場合はどうするのだ?

\footnotesize \textcolor{pink}{四国めたん:} 関数やメソッドの内部でtry~catchを使って例外を捕捉して処理すればいいのですわ

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

\footnotesize \textcolor{pink}{四国めたん:} それではCircleクラスのMessageメソッドにnoexceptを付加してみましょう

circle.h
#pragma once

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>

const double kPI = 3.14159265358979323846;
const int kMessageSize = 50;

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

 public:
  Circle(double diameter) : diameter_(diameter), pmessage_(nullptr) {
    pmessage_ = new char[kMessageSize];
  }
  ~Circle() {
    std::cout << "デストラクタが呼ばれました" << std::endl;
    delete[] pmessage_;
  }

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

  const char* Message() const noexcept;
};

#endif  // CIRCLE_H
circle.cpp
#include "circle.h"

const char* Circle::Message() const noexcept {
  double area = Area();
  sprintf_s(pmessage_, kMessageSize, "円の面積は%fです。", area);
  return pmessage_;
}

noexcept

\footnotesize \textcolor{lime}{ずんだもん:} 定義と宣言の両方にnoexceptを付加しているのだ

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

\footnotesize \textcolor{lime}{ずんだもん:} 今回はconstnoexceptの両方を指定しているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、ちなみにnoexceptconstの前に配置するとエラーになるので注意して下さいね

\footnotesize \textcolor{lime}{ずんだもん:} わかったのだ

throw()

関数やメソッドから投げられる可能性のある例外を指定する修飾子はあるのでしょうか

以前のC++言語の仕様ではthrowキーワードを使って、投げられる可能性のある例外を指定することができました

型 関数名(引数...) throw(例外の型...) { 処理 }

例外の型を列挙することで、どのような例外を処理すれば良いかが一目瞭然でした

ただ、最近のC++言語の仕様では、このような書き方はできなくなり、例外がない場合のみnoexceptを指定するように変更されました

結果として現在は、どのような例外が発生するかはドキュメントを頼る状況になっています

ですので、関数やメソッドに対するコメントの重要性は非常にに高まっています

まとめ

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

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

\footnotesize \textcolor{pink}{四国めたん:} 以上で 修飾子 についてのお話を一旦、終わりますわ

\footnotesize \textcolor{lime}{ずんだもん:} 一旦?

\footnotesize \textcolor{pink}{四国めたん:} はい、次回は続きとしてoverridefinalという 修飾子 についてお話ししますわね

\footnotesize \textcolor{lime}{ずんだもん:} 待っているのだ

Discussion