c++ - क्या हम इस कुंजी उन्मुख पहुंच-सुरक्षा पैटर्न की पुनः उपयोगिता को बढ़ा सकते हैं?




design-patterns idioms (2)

@GManNickG से बढ़िया जवाब। बहुत कुछ सीख लिया। इसे काम करने की कोशिश में, कुछ टाइपो मिले। स्पष्टता के लिए पूर्ण उदाहरण दोहराया गया। मेरा उदाहरण borrows में "Key in Keys ..." फ़ंक्शन से चेक करें यदि 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;
}

क्या हम इस कुंजी उन्मुख पहुंच-सुरक्षा पैटर्न के लिए पुन: उपयोगिता को बढ़ा सकते हैं:

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 मुहावरे से अलग है:

  • यह अटॉर्नी-क्लाइंट से अधिक संक्षिप्त हो सकता है (क्योंकि इसमें किसी तीसरे वर्ग के माध्यम से प्रॉक्सीइंग शामिल नहीं है)
  • यह अभिगम अधिकारों के प्रतिनिधिमंडल की अनुमति दे सकता है
  • ... लेकिन यह मूल वर्ग (प्रति विधि एक डमी पैरामीटर) पर भी अधिक घुसपैठ कर रहा है

( इस सवाल में एक साइड-चर्चा विकसित हुई, इस प्रकार मैं इस सवाल को खोल रहा हूं।)


मुझे यह मुहावरे पसंद है, और इसमें अधिक स्वच्छ और अधिक अभिव्यक्तिपूर्ण होने की क्षमता है।

मानक सी ++ 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) हालांकि मैक्रो का उपयोग करना बहुत मुश्किल नहीं है, लेकिन थोड़ा बदसूरत के रूप में देखा जा सकता है, पासकी-परिभाषाओं के ब्लॉक की आवश्यकता होती है। हालांकि, इन दोषों में सुधार सी ++ 03 में नहीं किया जा सकता है।

सी ++ 0x में, मुहावरे अपने सबसे सरल और सबसे अभिव्यक्तिपूर्ण रूप तक पहुंच सकता है। यह दोनों वैरिएड टेम्पलेट्स के कारण है और टेम्पलेट पैरामीटर को दोस्त बनने की इजाजत है। (ध्यान दें कि एमएसवीसी प्री-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(){}

केवल बॉयलरप्लेट कोड के साथ नोट करें, ज्यादातर मामलों में ( सभी गैर-फ़ंक्शन केस!) कुछ भी विशेष रूप से परिभाषित करने की आवश्यकता नहीं है। यह कोड सामान्य रूप से और कक्षाओं और कार्यों के किसी भी संयोजन के लिए मुहावरे को लागू करता है।

कॉलर को फ़ंक्शन के लिए विशिष्ट पासकी बनाने या याद रखने की आवश्यकता नहीं है। इसके बजाय, प्रत्येक वर्ग में अब अपनी अनूठी पासकी है और फ़ंक्शन बस चुनता है कि कौन सी पासकी यह पासकी पैरामीटर के टेम्पलेट पैरामीटर (कोई अतिरिक्त परिभाषा आवश्यक नहीं है) में अनुमति देगी; यह दोनों दोषों को समाप्त करता है। कॉलर बस अपनी खुद की पासकी बनाता है और इसके साथ कॉल करता है, और किसी और चीज के बारे में चिंता करने की आवश्यकता नहीं है।





access-protection