2010-07-24 6 views
23

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

+2

Гипотетический друг членство в стороне и не вдаваясь в бессмысленные примере Foo-баре - вы можете обеспечить «практический» пример, где использование этой модели превосходит какую-то другая более простую технику, кроме того, что сказал бы C# или Java эквиваленты этого быть? – 2010-07-24 12:21:29

+0

@Beh: Всякий раз, когда вам приходится ограничивать доступ к ресурсам, но не хотите предоставлять доступ к привилегированным клиентам (что редко требуется) для сохранения инкапсуляции. Связанная статья адвоката-клиента подробно рассматривается. В качестве практического примера, например, случай [как это] (http://stackoverflow.com/questions/3321283/c0x-lambda-how-can-i-pass-as-a-parameter/3321822#3321822) - класс-оболочка не для публики использовать, он должен быть непрозрачным помощником. Свободная функция, которая использует его, имеет полный доступ, хотя ему нужно только получить доступ к оболочкам 'get_function_pointer()'. –

+0

Я пытаюсь использовать это с классом шаблона (один друг ключа - это метод из класса шаблона) и не может понять, как управлять зависимостями, поскольку я не могу отделить объявление и определение шаблона. Правильно ли я заключил, что я не могу использовать это, чтобы предоставить доступ к ключевому методу класса шаблона? – iheanyi

ответ

22

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

В стандартном 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. Однако улучшения этих недостатков нельзя сделать в 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(){} 

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

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

+0

Мне нравится, куда вы направляетесь, но (конечно же, но;)) теперь мы возвращаемся к созданию ключа для каждого типа (на данный момент я еще не могу воспользоваться функциями C++ 0x)? Кроме того, хотя ваш подход имеет и другие преимущества, мне нравится простота прежней версии. Он не нуждается в поддерживающей структуре и, вероятно, имеет меньше проблем с получением отзывов. –

+0

@Georg: Действительно, я думаю, что в C++ 03 лучший способ - принять, что вам нужно вручную (ну, проще с макросами) сделать passkeys на коллекцию друзей и пойти с ним. Я не уверен, что вы имеете в виду под обзорами, но я нахожу C++ 03 намного проще, просто выбросьте утилиту в какой-нибудь заголовок 'passkey.hpp' и больше не смотрите на нее. :) Макрос намного чище, чем делать это вручную. Мне действительно нравится версия C++ 0x; сам факт, последний параметр может буквально читать «разрешить это, это и то», и что типы просто говорят «вот мой ключ, позвольте мне» - это сон. – GManNickG

+0

Правда, читаемость на заблокированных методах C++ 0x хороша :) С отзывами я имею в виду более консервативные справочники или рецензенты кода - если бы мы могли кодировать все так, как мы хотим, это было бы другим вопросом (в основном, макросы здесь). –

1

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

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

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

+0

Это не ответ и поэтому не должен быть размещен как один. – ThreeFx

+0

Итак, что мне делать? Случайно просматривайте сообщения в StackOverflow, чтобы надеяться получить достаточное количество ответов, чтобы получить мой ответ, чтобы я мог комментировать? Вы не читали первую часть, в которой я извинился за то, что не смог комментировать, я думаю;) У меня должно быть 50 представителей, чтобы комментировать и не могу, и это просто глупо, если люди не могут сделать один из двух вещи с самого начала, он отвечает, а не комментирует =/ –

+0

Выполнение чего-то неправильно не волшебным образом исправит себя, если вы извинитесь за это. Как вы сказали, вы можете попытаться ответить на некоторые вопросы, пока не нажмете 50 очков репутации. 50 rep не * это * много, поэтому вы должны быть в состоянии достичь таких относительно быстрых;) – ThreeFx

1

Отличный ответ от @GManNickG. Многому научился. В попытке заставить его работать, нашел пару опечаток. Полный пример повторяется для ясности. Мой пример заимствует «содержит ключ в ключах ...» от Check if C++0x parameter pack contains a type, отправленный @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; 
}