オペレーター
■概要
オペレーターとは演算子のことです。
●書式
書式例:
戻り値の型 operator 演算子(引数)
{
return 戻り値;
}
具体例:
class Test
{
public:
Test() : m_Val(100)
{}
int operator + (int val)
{
return (this->m_Val + val) * 100;
}
private:
int m_Val;
};
void main(void)
{
Test t;
int num = t + 100;
printf("%d\n", num);
while(1);
}
結果:
20000
オペレータの書式の内容:
戻り値の型:
オペレータの処理結果の戻り値の型を指定します。
演算子:
ほとんどの演算子を使用することが可能ですが、
「.」「::」「?」「*」はオーバーロードができないようになっています。
引数:
引数に設定される内容は演算子の右辺にあたる値の型です。
具体例では使用しているオーバーロードは
クラス変数「t」に対して「100」を足しているので
オペレータの引数にはint型の値100というデータが渡されます。
this:
左辺値のデータはオペレータを宣言しているクラスのインスタンスです。
なので、thisポインタ、または直接メンバ変数を指定することで
メンバ変数にアクセスすることができます。
thisポインタ:
クラスのメンバ関数で使用できるポインタ変数で
そのクラスのインスタンス自身を指しています。
return + 戻り値:
戻り値の型をvoid以外に設定した場合、return文を使用して
何らかの値を戻す必要があります。
●operatorの本質
operatorの本質は特殊な形式のメンバ関数です。
オーバーロードされた演算子というのは通常の演算子を
コンパイラがoperatorというメンバ関数を呼び出して変換しています。
■代入演算子のオーバーロード
代入演算子の演算子は「=」で数は少ないですが、
オブジェクトの代入による問題があり、状況によっては定義することが多いオーバーロードです。
●書式
書式例:
戻り値の型 operator=(引数);
具体例:
Character &operator=(Character &ch)
戻り値の型:
Character & => Characterクラスの参照
引数:
Character &ch => 代入の右辺のデータ型
呼び出し例:
Character ch1;
Character ch2;
ch1 = ch2; // この時に「operator=」が呼び出される
●コピーコンストラクタと代入演算子のオーバーロード
代入演算子のオーバーロードは全ての「=」で呼び出されるわけではありません。
一部の「=」では暗黙的にコピーコンストラクタが呼び出されます。
例:
class Character
{
public:
enum JOB
{
FREETER, // 無職
WARRIOR, // 戦士
WIZARD, // 魔法使い
PRIEST, // 僧侶
};
Character()
{
printf("通常のコンストラクタ\n");
m_Job = FREETER;
}
Character(Character &ch)
{
printf("コピーコンストラクタ\n");
this->m_Job = ch.GetJob();
}
~Character()
{
}
Character &operator=(Character &ch)
{
printf("代入演算子のオーバーロード\n");
this->m_Job = ch.GetJob();
return *this;
}
JOB GetJob()
{
return m_Job;
}
private:
JOB m_Job; // 職業
};
void main(void)
{
Character ch1;
Character ch2 = ch1;
getchar();
}
出力結果:
通常のコンストラクタ
コピーコンストラクタ
上記のように宣言時の初期化では代入演算子のオーバーロードではなく
コピーコンストラクタが呼び出されるので注意して下さい。
※配列の宣言時の初期化でもコピーコンストラクタが呼び出されます。
●オブジェクトの問題
オブジェクトの代入はメンバの変数の内容によって問題が発生することがあり、
その問題はコピーコンストラクタと代入演算子のオーバーロードで解決できます。
以下のコードが問題のコードです。
class Character
{
public:
enum JOB
{
FREETER, // 無職
WARRIOR, // 戦士
WIZARD, // 魔法使い
PRIEST, // 僧侶
};
Character()
{
m_Name = NULL;
m_IsLeader = false;
m_Job = FREETER;
}
Character(char *name, JOB job, bool is_leader)
{
m_Name = new char[64];
strcpy_s(m_Name, 64, name);
m_IsLeader = is_leader;
}
~Character()
{
if (m_Name != NULL)
{
delete[] m_Name;
m_Name = NULL;
}
}
bool IsLeader()
{
return m_IsLeader;
}
void ShowName()
{
printf("%s\n", m_Name);
}
private:
char *m_Name; // 名前
JOB m_Job; // 職業
bool m_IsLeader; // リーダー識別
};
void main(void)
{
Character gauri("ガウリイ", Character::JOB::WARRIOR, false);
Character amelia("アメリア", Character::JOB::PRIEST, false);
Character rina("リナ", Character::JOB::WIZARD, true);
Character party[] = { gauri, amelia, rina };
for (int i = 0; i < 3; i++)
{
party[i].ShowName();
}
if (party[0].IsLeader() == false)
{
Character tmp;
for (int i = 1; i < 3; i++)
{
// リーダーを先頭にする
if (party[i].IsLeader() == true)
{
tmp = party[i];
party[i] = party[0];
party[0] = tmp;
}
}
}
for (int i = 0; i < 3; i++)
{
party[i].ShowName();
}
}
結果:
ガウリイ
アメリア
リナ
謎の文字列
アメリア
ガウリイ
エラー
●問題の原因
問題の原因は動的に確保してデストラクタで解放しているメンバ変数があり、
そのメンバを持つクラスを別のクラス変数に代入しているからです。
代入をすることで、動的確保されたメンバ変数の値であるアドレスがコピーされて
代入先の変数に渡されます。
これで以下の図のように動的に確保したアドレスを二つのクラス変数が
共有することになります。
どちらかのクラス変数が寿命を迎えた場合、デストラクタが実行され
動的確保したメンバ変数が解放されます。
そうするとアドレスを共有している残ったクラス変数のメンバ変数も
解放されたことになり、それ以降でメンバ変数に対してアクセスを行うと
解放された領域にアクセスすることになるのでエラーや
こちらが予定している挙動とは異なる挙動になります。
「●オブジェクトの代入の問題」のコード例では
クラス変数の代入を行っている箇所が2箇所あり、
それは「①配列への代入」と「②配列の順番入れ替え時の代入」です。
①配列への代入
Character party[] = { gauri, amelia, rina };
②配列の順番入れ替え時の代入
Character ch = party[i];
party[i] = party[0];
party[0] = ch;
この2箇所でpartyとchに対してCharacterクラス変数の
「gauri」「amelia」「rina」の値を代入しているので、
動的確保したメンバ変数のアドレスの共有が発生しています。
①はコピーコンストラクタ、②は代入演算子のオーバーロードで解決できます。
●コピーコンストラクタによる解決
①はコピーコンストラクタを使用することで解決します。
解決コード:
class Character
{
public:
enum JOB
{
FREETER, // 無職
WARRIOR, // 戦士
WIZARD, // 魔法使い
PRIEST, // 僧侶
};
Character()
{
m_Name = NULL;
m_IsLeader = false;
m_Job = FREETER;
}
Character(char *name, JOB job, bool is_leader)
{
m_Name = new char[64];
strcpy_s(m_Name, 64, name);
m_IsLeader = is_leader;
m_Job = job;
}
// コピーコンストラクタ追加
Character(Character &ch)
{
printf("コピーコンストラクタ\n");
this->m_Name = new char[64];
strcpy_s(m_Name, 64, ch.GetName());
this->m_IsLeader = ch.IsLeader();
this->m_Job = ch.GetJob();
}
~Character()
{
if (m_Name != NULL)
{
delete[] m_Name;
m_Name = NULL;
}
}
char *GetName()
{
return m_Name;
}
bool IsLeader()
{
return m_IsLeader;
}
JOB GetJob()
{
return m_Job;
}
void ShowName()
{
printf("%s\n", m_Name);
}
private:
char *m_Name; // 名前
JOB m_Job; // 職業
bool m_IsLeader; // リーダー識別
};
void main(void)
{
Character gauri("ガウリイ", Character::JOB::WARRIOR, false);
Character amelia("アメリア", Character::JOB::PRIEST, false);
Character rina("リナ", Character::JOB::WIZARD, true);
Character party[] = { gauri, amelia, rina };
Character ch;
ch = rina;
for (int i = 0; i < 3; i++)
{
party[i].ShowName();
}
if (party[0].IsLeader() == false)
{
Character tmp;
for (int i = 1; i < 3; i++)
{
// リーダーを先頭にする
if (party[i].IsLeader() == true)
{
tmp = party[i];
party[i] = party[0];
party[0] = tmp;
}
}
}
for (int i = 0; i < 3; i++)
{
party[i].ShowName();
}
}
出力結果:
コピーコンストラクタ
コピーコンストラクタ
コピーコンストラクタ
ガウリイ
アメリア
リナ
謎の文字列
アメリア
ガウリイ
エラーで停止
上記のコードでコピーコンストラクタが呼び出されることになったので、
①の問題は解決されました。
●代入演算子による問題の解決
②の問題は代入演算子によるオーバーロードで解決します。
オーバーロードを行い代入を行う際にアドレスの共有が起きないようにします。
解決コード
class Character
{
public:
enum JOB
{
FREETER, // 無職
WARRIOR, // 戦士
WIZARD, // 魔法使い
PRIEST, // 僧侶
};
Character()
{
m_Name = NULL;
m_IsLeader = false;
m_Job = FREETER;
}
Character(char *name, JOB job, bool is_leader)
{
m_Name = new char[64];
strcpy_s(m_Name, 64, name);
m_IsLeader = is_leader;
}
Character(Character &ch)
{
printf("コピーコンストラクタ\n");
this->m_Name = new char[64];
strcpy_s(m_Name, 64, ch.GetName());
this->m_IsLeader = ch.IsLeader();
this->m_Job = ch.GetJob();
}
~Character()
{
if (m_Name != NULL)
{
delete[] m_Name;
m_Name = NULL;
}
}
// 演算子のオーバーロード追加
Character &operator=(Character &ch)
{
printf("代入演算子のオーバーロード\n");
this->m_Name = new char[64];
strcpy_s(m_Name, 64, ch.GetName());
this->m_IsLeader = ch.IsLeader();
this->m_Job = ch.GetJob();
return *this;
}
char *GetName()
{
return m_Name;
}
bool IsLeader()
{
return m_IsLeader;
}
JOB GetJob()
{
return m_Job;
}
void ShowName()
{
printf("%s\n", m_Name);
}
private:
char *m_Name; // 名前
JOB m_Job; // 職業
bool m_IsLeader; // リーダー識別
};
void main(void)
{
Character gauri("ガウリイ", Character::JOB::WARRIOR, false);
Character amelia("アメリア", Character::JOB::PRIEST, false);
Character rina("リナ", Character::JOB::WIZARD, true);
Character party[] = { gauri, amelia, rina };
Character ch;
ch = rina;
for (int i = 0; i < 3; i++)
{
party[i].ShowName();
}
if (party[0].IsLeader() == false)
{
Character tmp;
for (int i = 1; i < 3; i++)
{
// リーダーを先頭にする
if (party[i].IsLeader() == true)
{
tmp = party[i];
party[i] = party[0];
party[0] = tmp;
}
}
}
for (int i = 0; i < 3; i++)
{
party[i].ShowName();
}
}
出力結果:
コピーコンストラクタ
コピーコンストラクタ
コピーコンストラクタ
ガウリイ
アメリア
リナ
代入演算子のオーバーロード
代入演算子のオーバーロード
代入演算子のオーバーロード
リナ
アメリア
ガウリイ
これで①と②の問題が解決されたので、問題なく動作するようになります。
●まとめ
オブジェクトの代入の問題で代入演算子のオーバーロードが必要となりますが、
その際はコピーコンストラクタによる呼び出しが行われる可能性も忘れないように
注意して下さい。
■算術演算子のオーバーロード
算術演算子は「+」「-」「*」「/」「%」などですが、
代入演算子を使用した「+=」「-=」などもあります。
●書式
書式例:
戻り値の型 operator 算術演算子 (引数);
具体例:
Vector &operator += (Vector &ch)
戻り値の型:
Vector & => Vectorクラスの参照
引数:
Vector &ch => 代入の右辺のデータ型
呼び出し例:
Vector v1 = Vector(-10, -20, -30);
Vector v2 = Vector(10, 20, 30);
v1 += v2;
●サンプルコード
class Vector
{
public:
Vector() :
m_PosX(0.0f),
m_PosY(0.0f),
m_PosZ(0.0f)
{}
Vector(float x, float y, float z) :
m_PosX(x),
m_PosY(y),
m_PosZ(z)
{}
float GetPosX() { return m_PosX; }
float GetPosY() { return m_PosY; }
float GetPosZ() { return m_PosZ; }
Vector& operator += (Vector &vec)
{
printf("算術演算子のオーバーロード\n");
this->m_PosX += vec.GetPosX();
this->m_PosY += vec.GetPosY();
this->m_PosZ += vec.GetPosZ();
return *this;
}
private:
float m_PosX;
float m_PosY;
float m_PosZ;
};
void main(void)
{
Vector v1(-10.0f, -20.0f, -30.0f);
Vector v2(10.0f, 20.0f, 30.0f);
v1 += v2;
printf("PosX = %f\n", v1.GetPosX());
printf("PosY = %f\n", v1.GetPosY());
printf("PosZ = %f\n", v1.GetPosZ());
}
出力結果:
PosX = 0.0
PosY = 0.0
PosZ = 0.0
●算術演算子の利点
算術演算子の利点はメンバに対して同じ計算を行いたい場合に
使用者側がコーディングを短縮できることと、直感的に使いやすいところです。
例えば先ほどのVectorクラスでm_PosX、m_PosY、m_PosZに加算を行うとしたら
以下のように加算関数を作成する必要があります
例:
class Vector
{
public:
Vector() :
m_PosX(0.0f),
m_PosY(0.0f),
m_PosZ(0.0f)
{}
Vector(float x, float y, float z) :
m_PosX(x),
m_PosY(y),
m_PosZ(z)
{}
float GetPosX() { return m_PosX; }
float GetPosY() { return m_PosY; }
float GetPosZ() { return m_PosZ; }
void AddPosX(float x)
{
m_PosX += x;
}
void AddPosY(flaot y)
{
m_PosY += y;
}
void AddPosZ(float z)
{
m_PosZ += z;
}
void AddPos(float x, float y, float z)
{
m_PosX += x;
m_PosY += y;
m_PosZ += z;
}
private:
float m_PosX;
float m_PosY;
float m_PosZ;
};
void main(void)
{
Vector v1(-10.0f, -20.0f, -30.0f);
Vector v2(10.0f, 20.0f, 30.0f);
v1.AddPos(v2.GetPosX(), v2.GetPosX(), v2.GetPosZ());
printf("PosX = %f\n", v1.GetPosX());
printf("PosY = %f\n", v1.GetPosY());
printf("PosZ = %f\n", v1.GetPosZ());
}
出力結果:
PosX = 0.0
PosY = 0.0
PosZ = 0.0
AddPos関数を使用するよりもオペレータを使用したほうが
コードも少なく、直感的で分かりやすくなっていると思います。
■比較演算子のオーバーロード
比較演算子は「==」「<」「>」「!=」などがあります。
●書式
書式例:
戻り値の型 operator 比較演算子 (引数);
具体例:
bool operator > (Jugyoin &ch)
戻り値の型:
Jugyoin & => Jugyoinクラスの参照
引数:
Jugyoin &ch => 代入の右辺のデータ型
呼び出し例:
Jugyoin j1 = Jugyoin(Jugyoin::POST::STAFF);
Jugyoin j2 = Jugyoin(Jugyoin::POST::);
if (j1 > j2)
{
printf("j1の方が偉い\n");
}
●サンプルコード
class Jugyoin
{
public:
enum POST
{
STAFF, // 平社員
SECTION_CHIEF, // 課長
MANAGER, // 部長
PRESIDENT, // 社長
};
Jugyoin(POST post)
{
m_Post = post;
}
bool operator > (Jugyoin &jugyoin)
{
if (m_Post > jugyoin.GetPost())
{
return true;
}
return false;
}
void ShowPost(void);
POST GetPost() { return m_Post; }
private:
POST m_Post;
};
void Jugyoin::ShowPost()
{
switch (m_Post)
{
case POST::STAFF:
printf("社員\n");
break;
case POST::SECTION_CHIEF:
printf("課長\n");
break;
case POST::MANAGER:
printf("部長\n");
break;
case POST::PRESIDENT:
printf("社長\n");
break;
}
}
void main(void)
{
Jugyoin satou(Jugyoin::POST::STAFF);
Jugyoin suzuki(Jugyoin::POST::MANAGER);
satou.ShowPost();
suzuki.ShowPost();
if (satou > suzuki)
{
printf("satouの方が偉い");
} else {
printf("suzukiの方が偉いかも");
}
}
●注意点
比較演算子をオーバーロードする場合の注意点として
直感的に比較する内容が分かるものを作成する必要があります。
そうでないと何と何を比較して結果を返しているのかが分かりづらいです。