![]()
![]()
![]()
基本的な内容
LastUpdate : 12/12/29
C++を使うに当たって、私が忘れていたこと・知らなかったことをまとめています。
もくじ
char型とwchar_t型
constの使い方について
スマートポインターについて(shared_ptr)
暗黙の型変換関数
参照渡しについて
キャストの種類
隠蔽について
継承の挙動確認
ひし形継承について
テンプレートについて
一般化されたコピーコンストラクタ
マルチバイト文字と、ワイド文字という概念があります。
マルチバイト文字は、1文字を複数のchar型で表現するもの、ワイド文字とは、1文字をwchar_t型1つで表そうとするもの・・・みたい。
文字コードの問題がそもそも複雑なのに、さらに輪をかけて内部表現についてもぐだぐだしているので、相当面倒な話だったりする。出来ることなら深入りしたくない\(^o^)/
ワイド文字は「L"わんわんお"」や「L'a'」などと記述する。
マルチバイト文字と、ワイド文字を変換する関数が用意されている。以下に例を示す。
| サンプル |
#include <stdlib.h>
void keyword1() {
char* wordChar = "わんわんお!!!";
wchar_t* wordWChar = L"わんわんお!!!";
// 文字の変換には、ロケールが参照されるため、設定しておく
setlocale(LC_CTYPE, "jpn");
// ワイド文字列からマルチバイト文字列に変換
char mbStr[256];
wcstombs(mbStr, wordWChar, 256);
printf("result : %s\n", mbStr);
//マルチバイト文字列をワイド文字列に変換
wchar_t wStr[256];
mbstowcs(wStr, wordChar, 256);
printf("result : %ls\n", wStr);
}
|
| 実行結果 |
result : わんわんお!!! result : わんわんお!!! |
変更してはいけない変数にconstをつけることで、ソース上間違って変更をしていたら、コンパイラがエラーを出してくれます。
なので、便利!
・・・という風潮ですが、何が何でもすべてつける必要はないかなと私は思ってます。
関数の引数にconstをつけるのは良い方法かもしれません。
関数内の自動変数(一時変数?)につけるのは、ちょっと面倒なので、いらんのでは?と考えてます。
ポインタのconstは文法上ちょっと特殊ですので、下の例(1)〜(4)を見るとわかりやすいかと。
メンバ関数にもconstをつけることができます。
つけると、そのメソッドはメンバ変数を変更できない制限を加えることができます。なので、getterメソッドなんかにはちょうど良いです。
constをつけたメンバ関数内で、どうしてもメンバ変数を変更したい場合は、そのメンバ変数にmutableという修飾子をつけます。その例が、Study1MutableクラスのnumMutableメンバ変数に当たります。
| Study1.cpp |
#include <stdio.h>
#include <stdlib.h>
#include <string>
/**
* mutableとconstの使い方.
*/
class Study1Mutable {
public:
int num;
mutable int numMutable;
Study1Mutable() { }
~Study1Mutable() { }
// メソッドにconstをつけると、そのメソッドがメンバ変数を変更しないことを宣言できる
int getNum() const { return num; }
// メンバ変数を変更しようとするとコンパイルエラーが出る。
//void setNum(int _num) const { num = _num; }
int getNumMutable() const { return numMutable; }
// constをつけても、変更するメンバ変数にmutableをつければ、変更できる。
void setNumMutable(int _numMutable) const { numMutable = _numMutable; }
};
class Study1 {
public:
int num;
Study1() { }
~Study1() { }
int getNum() const { return num; }
void setNum(int _num) { num = _num; }
};
void study1() {
// ■(1)
// constが*より左についた場合、charMsgが指し示すものは変更不可
// charMsg変数自体は変更が出来る。
const char* charMsg1 = new char[10];
//strcpy(charMsg1, "aiueo"); // これはコンパイルエラー
//charMsg1[0]='A'; // これもコンパイルエラー
charMsg1 = new char[10]; // これはOK
// ■(2)
// constが*より右についた場合、charMsg変数自体は変更不可
// charMsgが指し示すものは変更可能。
char* const charMsg2 = new char[10];
strcpy(charMsg2, "aiueo"); // これはOK
charMsg2[0]='A'; // これもOK
//charMsg2= new char[10]; // これはコンパイルエラー
// ■(3)
// この場合、charMsg3変数自体も、charMsg3が指し示すものも変更不可となる
const char* const charMsg3 = new char[10];
//strcpy(charMsg3, "aiueo"); // これはコンパイルエラー
//charMsg3[0]='A'; // これはコンパイルエラー
//charMsg3= new char[10]; // これはコンパイルエラー
// ■(4)
// 対象がクラスも、意味は同じ。*の左にconstを置くと、メソッドがconstでないもの以外はコンパイルエラー
const Study1* study = new Study1();
//study->setNum(100); // メソッドにconstがついていないため、コンパイルエラー
int num = study->getNum(); // メソッドにconstがついているため、OK
delete study;
}
|
スマートポインタといわれるクラスがあります。ポインタをセットしておくと、今のスコープを抜けると、自動でセットしたポインタに対し「delete」をしてくれます。
なので、メモリの開放し忘れが防げます。
ただ、大切なのは常に「delete」しかしてくれないことです。配列をセットしても「delete」を呼ぶのでメモリリークします。その点だけは考えて使う必要があります。
以下に、自動でdeleteが呼ばれていることを確認するコードを示します。
| Study2.cpp |
#include <string>
#include <iostream>
#include <memory>
class AnyObject {
private :
std::string msg;
public:
AnyObject() {
std::cout << ">>>>>>>>>> AnyObjectのコンストラクタが呼ばれました!" << "\n";
}
~AnyObject() {
std::cout << "<<<<<<<<<< AnyObjectのディストラクタが呼ばれました!" << "\n";
}
std::string getMsg() const { return msg; }
void setMsg(std::string _msg) { msg = _msg; }
};
/* このファイル内でのみ使用する関数 */
static void sample() {
// メモリを確保する
AnyObject *obj = new AnyObject();
// 確保したメモリのポインタをshared_ptrに渡す
std::shared_ptr<AnyObject> pObj(obj);
obj->setMsg("ゆがみねぇな");
std::cout << "msg : " << obj->getMsg() << "\n";
// 確保したメモリをdeleteせず、メソッドを終わる
// shared_ptrが、objが指していたメモリ領域への参照カウントがゼロになったと判断し、メモリが開放される。
}
void study2() {
std::cout << "メソッドを呼ぶよ!" << "\n";
sample();
std::cout << "メソッドの呼び出しが終わったよ!" << "\n";
}
|
| study2関数の実行結果 |
メソッドを呼ぶよ! >>>>>>>>>> AnyObjectのコンストラクタが呼ばれました! msg : ゆがみねぇな <<<<<<<<<< AnyObjectのディストラクタが呼ばれました! メソッドの呼び出しが終わったよ! |
小手先のテクニック的な話となりますが・・・。
とあるクラスを、型として、特定のクラスに変換したい!けど変換する処理を書くのやだ!
っという場合、演算子のオーバーロードでなんとかなるらしい。
以下のコードでは、Study3AnyObjectとStudy3Objectがあります。そして、sample関数があります。この関数は、Study3AnyObjectを引数にとります。だけど、この関数にStudy3Objectを渡すことで呼び出したい!
ってな時に使います(こんな状況はほとんど設計ミスです。Study3Objectを引数にとるsample関数をもう一つ作るべきです。それも面倒なぐらいなケースでのみ使います。)
Study3Objectの中の「operator Study3AnyObject() 〜〜〜」という部分が、Study3AnyObjectへの変換が必要なときに呼び出される処理です。ここで、変換処理を記述します。
| Study3.cpp |
#include <string>
#include <iostream>
class Study3AnyObject {
private:
std::string msg;
public:
Study3AnyObject(std::string _msg) {
std::cout << "Study3AnyObjectコンストラクタ" << "\n";
msg = _msg;
}
~Study3AnyObject() {
std::cout << "Study3AnyObjectディストラクタ" << "\n";
}
Study3AnyObject(Study3AnyObject& study3AnyObject){
std::cout << "Study3AnyObjectコピーコンストラクタ" << "\n";
}
std::string getMsg() const { return msg; }
void setMsg(std::string _msg) { msg = _msg; }
};
class Study3Object {
private:
Study3AnyObject* obj;
public:
Study3Object() {
std::cout << "Study3Objectコンストラクタ" << "\n";
obj = new Study3AnyObject("暗黙の型変換のテスト!");
}
~Study3Object() {
std::cout << "Study3Objectディストラクタ" << "\n";
delete obj;
}
Study3Object(Study3Object& study3Object) {
std::cout << "Study3Objectコピーコンストラクタ" << "\n";
}
operator Study3AnyObject() const {
std::cout << "Study3Object::Study3AnyObject operatorが呼び出されました!" << "\n";
return *obj;
}
};
static void sample(Study3AnyObject obj) {
std::cout << "obj.msg : " << obj.getMsg() << "\n";
}
void study3() {
Study3Object obj;
std::cout << ">>>>>>>>>>>>>> sample関数を呼び出します" << "\n";
sample(obj);
std::cout << "<<<<<<<<<<<<<< sample関数から戻ってきました" << "\n";
}
|
| study3関数の実行結果 |
Study3Objectコンストラクタ Study3AnyObjectコンストラクタ >>>>>>>>>>>>>> sample関数を呼び出します Study3Object::Study3AnyObject operatorが呼び出されました! Study3AnyObjectコピーコンストラクタ obj.msg : Study3AnyObjectディストラクタ <<<<<<<<<<<<<< sample関数から戻ってきました Study3Objectディストラクタ Study3AnyObjectディストラクタ |
少々長いですが・・・。
クラスを値渡しでメソッドの引数として渡すと、コピーコンストラクタが動き、インスタンスが新たに作成されて、そのメソッド内で使用されることになります。
この場合、どのコピーコンストラクタが呼び出されるか・・・というのが問題です。下のサンプルのstudy4_1関数の実行結果を見るとわかりますが、引数型のクラスのコピーコンストラクタが呼ばれます。
なので、実態と異なるコピーコンストラクタが呼び出されるため、実態のクラスがもつメンバ変数すべてがコピーされず、不完全なコピーが行われます。これを「スライス問題」というらしい。
なので、値渡しになるケースは困るケースがでてくるので、参照を使用します(study4_2関数のケース)。
このようにすれば、参照渡しとなるため、コピーコンストラクタを呼ぶこともないので、問題は発生しません。
実行効率からみても、参照を使ったほうがいいはずです。
| Study4.cpp |
#include <string>
#include <iostream>
class Study4ObjA {
public:
Study4ObjA() {
std::cout << ">>>>>>>>>>>>> Study4ObjAコンストラクタが呼ばれました" << "\n";
}
virtual ~Study4ObjA() {
std::cout << "<<<<<<<<<<<<< Study4ObjAディストラクタが呼ばれました" << "\n";
}
Study4ObjA(Study4ObjA& obj) {
std::cout << ">>>>>>>>>>>>> Study4ObjAコピーコンストラクタが呼ばれました" << "\n";
}
void showMsg() {
std::cout << "message : " << createMsg() << "\n";
}
virtual std::string createMsg() {
return "Aだよ";
}
};
class Study4ObjB : public Study4ObjA {
public:
Study4ObjB() {
std::cout << ">>>>>>>>>>>>> Study4ObjBコンストラクタが呼ばれました" << "\n";
}
virtual ~Study4ObjB() {
std::cout << "<<<<<<<<<<<<< Study4ObjBディストラクタが呼ばれました" << "\n";
}
Study4ObjB(Study4ObjB& obj) {
std::cout << ">>>>>>>>>>>>> Study4ObjBコピーコンストラクタが呼ばれました" << "\n";
}
virtual std::string createMsg() override {
return Study4ObjA::createMsg() + " / Bだよ";
}
};
class Study4ObjC : public Study4ObjB {
public:
Study4ObjC() {
std::cout << ">>>>>>>>>>>>> Study4ObjCコンストラクタが呼ばれました" << "\n";
}
virtual ~Study4ObjC() {
std::cout << "<<<<<<<<<<<<< Study4ObjCディストラクタが呼ばれました" << "\n";
}
Study4ObjC(Study4ObjC& obj) {
std::cout << ">>>>>>>>>>>>> Study4ObjCコピーコンストラクタが呼ばれました" << "\n";
}
virtual std::string createMsg() override {
return Study4ObjB::createMsg() + " / Cだよ!";
}
};
static void callA(Study4ObjA obj) {
std::cout << "callAがshowMsgを呼びます" << "\n";
obj.showMsg();
}
static void callB(Study4ObjB obj) {
std::cout << "callBがshowMsgを呼びます" << "\n";
obj.showMsg();
}
static void callC(Study4ObjC obj) {
std::cout << "callCがshowMsgを呼びます" << "\n";
obj.showMsg();
}
// このケースでは、スライス問題も発生する。
// また、実態のクラスに関係なく、引数の型で指定したクラスの型としてオブジェクトが振舞う
void study4_1() {
std::cout << "study4_1処理開始" << "\n";
Study4ObjC obj;
std::cout << "■callA --------------------" << "\n";
callA(obj);
std::cout << "■callB --------------------" << "\n";
callB(obj);
std::cout << "■callC --------------------" << "\n";
callC(obj);
std::cout << "study4_1処理終了" << "\n";
}
static void callARef(Study4ObjA& obj) {
std::cout << "callAがshowMsgを呼びます" << "\n";
obj.showMsg();
}
static void callBRef(Study4ObjB& obj) {
std::cout << "callBがshowMsgを呼びます" << "\n";
obj.showMsg();
}
static void callCRef(Study4ObjC& obj) {
std::cout << "callCがshowMsgを呼びます" << "\n";
obj.showMsg();
}
// この参照渡しを使うとスライス問題は発生しない。
// また、実態の型として振舞いを行う
void study4_2() {
std::cout << "study4_2処理開始" << "\n";
Study4ObjC obj;
std::cout << "■callA --------------------" << "\n";
callARef(obj);
std::cout << "■callB --------------------" << "\n";
callBRef(obj);
std::cout << "■callC --------------------" << "\n";
callCRef(obj);
std::cout << "study4_2処理終了" << "\n";
}
|
| study4_1関数の実行結果 |
study4_1処理開始 >>>>>>>>>>>>> Study4ObjAコンストラクタが呼ばれました >>>>>>>>>>>>> Study4ObjBコンストラクタが呼ばれました >>>>>>>>>>>>> Study4ObjCコンストラクタが呼ばれました ■callA -------------------- >>>>>>>>>>>>> Study4ObjAコピーコンストラクタが呼ばれました callAがshowMsgを呼びます message : Aだよ <<<<<<<<<<<<< Study4ObjAディストラクタが呼ばれました ■callB -------------------- >>>>>>>>>>>>> Study4ObjAコンストラクタが呼ばれました >>>>>>>>>>>>> Study4ObjBコピーコンストラクタが呼ばれました callBがshowMsgを呼びます message : Aだよ / Bだよ <<<<<<<<<<<<< Study4ObjBディストラクタが呼ばれました <<<<<<<<<<<<< Study4ObjAディストラクタが呼ばれました ■callC -------------------- >>>>>>>>>>>>> Study4ObjAコンストラクタが呼ばれました >>>>>>>>>>>>> Study4ObjBコンストラクタが呼ばれました >>>>>>>>>>>>> Study4ObjCコピーコンストラクタが呼ばれました callCがshowMsgを呼びます message : Aだよ / Bだよ / Cだよ! <<<<<<<<<<<<< Study4ObjCディストラクタが呼ばれました <<<<<<<<<<<<< Study4ObjBディストラクタが呼ばれました <<<<<<<<<<<<< Study4ObjAディストラクタが呼ばれました study4_1処理終了 <<<<<<<<<<<<< Study4ObjCディストラクタが呼ばれました <<<<<<<<<<<<< Study4ObjBディストラクタが呼ばれました <<<<<<<<<<<<< Study4ObjAディストラクタが呼ばれました |
| study4_2関数の実行結果 |
study4_2処理開始 >>>>>>>>>>>>> Study4ObjAコンストラクタが呼ばれました >>>>>>>>>>>>> Study4ObjBコンストラクタが呼ばれました >>>>>>>>>>>>> Study4ObjCコンストラクタが呼ばれました ■callA -------------------- callAがshowMsgを呼びます message : Aだよ / Bだよ / Cだよ! ■callB -------------------- callBがshowMsgを呼びます message : Aだよ / Bだよ / Cだよ! ■callC -------------------- callCがshowMsgを呼びます message : Aだよ / Bだよ / Cだよ! study4_2処理終了 <<<<<<<<<<<<< Study4ObjCディストラクタが呼ばれました <<<<<<<<<<<<< Study4ObjBディストラクタが呼ばれました <<<<<<<<<<<<< Study4ObjAディストラクタが呼ばれました |
C++には、以下の4つのキャストがあります。
static_cast<型>(式)
dynamic_cast<型>(式)
const_cast<型>(式)
reinterpret_cast<型>(式)
カッコを使ってキャストする方式は、C言語のキャストです。C++なら上記のキャストを使用します。
これら4つのキャストの例を以下に示します。
| Study5.cpp |
class Study5ObjA {
public:
virtual std::string getMsg() { return "Study5ObjAだよ!"; }
};
class Study5ObjB : public Study5ObjA {
public:
virtual std::string getMsg() { return "Study5objBだよ!"; }
};
static void showMsg(Study5ObjA& obj) {
std::cout << "showMsg : " << obj.getMsg() << "\n";
}
void study5() {
// static_castの例(C言語でいう()でやるキャストと同じ)
float f = 10.5f;
int num = static_cast<int>(f);
std::cout << "static_castの結果 : " << num << "\n";
// dynamic_castの例(ダウンキャストを行う)
Study5ObjA* objB = new Study5ObjB();
Study5ObjB* downcastObj = dynamic_cast<Study5ObjB*>(objB);
std::cout << "downcastObj : " << downcastObj->getMsg() << "\n";
delete objB;
// dynamic_castで、キャストができない場合は結果が0になります
Study5ObjA* objA = new Study5ObjA();
Study5ObjB* failObj = dynamic_cast<Study5ObjB*>(objA);
if (failObj == 0)std::cout << "downcastObj is null!!" << "\n";
delete objA;
// const_castの例。唯一、constの指定を取り除くことができるキャストです(普通は使用しません)。
const int* constNum = new int;
//*constNum = 10; //これはエラーになる
int* constP = const_cast<int*>(constNum);
*constP = 20;
std::cout << "constNum : " << *constNum << "\n";
delete constNum;
// reinterpret_castの例。特殊な用途を除き使用しません。
// 例では、ポインタのアドレスを整数値に変換しています(その逆も可能です)
int *p = new int;
int pointerAddress = reinterpret_cast<int>(p);
std::cout << "pointerAddress : " << pointerAddress << "\n";
delete p;
std::cout << "study5終了" << "\n";
}
|
| study5関数実行結果 |
static_castの結果 : 10 downcastObj : Study5objBだよ! downcastObj is null!! constNum : 20 pointerAddress : 76046064 study5終了 |
隠蔽が起きてしまうようなケースは、設計上やらないハズ(無用な混乱を起こさないために)。
が、隠蔽が起きてしまって、隠蔽されたものが使いたい場合があります。そのようなケースの例を以下に示します。
また、注意したいのはシグネチャとか関係なしに、名前が一致しているものを隠蔽します。
| Study6.cpp |
#include <string>
#include <iostream>
class Study6ObjA {
public:
void setNum(int _num) {
std::cout << "Study6ObjA::setNum called" << "\n";
num = _num;
}
int getNum() const {
std::cout << "Study6ObjA::getNum called" << "\n";
return num;
}
void setNum(int, int) {
std::cout << "Study6ObjA::setNum(int, int)が呼ばれました!";
}
private:
int num;
};
class Study7ObjB : public Study6ObjA {
public:
using Study6ObjA::setNum;
void setNum(int _num) {
std::cout << "Study6ObjB::setNum called" << "\n";
// 隠蔽されている親クラスのsetNumを呼ぶ
Study6ObjA::setNum(_num);
}
int getNum() const{
std::cout << "Study6ObjB::getNum called" << "\n";
// 隠蔽されている親クラスのgetNumを呼ぶ
return Study6ObjA::getNum();
}
private:
int num;
};
void study6() {
Study7ObjB* obj = new Study7ObjB();
obj->setNum(100);
// 隠蔽はシグネチャに関係なく、名前が一致したものすべてを隠蔽してしまう。
// 「using Study6ObjA::setNum;」という記述をすることで、隠蔽されていたsetNum(int, int)が使えるようになる
// (この記述で、上位クラスで定義したsetNumも下位クラスで使用するよ・・・という指定ができる)
obj->setNum(10, 10);
int result = obj->getNum();
std::cout << "result : " << result << "\n";
std::cout << "study7終了!";
}
|
| study6関数の実行結果 |
Study6ObjB::setNum called Study6ObjA::setNum called Study6ObjA::setNum(int, int)が呼ばれました!Study6ObjB::getNum called Study6ObjA::getNum called result : 100 study7終了! |
継承したときの動作がJavaと異なる点があるので、メモ。
ずっとJavaにひたっていたらひっかかる。
以下のような継承関係がある場合、Study7ObjAのコンストラクタcreateNumメソッドはどのクラスにあるものが、呼ばれるか・・・が問題。
Javaの場合は、Study7ObjBクラスのcreateNumが呼び出される。しかし、C++の場合は、Study7ObjAのものが呼び出される。
Study7ObjAのコンストラクタ実行時には、Study7ObjBのことなんて不明な状態らしい。だから、createNumは自分が持ってるものしかわからない。だから、Study7ObjAのメンバ関数が呼ばれることになる・・・ということらしい。
| Study7.cpp |
#include <string>
#include <iostream>
class Study7ObjA {
public:
Study7ObjA() { createNum(); }
virtual ~Study7ObjA() { }
int getNum() const { return num; }
void setNum(int num) { this->num = num; }
virtual void createNum() {
setNum(100);
}
private:
int num;
};
class Study7ObjB : public Study7ObjA {
public:
//Study7ObjB() { createNum(); } // 今回のケースはこのように書くことで、numに999がセットされる
Study7ObjB() { }
virtual ~Study7ObjB() { }
virtual void createNum() {
setNum(999);
}
};
void study7() {
Study7ObjB obj;
std::cout << "number : " << obj.getNum() << "\n";
std::cout << "study7 終了" << "\n";
}
|
| study7関数実行結果 |
number : 100 study7 終了 |
ひし形継承の挙動と、仮想継承についてメモ。
・・・メモしたからといって、このような設計はしませんが。こんなこともあるよレベルです。
以下にひし形継承の例を示します。
Study8ObjBクラスと、Study8ObjCが、同じクラスを継承しています。そして、Study8ObjDが、この2つを多重継承しています。この状況では、Stdy8ObjDのインスタンスを生成すると、Study8ObjAは、2個できます。それぞれ、Study8ObjBとStudy8ObjCのそれぞれの上位クラスのインスタンス用として、2個生成されるわけです。これが、難儀な状態を生みます。
| Study8.cpp(ひし形継承のケース) |
#include <string>
#include <iostream>
class Study8ObjA {
public:
Study8ObjA() {
std::cout << "Study8ObjAコンストラクタが呼ばれました!" << "\n";
}
~Study8ObjA() {
std::cout << "Study8ObjAディストラクタが呼ばれました!" << "\n";
}
void setNum(int num) { this->num = num; }
int getNum() { return this->num; }
int num;
};
class Study8ObjB : public Study8ObjA {
public:
Study8ObjB() {
std::cout << "Study8ObjBコンストラクタが呼ばれました!" << "\n";
}
~Study8ObjB() {
std::cout << "Study8ObjBディストラクタが呼ばれました!" << "\n";
}
int a;
};
class Study8ObjC : public Study8ObjA {
public:
Study8ObjC() {
std::cout << "Study8ObjCコンストラクタが呼ばれました!" << "\n";
}
~Study8ObjC() {
std::cout << "Study8ObjCディストラクタが呼ばれました!" << "\n";
}
int a;
};
class Study8ObjD : public Study8ObjB, public Study8ObjC {
public:
Study8ObjD() {
std::cout << "Study8ObjDコンストラクタが呼ばれました!" << "\n";
}
~Study8ObjD() {
std::cout << "Study8ObjDディストラクタが呼ばれました!" << "\n";
}
};
void study8() {
Study8ObjD obj;
// 仮想継承を行わない場合、Study8ObjAが2個生成されるので、どちらのインスタンスかを
// 指定することで、アクセスできる
obj.Study8ObjB::setNum(100);
obj.Study8ObjC::setNum(200);
std::cout << "Study8ObjBのnum : " << obj.Study8ObjB::getNum() << "\n";
std::cout << "Study8ObjCのnum : " << obj.Study8ObjC::getNum() << "\n";
std::cout << "study8の処理が終了しました!!" << "\n";
}
|
| study8関数の実行結果(ひし形継承のケース) |
Study8ObjAコンストラクタが呼ばれました! Study8ObjBコンストラクタが呼ばれました! Study8ObjAコンストラクタが呼ばれました! Study8ObjCコンストラクタが呼ばれました! Study8ObjDコンストラクタが呼ばれました! Study8ObjBのnum : 100 Study8ObjCのnum : 200 study8の処理が終了しました!! Study8ObjDディストラクタが呼ばれました! Study8ObjCディストラクタが呼ばれました! Study8ObjAディストラクタが呼ばれました! Study8ObjBディストラクタが呼ばれました! Study8ObjAディストラクタが呼ばれました! |
Study8ObjAのインスタンスを2個作りたくないぜ!という場合は、仮想継承を行います。継承を記述する部分の記述に、virtualを追記するだけで実現可能です。
そうすることで、Study8ObjAは1個だけしか生成されなくなります。
| Study8.cpp(ひし形継承だが、仮想継承を使ったケース) |
#include <string>
#include <iostream>
class Study8ObjA {
public:
Study8ObjA() {
std::cout << "Study8ObjAコンストラクタが呼ばれました!" << "\n";
}
~Study8ObjA() {
std::cout << "Study8ObjAディストラクタが呼ばれました!" << "\n";
}
void setNum(int num) { this->num = num; }
int getNum() { return this->num; }
int num;
};
class Study8ObjB : virtual public Study8ObjA {
public:
Study8ObjB() {
std::cout << "Study8ObjBコンストラクタが呼ばれました!" << "\n";
}
~Study8ObjB() {
std::cout << "Study8ObjBディストラクタが呼ばれました!" << "\n";
}
int a;
};
class Study8ObjC : virtual public Study8ObjA {
public:
Study8ObjC() {
std::cout << "Study8ObjCコンストラクタが呼ばれました!" << "\n";
}
~Study8ObjC() {
std::cout << "Study8ObjCディストラクタが呼ばれました!" << "\n";
}
int a;
};
class Study8ObjD : public Study8ObjB, public Study8ObjC {
public:
Study8ObjD() {
std::cout << "Study8ObjDコンストラクタが呼ばれました!" << "\n";
}
~Study8ObjD() {
std::cout << "Study8ObjDディストラクタが呼ばれました!" << "\n";
}
};
void study8() {
Study8ObjD obj;
// 仮想継承を行う(継承を指定するところでvirtualと書く)と、Study8ObjAは1個だけ
// 生成されるようになる。なので、obj.setNumを直接呼んでも構わない。
obj.Study8ObjB::setNum(999);
obj.Study8ObjC::setNum(888);
obj.setNum(100);
std::cout << "num : " << obj.getNum() << "\n";
std::cout << "num : " << obj.Study8ObjB::getNum() << "\n";
std::cout << "num : " << obj.Study8ObjC::getNum() << "\n";
std::cout << "study8の処理が終了しました!!" << "\n";
}
|
| study8関数の実行結果(ひし形継承だが、仮想継承を使ったケース) |
Study8ObjAコンストラクタが呼ばれました! Study8ObjBコンストラクタが呼ばれました! Study8ObjCコンストラクタが呼ばれました! Study8ObjDコンストラクタが呼ばれました! num : 100 num : 100 num : 100 study8の処理が終了しました!! Study8ObjDディストラクタが呼ばれました! Study8ObjCディストラクタが呼ばれました! Study8ObjBディストラクタが呼ばれました! Study8ObjAディストラクタが呼ばれました! |
テンプレートの使い方についての例です。
| Study9.cpp |
#include <string>
#include <iostream>
class Study9ObjA {
public:
Study9ObjA() {
std::cout << "Study9ObjAのコンストラクタが呼ばれました!" << "\n";
}
~Study9ObjA() {
std::cout << "Study9ObjAのディストラクタが呼ばれました!" << "\n";
}
Study9ObjA(Study9ObjA& obj) {
std::cout << "Study9ObjAのコピーコンストラクタが呼ばれました!" << "\n";
}
std::string getName() { return "Study9ObjAです><"; }
int getNum() { return 100; }
};
// 関数テンプレートの例(1)
template <typename T>
T* createObject() {
T* result = new T();
// 以下のような記述も可能。
// 型TにgetNameメソッドが用意されているかどうかは、コンパイルするまでわからない。
std::cout << "msg : " << result->getName() << "\n";
return result;
}
// 関数テンプレートの例(2)
template <typename T>
int createNumber(T& obj) {
return obj.getNum() + 100;
}
// クラステンプレートの例
template <class T>
class Study9ObjB {
public:
Study9ObjB() {
std::cout << "Study9ObjB(template)のコンストラクタが呼ばれました!" << "\n";
field = new T();
}
~Study9ObjB() {
std::cout << "Study9ObjB(template)のディストラクタが呼ばれました!" << "\n";
delete field;
}
Study9ObjB(Study9ObjB& obj) {
std::cout << "Study9ObjBのコピーコンストラクタが呼ばれました!" << "\n";
delete field;
}
std::string getName() {
return field->getName();
}
std::string getMessage() {
return "ゆがみねぇな";
}
private :
T* field;
};
class Study9ObjC {
std::string getMessage() {
return "ファッ!";
}
};
template<>
class Study9ObjB<Study9ObjC> {
public:
std::string getName() {
return "Study9ObjBテンプレートをStudy9ObjCで特化したクラスだよ!";
}
};
// テンプレートの使い方と、特化について
void study9_1() {
// 関数テンプレートを使う
Study9ObjA* obj = createObject<Study9ObjA>();
std::cout << "createObject : " << obj->getName() << "\n";
std::cout << "createNumber : " << createNumber(*obj) << "\n";
delete obj;
// クラステンプレートを使う
Study9ObjB<Study9ObjA>* obj2 = new Study9ObjB<Study9ObjA>();
std::cout << "obj2 : " << obj2->getName() << "\n";
std::cout << "obj2 : " << obj2->getMessage() << "\n";
delete obj2;
// 特化したクラステンプレートを使う
Study9ObjB<Study9ObjC>* obj3 = new Study9ObjB<Study9ObjC>();
std::cout << "obj3 : " << obj3->getName() << "\n";
delete obj3;
std::cout << "study9の処理終了!" << "\n";
}
|
| study9_1関数実行結果 |
Study9ObjAのコンストラクタが呼ばれました! msg : Study9ObjAです>< createObject : Study9ObjAです>< createNumber : 200 Study9ObjAのディストラクタが呼ばれました! Study9ObjB(template)のコンストラクタが呼ばれました! Study9ObjAのコンストラクタが呼ばれました! obj2 : Study9ObjAです>< obj2 : ゆがみねぇな Study9ObjB(template)のディストラクタが呼ばれました! Study9ObjAのディストラクタが呼ばれました! obj3 : Study9ObjBテンプレートをStudy9ObjCで特化したクラスだよ! study9の処理終了! |
普通はいろいろな型に対応するために、引数の型毎に、メソッドやコンストラクタを用意する必要がありますが、この手法では1つ書いておけは、あとはテンプレートの機能でどんな型にでも対応可能です。
使いどころは少ないかもしれませんが・・・。
タイトルはコピーコンストラクタとありますが、メンバ関数にも適用できるみたいです。
| Study10.cpp |
#include <string>
#include <iostream>
template<typename T>
class Study10ObjA {
public:
Study10ObjA() {
std::cout << "Study10ObjAのコンストラクタが呼ばれました!" << "\n";
}
~Study10ObjA() {
std::cout << "Study10ObjAのディストラクタが呼ばれました!" << "\n";
}
template<typename U>
Study10ObjA(Study10ObjA<U>& obj) {
std::cout << "Study10ObjAのコピーコンストラクタが呼ばれました!" << "\n";
U uObj;
this->msg = uObj.getName();
}
template<typename U>
void yugami(Study10ObjA<U> obj) {
std::cout << "yugami : " << obj.getMsg() << "\n";
}
std::string getMsg() { return msg; }
private:
std::string msg;
};
class Study10ObjB {
public:
std::string getName() { return "Study10ObjBだよ!"; }
};
class Study10ObjC {
public:
std::string getName() { return "Study10ObjCだよ!"; }
};
void study10() {
Study10ObjA<Study10ObjB> b;
Study10ObjA<Study10ObjC> c;
// クラステンプレートにより生成されたという点以外bとcはソース上無関係なソースである。
// クラステンプレート(Study10ObjA)にて、一般化されたコピーコンストラクタ
// を記述することで、代入を可能にしている
std::cout << "b = cを実行します!" << "\n";
b = c;
std::cout << "bの結果 : " << b.getMsg() << "\n";
// 上記と同様に、一般化されたメンバ関数も作ることが可能
b.yugami(c);
c.yugami(b);
std::cout << "study10が終了しました" << "\n";
}
|
| study10メソッドの実行結果 |
Study10ObjAのコンストラクタが呼ばれました! Study10ObjAのコンストラクタが呼ばれました! b = cを実行します! Study10ObjAのコピーコンストラクタが呼ばれました! Study10ObjAのディストラクタが呼ばれました! bの結果 : Study10ObjCだよ! Study10ObjAのコピーコンストラクタが呼ばれました! yugami : Study10ObjCだよ! Study10ObjAのディストラクタが呼ばれました! Study10ObjAのコピーコンストラクタが呼ばれました! yugami : Study10ObjBだよ! Study10ObjAのディストラクタが呼ばれました! study10が終了しました Study10ObjAのディストラクタが呼ばれました! Study10ObjAのディストラクタが呼ばれました! |