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

  • 它可以比 Attorney-Client 更简洁(因为它不涉及通过第三类代理)
  • 它可以允许访问权限的委派
  • ...但它对原始类也更具侵入性(每个方法一个虚拟参数)

(在这个问题中展开了边讨论,因此我打开了这个问题。)

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

这种方法有两个缺点:1)调用者必须知道它需要创建的特定密钥。虽然一个简单的命名方案function_key(2)虽然使用宏不是很困难,但可以看作有点难看,需要一个密码定义块。但是,在 C++03 中无法对这些缺点进行改进。


在 C++0x 中,成语可以达到其最简单和最具表现力的形式。这是由于可变参数模板和允许模板参数成为朋友。(请注意,MSVC pre-2010 允许模板朋友说明符作为扩展;因此可以模拟此解决方案):

// 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++ 编译器有关,因为我以前也有过同样的怪癖,但不再使用 Visual C++12 (Studio 2013)。

但事情是这样的,我们可以通过“基本”不可复制性来增强安全性。Boost 版本太多了,因为它完全阻止了复制构造函数的使用,因此对于我们需要的东西来说有点太多了。我们需要的实际上是将复制构造函数设为私有,但并非没有实现。当然实现是空的,但它必须存在。我最近问过在这种情况下谁在调用 copy-ctor(在这种情况下,谁在调用SomeKey时调用了复制构造函数ProtectedMethod)。答案是显然标准确保它是调用的方法调用者-ctor老实说这看起来很合乎逻辑。因此,通过设置copy-ctor私有,我们允许朋友函数(theprotected Bar和 the granted Foo)调用它,从而允许Foo调用ProtectedMethod因为它使用值参数传递,但它也防止任何人超出Foo' 范围。

通过这样做,即使一个开发人员试图巧妙地使用代码,他实际上也必须Foo完成这项工作,另一个类将无法获得密钥,并且他几乎 100% 会意识到自己的错误以这种方式(希望如此,否则他太初学者了,无法使用这种模式,否则他应该停止开发:P)。

于 2014-07-14T17:13:58.107 回答
1

@GManNickG 的好回答。学到了很多。在试图让它工作时,发现了几个错别字。为了清楚起见,重复了完整的示例。我的示例从检查 C++0x 参数包是否包含@snk_kid 发布的类型借用了“包含键中的键...”函数。

#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 回答