Можем ли мы повысить возможность повторного использования этого ориентированного на ключ шаблона защиты доступа? - PullRequest
26 голосов
/ 24 июля 2010

Можем ли мы увеличить возможность повторного использования для этого ориентированного на ключ шаблона защиты доступа :

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-Клиент идиома:

  • Может быть более кратким, чем Attorney-Client (так как он не предполагает проксирование через третий класс)
  • Может разрешить делегирование доступаправа
  • ... но это также более навязчиво для исходного класса (один фиктивный параметр на метод)

(дополнительное обсуждение, разработанное в этого вопроса, поэтому я открываю этот вопрос.)

Ответы [ 3 ]

25 голосов
/ 24 июля 2010

Мне нравится эта идиома, и она может стать намного чище и выразительнее.

В стандарте 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(){}

ThisУ метода есть два недостатка: 1) вызывающий должен знать конкретный пароль, который ему нужно создать.В то время как простая схема именования (function_key) в основном устраняет ее, она все же может быть одним из очистителей абстракций (и проще).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(){}

В большинстве случаев обратите внимание только на стандартный код ( all )не функциональные случаи!) больше ничего не нужно специально определять.Этот код в общем и просто реализует идиому для любой комбинации классов и функций.

Вызывающей стороне не нужно пытаться создать или запомнить ключ доступа, специфичный для функции.Скорее, у каждого класса теперь есть свой уникальный пароль, и функция просто выбирает, какой пароль он разрешит в параметрах шаблона параметра passkey (никаких дополнительных определений не требуется);это устраняет оба недостатка.Вызывающий просто создает свой собственный пароль и звонит с ним, и ему не нужно больше ни о чем беспокоиться.

1 голос
/ 18 апреля 2016

Отличный ответ от @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;
}
1 голос
/ 14 июля 2014

Я прочитал много комментариев о невозможности копирования.Многие думали, что это не должно быть недоступно для копирования, потому что тогда мы не сможем передать его в качестве аргумента функции, которой нужен ключ.И некоторые даже удивились, что это сработало.Ну, это действительно не должно и, по-видимому, связано с некоторыми компиляторами Visual C ++, так как раньше у меня была та же странность, но больше не было с Visual C ++ 12 (Studio 2013).

Но вот что мы можем улучшитьбезопасность с «базовой» не копируемостью.Boost-версия слишком большая, поскольку она полностью предотвращает использование конструктора копирования и, следовательно, слишком много для того, что нам нужно.Нам действительно нужно сделать конструктор копирования закрытым, но не без реализации.Конечно, реализация будет пустой, но она должна существовать.Я недавно спросил, кто в этом случае вызывает copy-ctor (в данном случае кто вызывает конструктор копирования SomeKey при вызове ProtectedMethod).Ответ заключался в том, что, очевидно, стандарт гарантирует, что вызывающий метод вызывает -ctor, что, честно говоря, выглядит вполне логично.Таким образом, делая copy-ctor закрытым, мы позволяем функции друзей (protected Bar и granted Foo) вызывать ее, что позволяет Foo вызывать ProtectedMethod, поскольку она использует передачу аргумента значения, но это также препятствует тому, чтобы кто-либо вышел из сферы действия Foo.

Делая это, даже если коллега-разработчик попытается проявить смекалку с кодом, ему фактически придется заставить Foo выполнять работудругой класс не сможет получить ключ, и, скорее всего, он поймет свои ошибки почти 100% времени таким образом (надеюсь, в противном случае он слишком новичок, чтобы использовать этот шаблон, или ему следует прекратить разработку: P).

...