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

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

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

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

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

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

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


person Georg Fritzsche    schedule 24.07.2010    source источник
comment
Если оставить в стороне гипотетическое членство в друзьях и не вдаваться в бессмысленный пример foo-bar - можете ли вы предоставить практический пример, в котором использование этого шаблона превосходит какой-либо другой более простой метод, более того, каковы будут его эквиваленты в C # или Java?   -  person    schedule 24.07.2010
comment
@Beh: всякий раз, когда вам нужно ограничить доступ к ресурсам, но вы не хотите предоставлять привилегированным клиентам полный доступ (что редко требуется) для сохранения инкапсуляции. Связанная статья «Адвокат-клиент» содержит более подробную информацию. В качестве практического примера возьмем, например, случай, как этот - Класс-оболочка не предназначен для публичного использования, он должен быть непрозрачным помощником. Бесплатная функция, которая его использует, имеет полный доступ, хотя ей нужен только доступ к оболочкам get_function_pointer().   -  person Georg Fritzsche    schedule 24.07.2010
comment
Я пытаюсь использовать это с классом шаблона (один из друзей ключа - метод из класса шаблона) и не могу понять, как управлять зависимостями, поскольку я не могу разделить объявление и определение шаблона. Правильно ли я пришел к выводу, что я не могу использовать это, чтобы предоставить доступ к ключу метода класса шаблона?   -  person iheanyi    schedule 16.04.2014
comment
Я решил продвинуться вперед в этом вопросе, создав класс, который объединяет класс шаблона в ссылку. Затем я могу преобразовать класс шаблона в класс без шаблона, разделить объявление и определение и продолжить работу в обычном режиме, практически не внося никаких дополнительных сложностей или лишнего кода.   -  person iheanyi    schedule 16.04.2014


Ответы (3)


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

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

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

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

person GManNickG    schedule 24.07.2010
comment
Мне нравится, куда вы собираетесь, но (конечно, но;)) теперь мы вернулись к созданию ключа для каждого типа (на данный момент я еще не могу перейти на функции C ++ 0x)? Кроме того, хотя у вашего подхода есть и другие преимущества, мне нравится простота предыдущей версии. Он не нуждается в поддерживающей структуре и, вероятно, имеет меньше проблем с прохождением обзоров. - person Georg Fritzsche; 25.07.2010
comment
@Georg: В самом деле, я думаю, что в C ++ 03 лучший способ - это признать, что вам нужно вручную (ну, это упрощено с помощью макросов) создавать ключи доступа для каждой коллекции друзей и следовать этому. Я не уверен, что вы имеете в виду под обзорами, но я считаю, что C ++ 03 намного проще, просто добавьте служебный материал в какой-нибудь passkey.hpp заголовок и никогда больше не смотрите на него. :) Макрос намного чище, чем делать его вручную. Хотя мне очень нравится версия C ++ 0x; тот факт, что последний параметр может буквально читать, позволяет то, то и то, а типы просто говорят, что вот мой ключ, впусти меня - это мечта. - person GManNickG; 25.07.2010
comment
Правда, читаемость заблокированных методов C ++ 0x хороша :) Под обзорами я имею в виду более консервативные рекомендации или рецензентов кода - если бы мы могли кодировать все так, как мы этого хотим, это было бы другое дело (в основном обращаясь к макросы здесь). - person Georg Fritzsche; 25.07.2010
comment
@Georg: О, я всегда придумываю свои собственные рекомендации. :) - person GManNickG; 25.07.2010
comment
Тогда наслаждайтесь этим, пока он длится;) Мне определенно нравится улучшение читабельности с возможностью напрямую передавать их passkey для типов классов. - person Georg Fritzsche; 27.07.2010
comment
Структура allow очень хороша, я не знал о шаблоне friend для C ++ 0x (я все еще кодирую в основном на C ++ 03 ...), и он очень хорошо справляется с задачей! - person Matthieu M.; 29.07.2010

Я прочитал много комментариев о невозможности копирования. Многие думали, что его нельзя не копировать, потому что тогда мы не сможем передать его в качестве аргумента функции, которой нужен ключ. А некоторые даже удивились, что это сработало. На самом деле этого не должно быть, и, по-видимому, это связано с некоторыми компиляторами 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).

person Jeremy B.    schedule 14.07.2014
comment
Это не ответ, поэтому его не следует публиковать как единое целое. - person ThreeFx; 14.07.2014
comment
Хорошо, что мне делать? Случайно просматривайте сообщения на StackOverflow в надежде получить достаточно ответов, чтобы повысить свою репутацию, чтобы я мог комментировать? Вы не читали первую часть, где я извинился за то, что не смог прокомментировать, я полагаю;) У меня должно быть 50 представителей, чтобы комментировать, и я не могу, и это просто глупо, если люди не должны уметь делать одно из двух вещи с самого начала, он отвечает, а не комментирует = / - person Jeremy B.; 14.07.2014
comment
Сделав что-то неправильно, волшебным образом не исправится, если вы извинитесь за это. Как вы сказали, вы можете попытаться ответить на некоторые вопросы, пока не наберете 50 повторений. 50 повторений - это не так, так что вы сможете добиться этого относительно быстро;) - person ThreeFx; 14.07.2014
comment
Пытался дать на него как можно больше ответа. Просто чтобы указать на это, у меня действительно не так много времени, чтобы на самом деле создавать контент на StackOverflow, чтобы увеличить свою репутацию, я думаю, что, как многие просят, должны быть небольшие изменения в обработке комментариев. Люди могут, как и я в этом случае, захотеть привнести что-то новое, что-то, что не является ответом, а частично ответом, таким образом они хотят внести свой вклад в рост StackExchange, но они не могут! Я надеюсь, что когда-нибудь будет найдена хорошая идея - person Jeremy B.; 26.07.2014
comment
Я очень надеюсь, потому что проблема (меня это не беспокоит, я все равно могу получить 50 репутации с небольшим количеством времени) заключается в том, что со временем вопросы усложняются, а, следовательно, и ответы до такой степени, что когда-нибудь только ниша люди будут задавать вопросы или отвечать на них, не позволяя новичкам получить какую-либо репутацию. Тем не менее, у этих новичков (именно так развивается человечество) может быть одна небольшая, но блестящая и свежая идея (я иногда видел это), которая улучшит то, что очень хорошо известно. - person Jeremy B.; 26.07.2014
comment
Верно. Приближается время, когда StackOverflow завершит свою работу. На этом этапе больше не нужно задавать вопросов. (Но будут разрешены новые вопросы, чтобы у нас было что отрицать и закрывать.) - person Praxeolitic; 10.10.2014

Отличный ответ от @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;
}
person NameRakes    schedule 18.04.2016