オブジェクト指向の概念を取り入れたC++にはfriendという構文が在る。他のクラスや関数から非publicなメンバの利用を許可する『親友をつくる』機能だ。例えば次のコードはエラーとならない。
class A {
// Bクラスをフレンドクラス宣言
friend class B;
// gf_getAm関数をフレンド関数宣言
friend int gf_getAm(A &a);
private:
int m_;
};
class B {
int getAm(A &a) {
// BクラスからAクラスのprivateメンバにアクセス出来る.
return a.m_;
}
};
int gf_getAm(A &a)
{
// gf_getAm関数からAクラスのprivateメンバにアクセス出来る.
return a.m_;
}
今回はそんなfriendに関する話。
名前空間外へfriend
例によって例のごとく、C++でプログラミング中の私、天井冴太。ある名前空間内のクラスでグローバルスコープのクラスをフレンドクラスに宣言したい。なので次のように書いた。
namespace ns {
class C1 {
friend class C2;
int i;
public:
C1(int a) : i(a) {}
};
} // namespace ns;
class C2 {
public:
void set(ns::C1 &c1, int a) { c1.i = a; }
int get(ns::C1 &c1) { return c1.i; }
};
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
test.cpp
test.cpp(13) : error C2248: 'ns::C1::i' : private メンバ (クラス 'ns::C1' で宣言されている) にアクセスできません。
test.cpp(4) : 'ns::C1::i' の宣言を確認してください。
test.cpp(2) : 'ns::C1' の宣言を確認してください。
test.cpp(14) : error C2248: 'ns::C1::i' : private メンバ (クラス 'ns::C1' で宣言されている) にアクセスできません。
test.cpp(4) : 'ns::C1::i' の宣言を確認してください。
test.cpp(2) : 'ns::C1' の宣言を確認してください。
test.cpp: In member function `void C2::set(ns::C1&, int)': test.cpp:4: error: `int ns::C1::i' is private test.cpp:13: error: within this context test.cpp: In member function `int C2::get(ns::C1&)': test.cpp:4: error: `int ns::C1::i' is private test.cpp:14: error: within this context
あれ?変だな?……名前空間が悪さしてるのかな?friend class C2;が::C2では無くns::C2を探しに行ってしまっているとか?
という訳で、friend class C2;をfriend class ::C2;に変更してみる。
コンパイル……よし、VC++ 2005のコンパイラは通った。GCCはどうだ?
test.cpp:3: error: expected type-name test.cpp:3: error: friend declaration does not name a class or function test.cpp: In member function `void C2::set(ns::C1&, int)': test.cpp:4: error: `int ns::C1::i' is private test.cpp:13: error: within this context test.cpp: In member function `int C2::get(ns::C1&)': test.cpp:4: error: `int ns::C1::i' is private test.cpp:14: error: within this context
うーん、エラーが取れない。むしろ増えてるんですけど……
test.cpp:3: error: expected type-name test.cpp:3: error: friend declaration does not name a class or function
……の部分がアヤシイ気がする。小一時間悩んでコードの先頭にclass C2;を追加してみる。……ま、無理だろうけど…………通ったよ。
つまるトコ最終的に以下のようなコードに。
class C2;
namespace ns {
class C1 {
friend class C2;
int i;
public:
C1(int a) : i(a) {}
};
} // namespace ns;
class C2 {
public:
void set(ns::C1 &c1, int a) { c1.i = a; }
int get(ns::C1 &c1) { return c1.i; }
};
……って、ええぇぇぇ。1行目に
が必要なんて、チョットどうよ。美しくないぞ。class C2;friend宣言部分だけで何とかならんのか?
関数だと?
ココでふと疑問。クラスはこれで上手くいったが、じゃあ関数の場合は?
試しにC1::iにアクセスするグローバル関数を作成。上記コードの
の部分をそのプロトタイプにしてみた。class C2;
int f();
namespace ns {
class C1 {
friend int f();
int i;
public:
C1(int a) : i(a) {}
};
} // namespace ns;
int f()
{
ns::C1 c(1);
return c.i;
}
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
test.cpp
test.cpp(23) : error C2248: 'ns::C1::i' : private メンバ (クラス 'ns::C1' で宣言されている) にアクセスできません。
test.cpp(7) : 'ns::C1::i' の宣言を確認してください。
test.cpp(4) : 'ns::C1' の宣言を確認してください。
あれー、通らない。friendが認識されてない。
チョット考えてfriend int f();をfriend int ::f();に変更してみた。……よし通った。
……って、クラスだと::無くて良くて関数だと必要ってどうよ。統一感無いなぁ。……もしかして規格上はどちらの場合でも::要るんだろうか?VC++もGCCも規格外の動きをしているだけで。試しにクラスへのfriend宣言のコードで
をfriend class C2;friend class ::C2;にしてもコンパイル通ったし。どうなんだろう。
JISでのfriend
ところで、今回このエントリを書くにあたってJISのC++規格書(JISのサイトからX3014で検索して出てくるヤツ)を覗いてみたが、JIS C++規格ではfriendの事を『随伴』と表現している事を初めて知った。……んー、『随伴』ねぇ……『friend』の単語から受けるイメージとズレるなぁ……
