28

このキー指向のアクセス保護パターンの再利用性を高めることはできますか?

class SomeKey { 
    friend class Foo;
    // more friends... ?
    SomeKey() {} 
    // possibly non-copyable too
};

class Bar {
public:
    void protectedMethod(SomeKey); // only friends of SomeKey have access
};

継続的な誤解を避けるために、このパターンは弁護士/依頼人のイディオムとは異なります。

  • Attorney-Clientよりも簡潔にすることができます(3番目のクラスを介したプロキシが含まれないため)
  • アクセス権の委任を許可できます
  • ...しかし、元のクラスではより煩わしいものです(メソッドごとに1つのダミーパラメーター)

(この質問で開発されたサイドディスカッションなので、この質問を開きます。)

4

3 に答える 3

27

私はこのイディオムが好きで、もっとクリーンで表現力豊かになる可能性があります。

標準の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(){}

ボイラープレートコードだけで、ほとんどの場合(すべての非機能の場合!)、特別に定義する必要はありません。このコードは、クラスと関数の任意の組み合わせのイディオムを一般的かつ単純に実装します。

呼び出し元は、関数に固有のパスキーを作成または記憶しようとする必要はありません。むしろ、各クラスには独自のパスキーがあり、関数はパスキーパラメーターのテンプレートパラメーターで許可するパスキーを選択するだけです(追加の定義は必要ありません)。これにより、両方の欠点が解消されます。呼び出し元は、独自のパスキーを作成してそれを使用して呼び出すだけで、他に何も心配する必要はありません。

于 2010-07-24T11:56:54.123 に答える
3

コピー不可についてのコメントをたくさん読みました。キーを必要とする関数に引数として渡すことができないため、多くの人がコピーできないようにすべきではないと考えていました。そして、それが機能していることに驚いた人さえいました。以前は同じ奇妙さを持っていましたが、Visual C ++ 12(Studio 2013)ではなくなったため、実際にはそうではなく、一部のVisualC++コンパイラに関連しているようです。

しかし、これが重要です。「基本的な」非コピー性でセキュリティを強化できます。Boostバージョンは、コピーコンストラクターの使用を完全に妨げるため、多すぎます。したがって、必要なものには少し多すぎます。必要なのは、実際にはコピーコンストラクターをプライベートにすることですが、実装なしではありません。もちろん、実装は空になりますが、存在している必要があります。私は最近、そのような場合に誰がcopy-ctorを呼び出しているのかを尋ねました(この場合、誰が呼び出し時にのコピーコンストラクターをSomeKey呼び出しますProtectedMethod)。-ctor答えは、明らかに標準は、正直に非常に論理的に見えるを呼び出すのはメソッド呼び出し元であることを保証するということでした。したがって、copy-ctorプライベートにすることで、友達機能(protected Barおよびgranted Foo)がそれを呼び出すことができるようになり、したがってFooProtectedMethodFoo値引数の受け渡しを使用するためですが、の範囲外の人を防ぐこともできます。

これを行うことで、仲間の開発者がコードを賢くプレイしようとしても、実際にFooその仕事をしなければならず、別のクラスがキーを取得できなくなり、間違いをほぼ100%認識できる可能性があります。この方法での時間の(うまくいけば、そうでなければ、彼はこのパターンを使用するにはあまりにも初心者であるか、開発を停止する必要があります:P)。

于 2014-07-14T17:13:58.107 に答える
1

@GManNickGからの素晴らしい答え。たくさん学んだ。それを機能させるために、いくつかのタイプミスを見つけました。明確にするために完全な例を繰り返しました。私の例では、C++0xパラメータパックに@snk_kidによって投稿されたタイプが含まれているかどうかを確認するから「containsKeyinKeys...」関数を借用しています。

#include<type_traits>
#include<iostream>

// identify if type is in a parameter pack or not
template < typename Tp, typename... List >
struct contains : std::false_type {};

template < typename Tp, typename Head, typename... Rest >
struct contains<Tp, Head, Rest...> :
  std::conditional< std::is_same<Tp, Head>::value,
  std::true_type,
  contains<Tp, Rest...>
  >::type{};

template < typename Tp >
struct contains<Tp> : std::false_type{};


// everything is private!
template <typename T>
class passkey {
private:
  friend T;
  passkey() {}

  // noncopyable
  passkey(const passkey&) = delete;
  passkey& operator=(const passkey&) = delete;
};


// what keys are allowed
template <typename... Keys>
class allow {
public:
  template <typename Key>
  allow(const passkey<Key>&) {
    static_assert(contains<Key, Keys...>::value, "Pass key is not allowed");
  }

private:
  // noncopyable
  allow(const allow&) = delete;
  allow& operator=(const allow&) = delete;
};


struct for1;
struct for2;

struct foo {
  void restrict1(allow<for1>) {}
  void restrict2(allow<for1, for2>){}
} foo1;
struct for1 {
  void myFnc() {
    foo1.restrict1(passkey<for1>());
  }
};
struct for2 {
  void myFnc() {
    foo1.restrict2(passkey<for2>());
   // foo1.restrict1(passkey<for2>()); // no passkey
  }
};


void main() {
  std::cout << contains<int, int>::value << std::endl;
  std::cout << contains<int>::value << std::endl;
  std::cout << contains<int, double, bool, unsigned int>::value << std::endl;
  std::cout << contains<int, double>::value << std::endl;
}
于 2016-04-18T05:59:48.643 に答える