【C++言語入門】 第22回 クラステンプレート
クラステンプレート
Circle
と云うクラスを使ってプログラム例を作成していましたわ
#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) {
}
virtual ~Circle() {
}
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;
}
};
#endif // CIRCLE_H
double
で処理していますわ
int
などの他の数値型での処理をおこなうクラスを追加することを考えてみますわ
double
としている部分をint
などに変える必要がありますわ
template <class テンプレート引数1, 型 テンプレート引数2, ...>
class クラス名 {
メンバーなど
}
template
キーワードに続けて山括弧"<>"内に テンプレート引数 をリストアップしているのだ
class
キーワードを付加したものを 型引数 と言いますわ
class
キーワードの代わりにtypename
キーワードを付加することも増えていますわ
クラス名<実際の型, ..., 実際の値, ...> インスタンス名(コンストラクタの引数...); // (1)
クラス名<実際の型, ..., 実際の値, ...>* ポインタ名 = new クラス名<実際の型, ..., 実際の値, ...>(コンストラクタの引数...); // (2)
new
により作成する場合なのだ
new
の後のクラス名に続けて山括弧"<>"内に 実際の型 や 実際の値 をリストアップしますわ
クラステンプレートを使ったCircleクラス
Circle
クラスを テンプレート を使って書き換えてみましょう
#pragma once
#ifndef CIRCLE_H
#define CIRCLE_H
#include <iostream>
const double kPI = 3.14159265358979323846;
/// @brief 円
template <class T = double, int init = 10>
class Circle {
T diameter_; // 直径
public:
Circle(T diameter = init) : diameter_(diameter) {}
virtual ~Circle() {}
T Diameter() const { return diameter_; }
void Diameter(T diameter) { diameter_ = diameter; }
virtual T Area() const {
T radius = Diameter() / 2;
T a = radius * radius * kPI;
return a;
}
};
#endif // CIRCLE_H
= デフォルト値
としてデフォルトの値を指定していますわね
double
としていた部分を"T"で置き換えていますわ
クラステンプレートを使ったクラスオブジェクトの生成
Circle
クラスのインスタンスを作って使ってみましょう
#include <iostream>
#include "circle.h"
int main(int argc, char* argv[]) {
Circle<> c(5.0);
Circle<double, 3> d;
Circle<int>* pc = new Circle<int>;
std::cout << "Circle<> c(5.0);の円の面積は" << c.Area() << "です。"
<< std::endl;
std::cout << "Circle<double, 3.0> d;の円の面積は" << d.Area() << "です。"
<< std::endl;
std::cout << "Circle<int>* pc = new Circle<int>;の円の面積は" << pc->Area() << "です。"
<< std::endl;
delete pc;
pc = nullptr;
return 0;
}
Circle<> c(5.0);
では テンプレート引数 の値として両方ともにデフォルトの値を使用していますわ
Circle<double, 3> d;
では テンプレート引数 の値として両方の テンプレート引数 を指定していますわ
Circle<int>* pc = new Circle<int>;
では テンプレート引数 の"T"としてint
を指定していますわ
new
でおこなっているのでポインタとなっていますわ
new
の型指定については、 テンプレート引数 も含めて同じでなければなりませんわ
Circle<int>
の部分なのだ
T a = radius * radius * kPI;
の部分ですわね
double
なのですが、"T"がint
なので、型の不一致に関するワーニングですわね
メソッドに対するテンプレート
template<class テンプレート引数1, ..., 型 テンプレート引数2, ...>
戻り値の型 メソッド名(引数...) { 処理 };
Circle
クラスのArea
メソッドに対して使ってみましょう
#pragma once
#ifndef CIRCLE_H
#define CIRCLE_H
#include <iostream>
const double kPI = 3.14159265358979323846;
/// @brief 円
template <class T = double, int init = 10>
class Circle {
T diameter_; // 直径
public:
Circle(T diameter = init) : diameter_(diameter) {}
virtual ~Circle() {}
T Diameter() const { return diameter_; }
void Diameter(T diameter) { diameter_ = diameter; }
template <class U>
U Area() const {
U radius = Diameter() / 2;
U a = radius * radius * kPI;
return a;
}
};
#endif // CIRCLE_H
#include <iostream>
#include "circle.h"
int main(int argc, char* argv[]) {
Circle<> c(5.0);
Circle<double, 3> d;
Circle<int>* pc = new Circle<int>;
std::cout << "Circle<> c(5.0);の円の面積は" << c.Area<double>() << "です。"
<< std::endl;
std::cout << "Circle<double, 3.0> d;の円の面積は" << d.Area<double>() << "です。"
<< std::endl;
std::cout << "Circle<int>* pc = new Circle<int>;の円の面積は" << pc->Area<double>()
<< "です。" << std::endl;
delete pc;
pc = nullptr;
return 0;
}
Area
メソッドの 戻り値 と変数の型をメソッドの テンプレート引数 に置き換えているのだ
Area
メソッドの呼び出しでは、山括弧"<>"内に 実際の型 を指定していますわ
virtual
を指定していないのだが...
ソースファイルでのメソッドの定義
Circleクラスでは
Area`メソッドの定義をヘッダーファイルのクラス定義内でおこなっていますわ
ソースファイルでのメソッドの定義方法
テンプレート を使った場合にソースファイルでメソッドを定義する場合の形式は以下の通りです
template<class テンプレート引数1, 型 テンプレート引数2...>
戻り値の型 クラス名<テンプレート引数1, テンプレート引数2, ...>::メソッド名(引数...) { 処理 }
まず冒頭に クラステンプレート のtemplate
以下を書きます
次に通常のメソッドと同様に、"戻り値の型"に続けて"クラス名"と"メソッド名"をスコープ演算子"::"を挟んで書きます
なお"クラス名"は山括弧"<>"内に テンプレート引数 を列挙します
その際class
や型
などは付加しないので注意が必要です
ちなみにヘッダーファイルのクラス定義内のメソッドは宣言のみとなります
とりあえずCircle
クラスを書き換えてみます
#pragma once
#ifndef CIRCLE_H
#define CIRCLE_H
#include <iostream>
const double kPI = 3.14159265358979323846;
/// @brief 円
template <class T = double, int init = 10>
class Circle {
T diameter_; // 直径
public:
Circle(T diameter = init) : diameter_(diameter) {}
virtual ~Circle() {}
T Diameter() const { return diameter_; }
void Diameter(T diameter) { diameter_ = diameter; }
T Area() const;
};
#endif // CIRCLE_H
#include "circle.h"
template <class T, int init>
T Circle<T, init>::Area() const {
T radius = Diameter() / 2;
T a = radius * radius * kPI;
return a;
}
#include <iostream>
#include "circle.h"
int main(int argc, char* argv[]) {
Circle<> c(5.0);
Circle<double, 3> d;
Circle<int>* pc = new Circle<int>;
std::cout << "Circle<> c(5.0);の円の面積は" << c.Area() << "です。"
<< std::endl;
std::cout << "Circle<double, 3.0> d;の円の面積は" << d.Area() << "です。"
<< std::endl;
std::cout << "Circle<int>* pc = new Circle<int>;の円の面積は" << pc->Area()
<< "です。" << std::endl;
delete pc;
pc = nullptr;
return 0;
}
実行すると 未解決のシンボル というエラーが出てしまいます
これは"circle.cpp"をコンパイルする際にはmain
関数でどのような形で呼ばれるかが判らないので、 テンプレート から作成されるメソッドが確定しないことが原因です
その結果、 未解決のシンボル と云うエラーとなってしまいます
ですので、 テンプレート を使ったメソッドをソースファイルで定義する場合には、具体的な テンプレート引数 を明示しておく必要があります
明示の方法は以下の通りです
template 型 クラス名<実際の型, ..., 実際の値, ...>::メソッド名(引数...);
"circle.cpp"を書き直してみます
#include "circle.h"
template <class T, int init>
T Circle<T, init>::Area() const {
T radius = Diameter() / 2;
T a = radius * radius * kPI;
return a;
}
template double Circle<double>::Area() const;
template double Circle<double, 3>::Area() const;
template int Circle<int>::Area() const;
メイン関数では3種類のインスタンスを生成してArea
メソッドを呼び出しているので、 テンプレート引数 の明示についても3種類必要です
テンプレート引数 を明示しておくのは意外に面倒なので、 クラステンプレート のメソッドの定義は、ヘッダーファイルのクラス定義内に書くか、少なくともヘッダーファイル内に書く方が得策です
Discussion