Подтвердить что ты не робот

Можем ли мы увеличить повторное использование этой ключевой схемы защиты доступа?

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

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

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

Чтобы избежать повторных недоразумений, этот шаблон отличается от "Адвокат-клиент" :

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

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

4b9b3361

Ответ 1

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

В стандартном С++ 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 passkey
    /* f.restricted3(foo::restricted3_key()); */
}

int main(){}

Этот метод имеет два недостатка: 1) вызывающий должен знать конкретный ключ доступа, который необходимо создать. Хотя простая схема именования (function_key) в основном устраняет ее, она все равно может быть одним абстракционным чистящим средством (и проще). 2) Хотя это не очень сложно использовать макрос, можно рассматривать как немного уродливое, требуя блока определения passkey. Однако улучшения этих недостатков нельзя сделать в С++ 03.


В С++ 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 passkey
    /* f.restricted3(passkey<quux_tag>()); */
}

int main(){}

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

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

Ответ 2

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

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

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

Ответ 3

Отличный ответ от @GManNickG. Многому научился. В попытке заставить его работать, нашел пару опечаток. Полный пример повторяется для ясности. Мой пример заимствует функцию "Ключи в ключах..." из Проверьте, содержит ли пакет параметров С++ 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;
}