C++ ⇒ VBA 書いて覚えるための初心者自己中記事

C++ ⇒ VBA 勉強の履歴を付けるというかノート代わりに使ってます

C++ フレンド friend 不完全型 書いて覚えるための初心者自己中記事

 

nenechi.hatenablog.com

 この記事で一度勉強したfriend についてさらに勉強

過去の勉強では

 

~~~~~

friend

オーバーロードの実装はクラス外で行うが

宣言部分はクラス内で行う。

その際宣言部の先頭に friend を記述する。

そうすると演算子オーバーロードはクラス外の認識で、しかしクラス内のprivate にもアクセスできるようになる。

~~~~~

 

こんなんだった。

 

この時は演算子オーバーロードの話だったけど、friend は別に用途を限定してないはずだから、これからやるのはもっと詳しくって感じなのだろうか。

 

 

まず、クラスのコンストラクタは普通public に記述する。

以前アクセス指定子の勉強をしたときにコンストラクタをprivate にしたらどうなるかを試した時の記事から抜粋したのがこれ↓

~~~~~~~~

public と private のアクセス指定子を使ってメンバ変数にはメンバ関数からしかアクセス出来ないようにしました。

コンストラクタはどっちなんだろうと思ったけど、 private にしたらエラー出た。

class型の変数宣言した時に呼び出せなくなっちゃったんだろうか。

 ~~~~~~~~

 

コンストラクタを private にするとクラスのオブジェクトを作れなかったはず。

 

しかし、friend を利用するとこれもできてしまうらしい。

 

頭で整理しにくいので少しずつ頑張る。

まずクラスをfriendするとは

class Value {
	friend class ValueFriend; //これでValueFriendクラスはValueクラスを好き放題出来る
 
};
 
class ValueFriend {
 
};
 
int main() {
 
}

 こういうこと。

プロトタイプ書く場所に書くのね。

 んで、コンストラクタをprivate にしてオブジェクトを作ろうとすると

class Value {
	friend class ValueFriend; //これでValueFriendクラスはValueクラスを好き放題出来る
 
 
private:
	Value() {} //コンストラクタをprivateにした
};
 
class ValueFriend {
 
};
 
int main() {
	Value v; //エラーになる(コンストラクタにアクセス出来ない)
}

 エラーになる。

ここまでは前に試した通り。

 

おっと、ポインタを試してなかった

class Value {
	friend class ValueFriend; //これでValueFriendクラスはValueクラスを好き放題出来る
 
 
private:
	Value() {} //コンストラクタをprivateにした
};
 
class ValueFriend {
 
};
 
int main() {
	Value* v; //ポインタオブジェクトは作れた
}

 作れた。

これって、実体の作れない抽象クラスでもポインタや参照でアップキャストできるのと同じ話かもしれない。

 

テキストだとこの方法をValueFriend クラスでやっているけど。そもそもこれは可能なんだね。

friend だから出来る、みたいに勘違いしちゃうよ。俺みたいな素人だと。

 

うん、とにかくValue クラスにfriend class ValueFriend ;  で

Value クラスのprivate にも干渉できるという話だった。

 

テキストだと凄い複雑なことしてるから混乱する。

 

~~~

ごめんやっぱり俺の勘違いだ、

class Value {
	friend class ValueFriend; //これでValueFriendクラスはValueクラスを好き放題出来る
public:
	void ValueSet(int n) { v_n = n; }
	const int* ValueGet() { return &v_n; }
private:
	Value() {} //コンストラクタをprivateにした
	int v_n;
};
 
class ValueFriend {
 
};
 
int main() {
	Value* v; //ここはテキストでもこうやってる
	v->ValueSet(3);
	cout << v->ValueGet << endl;//ここがエラーになる
}

 なんかエラーになる。

テキストだとここがちゃんと出来ているから、どういう仕組みか勉強。

class Value {
	friend class ValueFriend; //これでValueFriendクラスはValueクラスを好き放題出来る
public:
	int Get()const { return m_n; }
private:
	Value(int n):m_n(n) {} //コンストラクタをprivateにした
	Value(const Value&);//コピーコンストラクタも不可侵に
	void operator=(const Value&);//代入演算子も不可侵に
	int m_n;
};
 
class ValueFriend {
public:
	Value* New(int n) const {//メンバ関数NewでValueのポインタを返す
		return new Value(n);
	}
 
};
 
#define ARRAY_SIZE(array) (sizeof (array) / sizeof *(array))
 
int main() {
	static const int VALUE[] = {
		1, 2, 4, 8,
	};
	static const int SIZE = ARRAY_SIZE(VALUE);
 
	ValueFriend vfriend;
 
	for (int i = 0; i < SIZE; ++i) {
		Value* value = vfriend.New(VALUE[i]);/*Valueのポインタは作れる
											 ValueFriend側でnewで作った
											 Valueのアドレスを代入している*/
		cout << value->Get() << ' ';/*するとなぜかさっきは出来なかったことが出来ている
									?????*/
		delete value;
	}
	cout << endl;
 
	system("pause");
}

 

 new か?

newなのか?

 

 

ちがう。それ以前の不理解だ。

class Value {
public:
	int Get() { return m_n; }
	void Set(int n) { m_n = n; }
private:
	Value(int n):m_n(n) {}
	int m_n;
};
 
 
void main() {
 
	Value* vset(5);//初期化できないエラー
	vset->Set(3);
	cout << vset->Get() << endl;
 
 
	Value* v = new Value(3);//初期化出来ないエラー
}

 これは出来ないだろう。うん。

public メンバ関数で初期化できるかもしれないと思ったけど。どうだろう。

class Value {
public:
	Value* Inu(int n) {
		return new Value(n);
	}
	int Get() { return m_n; }
	void Set(int n) { m_n = n; }
private:
	Value(int n):m_n(n) {}
	int m_n;
};
 
 
void main() {
 
 
	Value* vinu = //ここで初期化するメンバ関数Inu を使いたかったけど無理だ。
 
 
	Value* vset(5);//初期化できないエラー
	vset->Set(3);
	cout << vset->Get() << endl;
 
 
	Value* v = new Value(3);//初期化出来ないエラー
}

 自分のクラスでは無理なのか。どうしても。

 

なるほど、だからfriend なクラスからアクセスして初期化したものをポインタで受け取る流れなのか。

少しわかったかも。

v->ValueSet(3);

がエラーにならなかったのはよくわからないけど。

 

 頭の中を整理しよう。

 

コンストラクタがprivate なクラス Value

Valueクラスはコンストラクタにアクセス出来ないから初期化出来ない。

初期化できないということは実体がない。

実体を作るためにはコンストラクタにアクセスしなければならない。

private コンストラクタにアクセスできるのは同じクラスのメンバ関数では?と思い試そうとしたが、実体のないクラスオブジェクトのメンバ関数を使う方法なんてわからない。

そこでValue クラスの友達 ValueFriend 。

ValueFriend ならValueクラスのprivate コンストラクタにアクセス出来る。

なのでValueFriend でValue クラスのオブジェクトを作って、そのポインタを返してあげる。

それをmain 関数部分で実体のなかったValue クラスポインタで受け取る。

受け取ったクラスポインタは初期化されているから実体がある。

ということで良いのかな?

 

ややこしい。

 

なぜこんな回りくどい方法があるのかというと、

特定のクラスを通さないと作れないクラス

を目的としているらしい。

 

ちなみに、このフレンド関係は一方的なものだそうで。

Value クラスはValueFriend クラスのアクセス指定を無視できないそうです。

Valueかわいそ。

 

 

不完全型について

 先ほどまでのfriend クラスでオブジェクトを作っちゃおう、の内容を見ると

実際にオブジェクトを作っているのはValueFriend のメンバ関数New だけ。

なのでこのNew さえVlaue クラスのアクセス指定を無視できれば問題ない。

 

じゃあそうしよう、としてfriend登録を書くと

class Value {
	//friend class ValueFriend; //これでValueFriendクラスはValueクラスを好き放題出来る
	friend Value* ValueFriend::New(int n) const;

 エラーになる。いっぱいエラーが出た。

これはValueFriend::New の部分で、まだValueFriendが宣言されていないから。

 

しかし、じゃあValue より前にValueFried を記述しよう!

としてもダメ。

class ValueFriend {
public:
	Value* New(int n) const {//メンバ関数NewでValueのポインタを返す
		return new Value(n);
	}

 ValueFriend のNew関数内でValueの記述があるので同じこと。

・・詰んだ。

 

 何か違和感があったけど、わかった、そうだ、そもそもさぁ

クラスをfriend してた時だって同じじゃないの?

どうしてクラスのfriend では後に記述されていたValueFriend のことを認めてくれたのに今回は認めてくれないの?

ひどくない?

いじめ?

 

 

真面目に違いは何だろうか。

friend class ValueFriend; クラスの場合

friend Value* ValueFriend::New(int n) const; メンバ関数の場合

 

 どちらも実装部分は後に記述。

なのにメンバ関数だけNG

 

ん?まてよ、それがこれから勉強する不完全型ってやつなのか?

 

とりあえずテキスト進めてみようかな。

 

 

 

 おぉ、なんか知りたかったこと書いてある。

 

Hoge hoge;

オブジェクトを作るときはそのクラスに必要なメモリサイズが判明していなければならない。

初期化を行うためにコンストラクタの内容も把握しておかなければならない。

これはクラスHogeの完全な宣言がなければ達成不可能。

 

Hoge* hoge;

クラスへのポインタのサイズは参照先の型に関係なく一律で決まっている。

明示的に初期化しなければ初期化されない。

動的変数で初期化されたとしてもそれはヌルポインタでされているだけ。

 

クラスへのポインタを作るときは

クラスの完全な宣言がなくても可能ということ。

 

完全な宣言でない、とはどういうものかというと

class Hoge;

これだけ。

これで、Hogeというクラスがあるんだよ~

ということだけが認知される。

そのHogeというクラスの中身に言及はされない。

 

さっきの

friend class ValueFriend; クラスの場合

friend Value* ValueFriend::New(int n) const; メンバ関数の場合

これでいうところの、クラスの場合がまさにそれだ。

 

これを

不完全型 (incomplete type)

という。

不完全型はクラスに限らず、構造体でも

struct Hoge;

とすれば不完全型になる。

 

不完全型を利用してメンバ関数をfriend にするには

class Value;     不完全型の宣言

class ValueFriend { }  完全な宣言

class Value { }  完全な宣言

ValueFrienf::~~    メンバ関数の実装 //実装は順不同

Value::~~   メンバ関数の実装 //実装は順不同

 

まずValueクラスの不完全型を用意する。

 

次にValueFriendクラスを宣言する。この時にValueFriend クラスの宣言にはValue* の記述があるが、上に不完全型のValue宣言があるので大丈夫。

 

さらにValueクラスの宣言、ここで先ほどエラーになったValueFriend クラスのメンバ変数New のfriend宣言を書くが、すでにValueFriendクラスの宣言は上にあるので大丈夫。

 

そしてメンバ関数の実装をおこなう。

実装についてはそもそもプロトタイプがあれば後に記述しても問題ない、というものなので順不同。のはず。

 

 このような処理が必要なのは

friend がお互いに参照しあっている場合の話。

excelだと相互参照はエラーだよね。

 

ここまで。