* passkey 패턴 : public 멤버 함수의 접근 권한 관리로 public 함수를 non-public 하게
Project/Win32API로 던그리드 모작

* passkey 패턴 : public 멤버 함수의 접근 권한 관리로 public 함수를 non-public 하게

Woon2World :: Programming & Art Life

 

 

배경

싱글톤 패턴을 구현할 때 부모 클래스의 protected 멤버로 정의된 클래스를 자식 클래스의 특정한 멤버 함수가 매개변수로 받으면, 그 멤버 함수는 public이긴 하지만 그 부모 클래스에서만 이용 가능한, reverse-protected인 양 동작하는 멤버 함수가 된다는 사실을 이용했다

 

이를 조금 더 일반화하여, public인 멤버 함수를 특정 지역에서만 사용 가능하도록 하는 디자인 패턴이 혹시 이미 존재하지 않을까 했다

 

그 결과 다음 두 글을 발견할 수 있었다

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Friendship_and_the_Attorney-Client

 

More C++ Idioms/Friendship and the Attorney-Client - Wikibooks, open books for an open world

From Wikibooks, open books for an open world Jump to navigation Jump to search Control the granularity of access to the implementation details of a class A friend declaration in C++ gives complete access to the internals of a class. Friend declarations are

en.wikibooks.org

 

https://stackoverflow.com/questions/3217390/clean-c-granular-friend-equivalent-answer-attorney-client-idiom/3218920#3218920

 

clean C++ granular friend equivalent? (Answer: Attorney-Client Idiom)

Why does C++ have public members that anyone can call and friend declarations that expose all private members to given foreign classes or methods but offer no syntax to expose particular members to...

stackoverflow.com

 

위의 글은 각각 Attorney-Client Idiom, Passkey Pattern을 소개하는 내용이다

 

Attorney-Client Idiom은 중간에 매개 클래스를 하나 두어서 특정한 클래스가 Client 클래스에 friend로 전달하고 싶은 부분만 매개 클래스에 따로 정의하여, 처음의 클래스가 매개 클래스를 friend 선언, 매개 클래스가 Client 클래스를 friend 선언하는 것으로 원하는 부분만 공개하는 전략이다

원하는 정보만 지나가게 하므로 매개 클래스두 클래스를 잇는 다리이자 필터 역할을 한다고 볼 수 있다

 

Passkey Pattern은 접근을 관리하고 싶은 함수의 매개변수에 태그 구조체를 추가하여, 그 태그 구조체는 특정 클래스를 friend 선언하고 생성자를 private화 해 friend로서 태그 구조체를 생성할 수 있는 권한을 가진 클래스만이 해당 함수를 호출할 수 있도록 하는 전략이다 

 

Attorney-Client Idiom은 일반성이 부족하고 friend로 전하길 원하는 부분만 따로 빼낸 클래스 하나를 통째로 더 선언해야 하지만, 사용자 코드에서 함수를 호출할 때 인자를 하나씩 더 적어야 하는 불편함을 없앨 수 있고

 

Passkey Pattern은 반대로 사용자 코드에서 함수를 호출할 때 인자를 하나씩 더 적어야 하지만, 태그 구조체가 템플릿에 의해 일반화될 수 있고 클래스 사양과 관계없이 생성자 한 줄만 적으면 태그 구조체 구현이 가능하다

 

이젠 템플릿에 익숙하고 코드를 한 줄이라도 더 줄이길 원하는 나는 Attorney-Client Idiom보단 Passkey Pattern이 더 매력적으로 보였다

 

 

 

아이디어

  • 태그 구조체 템플릿을 준비한다
  • 태그 구조체 템플릿은 자신의 생성자를 private 멤버로 두고, 템플릿 매개변수로 전달된 클래스를 friend 선언한다
  • 접근 제한이 필요한 public 멤버 함수는 접근을 허용할 클래스로 태그 구조체 템플릿을 인스턴스화하여 매개변수로 받는다

 

태그 구조체 템플릿

template <typename T>
class key { friend T;    key() = default; };
cs

 

실제 사용

template <typename T>
class key { friend T;    key() = default; };
 
class Camera;        // 전방선언
 
class Character
{
public:
 
    // ...
 
   POINT getpos(key<Camera>const
    {
        // 위치를 반환한다
    }
 
    // ...
};
 
class Camera
{
public:
 
    // ...
 
    void attach(const Character* chara)
    {
       auto pos = chara->getpos({});
        // 카메라를 캐릭터에 붙인다
    }
 
    // ...
};
 
// ...
cs

 

 

  • Character의 getpos()는 key<Camera>를 매개변수로 받으므로, Camera만이 접근 권한을 가지고 있다.
  • Camera에서 Character의 getpos()를 호출할 때, getpos({}); 과 같은 간결하고 아름다운 문법으로 호출 가능하다.
    ( getpos({}) 는 getpos(key<Camera>{}) 와 같다! 컴파일러의 놀라운 추론 능력에 박수를.. )

 

한 걸음 더

https://stackoverflow.com/questions/3324898/can-we-increase-the-re-usability-of-this-key-oriented-access-protection-pattern

 

Can we increase the re-usability of this key-oriented access-protection pattern?

Can we increase the re-usability for this key-oriented access-protection pattern: class SomeKey { friend class Foo; // more friends... ? SomeKey() {} // possibly non-copyable too...

stackoverflow.com

 

범용성을 늘린 버전의 코드도 있다.

 

 

class Scene;        // 전방선언
class Camera;
class Weapon;
 
class Character {
public:
 
    // ...
 
    void render(allow<Scene>) {}
    POINT getpos(allow<Camera, Weapon>) {}
 
    // ...
};
 
class Scene {
public:
 
    // ...
 
    void render() {
        chara->render(passkey<Scene>{});
 
        // ...   
    }
 
    // ...
 
private:
    Character* chara;
    Camera* camera;
 
    // ...
};
 
 
class Camera {
public:
 
    // ...
 
    void attach(Character* chara) {
        auto pos = chara->getpos(passkey<Camera>{});
        // 카메라를 캐릭터에 붙인다
    }
 
    // ...
};
 
class Weapon {
public:
 
    // ...
 
    void attach(Character* chara) {
        auto pos = chara->getpos(passkey<Weapon>{});
        // 무기를 캐릭터에 붙인다
    }
 
    // ...
};
cs

 

 

함수가 접근을 허용할 목록allow를 통해 관리할 수 있다

allow의 템플릿 인자 목록에 명시된 클래스만이 해당 함수를 호출할 수 있다

 

allow 목록을 매개변수로 받는 함수를 호출할 때는 passkey를 통해 자신의 타입을 key로 전달한다

해당 key가 allow 목록에 명시되어 있지 않다면 호출이 허용되지 않는다

 

 

 

allow와 passkey의 구체적인 구현은 다음과 같다

 

 

#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 {
    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;
};
cs

 

 

contains는 첫번째 템플릿 매개변수가 나머지 템플릿 매개변수들 사이에 포함되는지 검사한다

contains<T1, T2, T3>는 T1이 {T2, T3} 에 존재하지 않으므로 false_type

contains<My1, My2, My3, My4, My1, My5>는 My1이 {My2, My3, My4, My1, My5}에 존재하므로 true_type이다

마찬가지로 contains<A, B, A, B>도 A가 {B, A, B}에 존재하므로 true_type이다

 

이 contains를 이용해 allow를 구현한다

전달된 key가 allow목록 안에 contain되는지를 검사하는 것이다

 

 

 

시사점

접근 권한 관리는 프로젝트의 크기가 커지면 커질 수록 훨씬 중요해진다

기존에 게임을 만든 경험 속에서, get()과 set()을 최대한 안 써서 코드 캡슐화를 통해 유지보수성을 늘리려 했지만

클래스가 서로의 정보를 요구하는 설계가 거의 강제되는 게임 특성상 get(), set()을 안 쓸 수가 없었다

 

이와 같은 접근 권한 관리를 통해 불가피하게 get(), set()을 public하게 쓰는 상황에서도 특정 클래스만이 그 함수를 사용할 수 있도록 보호할 수 있다 (아마 protected가 이래서 나온 말일 거다)

 

get()과 set()을 제외하더라도 다양한 메서드에서 다른 클래스의 private 멤버를 요구하게 될 일이 발생하는데, 그럴 때마다 이 패턴을 활용하면 훨씬 안정성있는 코드를 작성할 수 있다

 

 

'Project > Win32API로 던그리드 모작' 카테고리의 다른 글

게임 프레임 관리  (0) 2021.08.19
빌더 패턴 - 직사각형 만들기  (0) 2021.08.18
c++식 랜덤값 얻기의 개선안  (0) 2021.08.15
더블버퍼링  (0) 2021.08.14
싱글톤 패턴  (0) 2021.08.14