私はこのイディオムが好きで、もっとクリーンで表現力豊かになる可能性があります。
標準のC++03では、次の方法が最も使いやすく、最も一般的だと思います。(ただし、あまり改善されていません。ほとんどの場合、自分自身を繰り返す必要がありません。)テンプレートパラメーターはフレンドにすることはできないため、マクロを使用してパスキーを定義する必要があります。
// define passkey groups
#define EXPAND(pX) pX
#define PASSKEY_1(pKeyname, pFriend1) \
class EXPAND(pKeyname) \
{ \
private: \
friend EXPAND(pFriend1); \
EXPAND(pKeyname)() {} \
\
EXPAND(pKeyname)(const EXPAND(pKeyname)&); \
EXPAND(pKeyname)& operator=(const EXPAND(pKeyname)&); \
}
#define PASSKEY_2(pKeyname, pFriend1, pFriend2) \
class EXPAND(pKeyname) \
{ \
private: \
friend EXPAND(pFriend1); \
friend EXPAND(pFriend2); \
EXPAND(pKeyname)() {} \
\
EXPAND(pKeyname)(const EXPAND(pKeyname)&); \
EXPAND(pKeyname)& operator=(const EXPAND(pKeyname)&); \
}
// and so on to some N
//////////////////////////////////////////////////////////
// test!
//////////////////////////////////////////////////////////
struct bar;
struct baz;
struct qux;
void quux(int, double);
struct foo
{
PASSKEY_1(restricted1_key, struct bar);
PASSKEY_2(restricted2_key, struct bar, struct baz);
PASSKEY_1(restricted3_key, void quux(int, double));
void restricted1(restricted1_key) {}
void restricted2(restricted2_key) {}
void restricted3(restricted3_key) {}
} f;
struct bar
{
void run(void)
{
// passkey works
f.restricted1(foo::restricted1_key());
f.restricted2(foo::restricted2_key());
}
};
struct baz
{
void run(void)
{
// cannot create passkey
/* f.restricted1(foo::restricted1_key()); */
// passkey works
f.restricted2(foo::restricted2_key());
}
};
struct qux
{
void run(void)
{
// cannot create any required passkeys
/* f.restricted1(foo::restricted1_key()); */
/* f.restricted2(foo::restricted2_key()); */
}
};
void quux(int, double)
{
// passkey words
f.restricted3(foo::restricted3_key());
}
void corge(void)
{
// cannot use quux's passkey
/* f.restricted3(foo::restricted3_key()); */
}
int main(){}
この方法には2つの欠点があります。1)呼び出し元は作成する必要のある特定のパスキーを知っている必要があります。単純な命名スキーム(function_key
)は基本的にそれを排除しますが、それでも1つの抽象化クリーナー(そしてより簡単)である可能性があります。2)マクロの使用はそれほど難しくありませんが、少し醜いものと見なすことができますが、パスキー定義のブロックが必要です。ただし、C++03ではこれらの欠点を改善することはできません。
C ++ 0xでは、イディオムは最も単純で最も表現力豊かな形式に到達できます。これは、可変個引数テンプレートとテンプレートパラメータを友達にすることができるためです。(2010年以前のMSVCでは、テンプレートフレンド指定子を拡張機能として使用できるため、このソリューションをシミュレートできます)。
// each class has its own unique key only it can create
// (it will try to get friendship by "showing" its passkey)
template <typename T>
class passkey
{
private:
friend T; // C++0x, MSVC allows as extension
passkey() {}
// noncopyable
passkey(const passkey&) = delete;
passkey& operator=(const passkey&) = delete;
};
// functions still require a macro. this
// is because a friend function requires
// the entire declaration, which is not
// just a type, but a name as well. we do
// this by creating a tag and specializing
// the passkey for it, friending the function
#define EXPAND(pX) pX
// we use variadic macro parameters to allow
// functions with commas, it all gets pasted
// back together again when we friend it
#define PASSKEY_FUNCTION(pTag, pFunc, ...) \
struct EXPAND(pTag); \
\
template <> \
class passkey<EXPAND(pTag)> \
{ \
private: \
friend pFunc __VA_ARGS__; \
passkey() {} \
\
passkey(const passkey&) = delete; \
passkey& operator=(const passkey&) = delete; \
}
// meta function determines if a type
// is contained in a parameter pack
template<typename T, typename... List>
struct is_contained : std::false_type {};
template<typename T, typename... List>
struct is_contained<T, T, List...> : std::true_type {};
template<typename T, typename Head, typename... List>
struct is_contained<T, Head, List...> : is_contained<T, List...> {};
// this class can only be created with allowed passkeys
template <typename... Keys>
class allow
{
public:
// check if passkey is allowed
template <typename Key>
allow(const passkey<Key>&)
{
static_assert(is_contained<Key, Keys>::value,
"Passkey is not allowed.");
}
private:
// noncopyable
allow(const allow&) = delete;
allow& operator=(const allow&) = delete;
};
//////////////////////////////////////////////////////////
// test!
//////////////////////////////////////////////////////////
struct bar;
struct baz;
struct qux;
void quux(int, double);
// make a passkey for quux function
PASSKEY_FUNCTION(quux_tag, void quux(int, double));
struct foo
{
void restricted1(allow<bar>) {}
void restricted2(allow<bar, baz>) {}
void restricted3(allow<quux_tag>) {}
} f;
struct bar
{
void run(void)
{
// passkey works
f.restricted1(passkey<bar>());
f.restricted2(passkey<bar>());
}
};
struct baz
{
void run(void)
{
// passkey does not work
/* f.restricted1(passkey<baz>()); */
// passkey works
f.restricted2(passkey<baz>());
}
};
struct qux
{
void run(void)
{
// own passkey does not work,
// cannot create any required passkeys
/* f.restricted1(passkey<qux>()); */
/* f.restricted2(passkey<qux>()); */
/* f.restricted1(passkey<bar>()); */
/* f.restricted2(passkey<baz>()); */
}
};
void quux(int, double)
{
// passkey words
f.restricted3(passkey<quux_tag>());
}
void corge(void)
{
// cannot use quux's passkey
/* f.restricted3(passkey<quux_tag>()); */
}
int main(){}
ボイラープレートコードだけで、ほとんどの場合(すべての非機能の場合!)、特別に定義する必要はありません。このコードは、クラスと関数の任意の組み合わせのイディオムを一般的かつ単純に実装します。
呼び出し元は、関数に固有のパスキーを作成または記憶しようとする必要はありません。むしろ、各クラスには独自のパスキーがあり、関数はパスキーパラメーターのテンプレートパラメーターで許可するパスキーを選択するだけです(追加の定義は必要ありません)。これにより、両方の欠点が解消されます。呼び出し元は、独自のパスキーを作成してそれを使用して呼び出すだけで、他に何も心配する必要はありません。