c – 我们能否增加这种面向密钥的访问保护模式的可重用性?

我们可以增加this key-oriented access-protection pattern的可重用性:

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成语不同:

>它可以比律师 – 客户更简洁(因为它不涉及通过第三类代理)
>它可以允许授权访问权限
> …但它对原始类也更具侵入性(每个方法一个虚拟参数)

(在this question开发了一个侧面讨论,因此我打开了这个问题.)

解决方法:

我喜欢这个成语,它有可能变得更干净,更具表现力.

在标准C 03中,我认为以下方式最容易使用且最通用. (虽然没有太大的改进.大多数情况下都会重复自我.)因为template parameters cannot be friends,我们必须使用宏来定义密钥:

// 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(){}

此方法有两个缺点:1)调用者必须知道它需要创建的特定密钥.虽然一个简单的命名方案(function_key)基本上消除了它,但它仍然可以是一个抽象清洁器(并且更容易). 2)虽然使用宏并不是很困难,但可能会看起来有点难看,需要一组passkey-definitions.然而,在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(){}

仅注意样板代码,在大多数情况下(所有非功能情况!)都不需要特别定义.该代码通常简单地为类和函数的任何组合实现惯用语.

调用者不需要尝试创建或记住特定于该函数的密钥.相反,每个类现在都有自己唯一的密码,函数只需在passkey参数的模板参数中选择允许的密钥(不需要额外的定义);这消除了这两个缺点.调用者只是创建自己的密钥并使用它调用,而不需要担心其他任何事情.

上一篇:为什么C#不提供C风格的“朋友”关键字?


下一篇:关于friend友元的注意事项