Api design for c++ pattern

975 views

Published on

Api Design For C++ Ch3 Pattern

Published in: Software
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
975
On SlideShare
0
From Embeds
0
Number of Embeds
11
Actions
Shares
0
Downloads
0
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Api design for c++ pattern

  1. 1. 박진호
  2. 2.  Pimpl 이디엄  싱글톤과 팩토리 메서드  프록시, 어댑터와 퍼사드  옵저버
  3. 3.  내부의 구체적인 코드를 public 헤더 파일로 부터 완전히 숨김  기본적으로 private 멤버 변수와 함수를 .cpp 파일에 위치 시킴
  4. 4. 공개 인터페이스 Public: Function1() Function2() Protected: Function3() Private: Impl* pImpl 내부 구현 Public: PrivateFuntion1() PrivateFunction2() PrivateData1 privateData2 pImpl 이디엄은 숨겨진 구현 클래스를 내부 포인터로 가리키는 공개 클래스를 말함
  5. 5. // autotimer.h #ifdef WIN32 #include <windows.h> #else #include <sys/time.h> #endif #include <string> class AutoTimer { public: /// Create a new timer object with a human readable name explicit AutoTimer(const std::string &name); /// On destruction, the timer reports how long it was alive AutoTimer(); private: // Return how long the object has been alive double GetElapsed() const; std::string mName; #ifdef WIN32 DWORD mStartTime; #else struct timeval mStartTime; #endif
  6. 6. // autotimer.h #include <string> class AutoTimer { public: explicit AutoTimer(const std::string &name); AutoTimer(); private: class Impl; Impl *mImpl; };
  7. 7. #include "autotimer.h" #include <iostream> #if_WIN32 #include <windows.h> #else #include <sys/time.h> #endif class AutoTimer::Impl { public: double GetElapsed() const { #ifdef _WIN32 return (GetTickCount() - mStartTime) / 1e3; #else struct timeval end_time; gettimeofday(&end_time, NULL); double t1 = mStartTime.tv_usec / 1e6 + mStartTime.tv_sec; double t2 = end_time.tv_usec / 1e6 + end_time.tv_sec; return t2-t1; #endif } std::string mName; #ifdef _WIN32 DWORD mStartTime; #else struct timeval mStartTime; #endif
  8. 8. AutoTimer::AutoTimer(const std::string &name) : mImpl(new AutoTimer::Impl()) { mImpl->mName name; #ifdef _WIN32 mImpl->mStartTime = GetTickCount(); #else gettimeofday(&mImpl->mStartTime, NULL); #endif } AutoTimer::AutoTimer() { std::cout << mImpl >mName << ": took " << mImpl >GetElapsed() << " secs" << std::endl; delete mImpl; mImpl = NULL; }
  9. 9. AutoTimer::AutoTimer(const std::string &name) : mImpl(new AutoTimer::Impl()) { mImpl->mName name; #ifdef _WIN32 mImpl->mStartTime = GetTickCount(); #else gettimeofday(&mImpl->mStartTime, NULL); #endif } AutoTimer::AutoTimer() { std::cout << mImpl >mName << ": took " << mImpl >GetElapsed() << " secs" << std::endl; delete mImpl; mImpl = NULL; }
  10. 10. 1. Private 멤버 변수만을 포함한다. 2. Private 멤버 변수와 함수를 포함한다. 3. Public 클래스의 모든 함수와 대응되는 함수 를 Impl 클래스에 구현한다.
  11. 11.  기본적으로 얕은 복사가 사용됨.  다음의 2가지 선택사항을 취해 얕은 복사 시 예상치 못한 오류 발생 예방  클래스를 복사할 수 없게 만듦 ▪ Private으로 복사 생성자 선언 및 할당 연산자 선언  깊은 복사 기능을 명시적으로 정의
  12. 12.  정보 은닉  #define prive public  #include “yourapi.h”  #undef private  의존성 제거  Windows.h 및 sys/time.h에 대한 의존성 없앰  빠른 컴파일  Api 계층 구조가 줄어듦  뛰어난 이진 호환성  Pimpl 이디엄 포함한 객체의 크기는 절대 바뀌지 않음  지연 할당  Pimpl 클래스는 필요할 때 생성되기 때문에 네트워크 연결과 같은 제한된 또는 많은 비용이 드는 리소스를 사용할 때 유용
  13. 13.  Pimpl 구현 객체를 메모리에 할당하고 해제해야하는 문제.  포인터의 크기 때문에 객체 크기 역시 늘어나서 모든 변수 접근에 필요한 간접 참조의 성능 문제 발생  New와 delete 호출에 따른 추가적인 비용 ▪ 빠른 Pimpl 이디엄(new와 delete 연산자를 오버로드 해서 적은 메모리를 사 용하도록 고정된 크기의 메모리 할당자를 사용하는 효율적인 메모리 사용 방 법)  코드를 읽는 것, 디버깅 하는것도 어려워 짐.  상수 함수 안에 있는 변수들은 분리된 객체 안에 존재하기 때문에 컴파일러는 이 변수들의 값 변화를 더 이상 감지할 수없음. void PimpledObject::ConstMethod() const { mImpl->mName = "string changed by a const method"; }
  14. 14.  페이지 112 ~ 114 참고
  15. 15.  어떻게 싱글톤 객체를 할당할 것인가?  언제 싱글톤 객체를 파괴 시킬 것인가?  쓰레드 안정성을 지원하는 가?  쭉정이(알맹이가 없는) 싱글턴을 사용하면 어떻게 되는가?
  16. 16.  발전된 형태의 전역 변수  프로그램 상에서 하나의 인스턴스만 있고 두번째 인스턴스를 만들 수 없는 기능  쓰임에 따라 최선의 방법들이 달라짐
  17. 17.  기본 생성자를 private에 선언  외부에서 new로 객체 생성 불가  Singleton* pObject = new Singleton();  컴파일 에러  복사 생성자, 복사 대입연산자 도 private에 선언  복사로 인한 생성 불가  정의를 하지 않고 선언만 함 (정의가 있으면 멤버함수 혹은 프렌드 함수가 호출 할 수 있기 때문에 정의 하지 않음)  복사 생성자 ▪ Singleton object1( Singleton::GetInstance() );  컴파일 에러  복사 대입 연산자 ▪ Singleton& object1 = Singleton::GetInstance(); ▪ Singleton& object2 = Singleton::GetInstance(); ▪ object1 = object2;  컴파일 에러
  18. 18.  .h  .cpp
  19. 19.  Static 클래스 멤버 변수는 static 전역 변수처럼 프로그램 시작 시 main() 함수 호출 이전에 초기화  싱글톤 객체 사용하지 않더라도 무조건 생성되기 때문 에 비효율적  정적 객체는 다른 전역객체의 생성자에서 참고하고 싶 은 경우 문제 발생 할 수 있음  C++ 표준에서는 전역 객체들의 생성 순서에 대해 명확하게 정 의하고 있지 않기 때문  Main() 함수가 실행 하기 전에만 생성되면 됨  어떤 전역 객체의 생성자에서 위 싱글톤 객체를 참조하려고 할 경우 싱글톤 객체가 생성되기 전인 경우 문제 발생 할 수 있 음
  20. 20.  .h  .cpp
  21. 21.  최초 GetInstace() 를 호출하는 시점에 객체가 생성  한번도 해당 객체를 생성하지 않으면 객체 생 성 하지 않으므로 자원을 효율적으로 사용  다른 전역 객체의 생성자에서 참조하는 것도 가능  프로그램이 종료되는 순간 동적 객체는 자동으 로 해제되기 때문에 굳이 명시적으로 해제할 필요 없음
  22. 22.  프로그램을 종료하기 위해 exit() 가 수행하는 cleanup 과정 (C++ 기준) 1. 현재 thread의 thread storage duration 내에 있는 object 들을 모두 소멸 ( C++11 only) 2. static storage duration의 object들을 소멸 1. Static storage duration 2. Thread storage duration 3. Automatic storage duration 4. Dynamic storage duration 3. atexit 에 등록되어 있는 함수들을 호출 4. 모든C stream(stdin, stdout, stderr 등등) 과 tmpfile 에 의해 생성된 파일들을 비우고 닫음 5. 제어권이 host environment 로 넘어감
  23. 23.  두번째 싱글톤 객체가 반드시 프로그램 종료 시 반납해야 하는 외부 시스템 자원을 사용하 는 경우 사용  싱글턴 객체를 이용하여, OS 리소스 를 만들고 해제 하지 않는다면 "리소스 릭:Resource Leak" 이 발생  싱글턴 객체의 소멸자에 리소스 해제 기능을 놓고, 싱글턴 객체를 파괴 함 으로써 이러한 문제 해결  Ateexit() 함수를 사용  다른 전역 객체의 소멸자를 이용
  24. 24.  #include <stdlib.h>  int atexit(void (*func)(void));  함수 호출 성공 시 0, 실패 시 0 아닌 값 반환.  반환형과 매개변수 형이 void로 선언된 함수의 이름이(주소 값이) atexit함수의 인자로 전달  인자로 전달된 함수가 프로그램 종료 시 자동으로 호출되며, 이렇게 자 동으로 호출되어야 할 함수는 32개 이상 등록할 수 있음.  atexit 함수의 특성  등록된 순서의 역순으로 호출  atexit 함수를 통해서 등록된 함수는 프로그램이 정상적으로 종료될 때에만 호출
  25. 25.  .h .cpp
  26. 26.  .h .cpp
  27. 27.  명시적 해제 작업을 피하기 위해 static 지역 객 체 사용 1. 지역 static 객체는 전역 static 객체와 달리 해당 함수를 처음 호출 하는 시점에 초기화 2. 위 객체를 한번도 사용하지 않으면 생성되지 않음 3. Static 이므로 프로그램 종료 시 까지 객체가 유지 4. 종료시에는 자동으로 소멸자 호출
  28. 28.  로그 객체, 키보드 객체, 모니터 객체가 존재하며, 네번째 싱글톤으로 구성 (1)키보드 객체를 생성하고, 모니터 객체를 생성 하는 중 실패하여, (2)로그 객체를 생성하여 로그를 찍음 (3)그리고 프로그램이 종료 작업에 들어 가게 됨.  에러 상황 (4) 로그 객체는 먼저 삭제됨. (5)키보드 객체를 파괴할 때, 실패하여, (6)로그 객체를 이용해 로그를 찍으려 함 하지만, 로그 객체는 이미 파괴된 후이므로, 쭉정이 싱글톤에 접근하게 되어, 크래 쉬가 발생  상황 분석  static 을 이용하기 때문에, 스택 형태로 메모리에 올라와져 있어, 키보드 객체가 파괴되기 전 로 그 객체 부터 파괴.  왜냐하면 로그 객체가 키보드 객체 후에 만들어졌기 때문
  29. 29.  네번째 싱글톤 객체를 다른 전역 객체의 소멸 자에서 사용하려고 하면 문제 발생  C++ 표준에서는 전역 객체들의 생성 순서만 명시하 지 않은 것이 아니라 소멸 순서에 대해서도 명시해 놓지 않음  어떤 전역 객체가 소멸자에서 저 싱글톤 객체를 사 용하려고 할 때 싱글톤 객체가 먼저 소멸했다면 문 제가 발생 (참조 무효화)
  30. 30.  싱글톤 참조 시 해당 객체의 소멸 여부를 파악 하고 만약 소멸했다면 되살림  싱글턴의 파괴 시점을 조작
  31. 31.  Static 지역 변수의 특징  메모리의 생성은 프로그램이 static 지역변수 가 포함된 함수를 호출해야 생성  프로그램 종료 시점에 메모리 파괴가 일어난다 해도 그 공간은 빈 공간으로 남아 있음( 다른것으로 채워지지 않음)  위 특징 사용.  프로그램 종료 시점에 그 메모리 공간에 다시 쓰기 위 해 replacement new를 사용  파괴 시점을 제어하기 위해 std::atexit함수 사용
  32. 32.  Replacement new  이미 할당된 메모리 공간에 생성자만 호출  일반 new처럼 사용하되 인자로 메모리가 할당되어있는 포인터를 넘겨줌  내부적으로 void* operator new(size_t, void*) 원형의 new를 호출  Ex)  void* pBase = malloc(sizeof(CBase));  new(pBase) CBase;  메모리를 재할당 하지 않음  기존 데이터가 그대로 남아있고, 생성자에서 초기화하는 데이터 만 초기화  가상함수 테이블을 초기화  메모리풀링 등에서 유용하게 사용
  33. 33.  .h
  34. 34.  .cpp Static 객체의 생성/소멸 시점에 대해 정확히 파악해서 싱글톤 객체를 전 역 객체의 생성/ 소멸자에서 마구잡이로 참조하는 일이 없도록 주의해서 프로그래밍 해야함
  35. 35.  고려해야 할 사항  싱글턴 객체의 생성 동기화
  36. 36.  고려해야 할 사항  싱글턴 객체의 생성 동기화
  37. 37.  멀티 스레드 환경 지원 안함
  38. 38.  쓰레드A가 GetInstance() 함수 진입  1번 조건 체크 하고 다음 줄 실행 전에 중지  쓰레드 B가 GetInstance() 함수에 진입  쓰레드 B는 s_pInstance가 NULL 이므로 1번 계속 진행.   쓰레드 B는 2번 실행하여 객체 생성 후 호출자에게 instance 리턴  쓰레드A는 다시 진행 됨. 2번이 다시 호출되어 또다른 인스턴스 생성 A쓰레드 B쓰레드 2번 1번 2번 1번
  39. 39. B쓰레드
  40. 40. 널일 경우에만 락을 획득 하기 위함 위 조건 체크와 락을 얻을 때 사이에 다른 스 레드가 먼저 들어와서 pInstance를 초기화 할 수 있기 때문에 한번더 null 인지 체크
  41. 41.  s_pInstance = new LazySingleton(); 의미  1. Singleton 객체를 담기 위한 메모리를 할당  2. 할당된 메모리에 Singleton 객체를 생성  3. s_pInstance가 할당된 메모리를 가리키도록 함  컴파일러가 이 순서대로 수행되도록 제한하지 않음. 컴파일러는 때때로 스텝 2와 스텝 3사이의 순서를 바꾸기도 함
  42. 42.  A쓰레드에서 s_pInstance가 할당된 메모리 를 가리키도록 하고 그 후에 할당된 메모리 에 객체를 생성 해야 하는데 할당 전에  B 쓰레드에서 싱글턴 객체를 바로 사용할 경 우 껍데기를 접근하여 문제 발생  (싱글턴 객체는 메모리를 가리키기만 하고 실제 객체객체 생성은 안된 상황)
  43. 43.  Volatile 한정 지시자 사용
  44. 44.  volatile로 선언된 개체(변수, 메모리 위치, 코드)는 optimize 룰을 적용하지 말라.  ( Objects declared as volatile are not used in certain optimizations ( 출처 - MSDN ))  컴파일러가 효율 등을 고려해 optimize 관점에서 코 드를 임의로 변경(의미 없는 코드 삭제나 실행 순서 변경)을 시키지 않음  volatile로 선언된 개체는 메모리에 할당되고 작업 요 청시 직접(해당 주소에), 바로(효율을 위한 지연 없 이) 처리된다.
  45. 45.  volatile 키워드를 사용해야 할 곳이 있다면 C++11에 서는 atomic 변수를 사용  C++11에서는 '서로 다른 스레드 간에 순서 관계가 정 의 되지 않은 메모리 읽기 쓰기 조작은 data race에 의한 알 수 없는 동작을 한다' 라고 되어 있음.  즉 C++11에서 volatile 변수는 Memory Barrier 동작을 한다고 명시하지 않음  메모리 바리어는 대부분 낮은 수준의 기계어에서 사 용되며 여러 장치가 공유하는 메모리를 사용하는 데 쓰임
  46. 46.  Lazy Instance 모델 대신 코드가 실행되는 시점, main() 이 호출되기 전이나 뮤텍스 잠금으로 API가 초기화 시점에 초기화  정적 초기화 static Singleton& foo = Singleton::GetInstance();  명시적 API 초기화 VoidAPIInitialize() { Mutex mutex; ScopredLock(&mutex); Singleton::GetInstance(); }
  47. 47.  의존성 삽입 class MyClass { public: MyClass() : mDatabase( new Database ("mydb, "localhost", "user", "pass")) {} private: Database* mDatabase; } class MyClass { public: MyClass(Database* db) : mDatabase(db) {} private: Database* mDatabase; }
  48. 48.  모노스테이트 패턴  싱글톤의 문제는 전역 상태를 유지하고 접근하는데서 발생 // monostate.h class MonoState { public: int GetTheAnswer() const { return sAnswer; } private: static int sAnswer; }; // monostate.cpp int MonoState::sAnswer = 42;  세션 문맥 사용
  49. 49.  C++ 생성자에서의 제약 사항  결과 값 리턴 불가  명명 규칙의 제약  정적 형식의 객체 생성  가상 생성자의 부재  팩토리 메서드는 위 제약사항에 구애받지 않음
  50. 50. // renderer.h #include <string> class IRenderer { public: virtual IRenderer() {} virtual bool LoadScene(const std::string &filename) 0; virtual void SetViewportSize(int w, int h) 0; virtual void SetCameraPosition(double x, double y, double z) 0; virtual void SetLookAt(double x, double y, double z) 0; virtual void Render() 0; };
  51. 51. // rendererfactory.h #include "renderer.h" #include <string> class RendererFactory { public: IRenderer *CreateRenderer(const std::string &type); }; // rendererfactory.cpp #include "rendererfactory.h" #include "openglrenderer.h" #include "directxrenderer.h" #include "mesarenderer.h“ IRenderer *RendererFactory::CreateRenderer(const std::string &type) { if (type "opengl") return new OpenGLRenderer(); if (type "directx") return new DirectXRenderer(); if (type "mesa") return new MesaRenderer(); return NULL;
  52. 52.  사용자가 원하는 타입의 인스턴스를 런타임 시에 생성하기 때문에 유연성 제공  상속 클래스의 헤더 파일은 팩토리를 구현 하는 .cpp 파일에만 존재하며 public으로 선 언된 RenererFacory.h 파일에는 포함하지 않 음  그러나 이것은 사용자에게 새로운 그래픽 처리기를 제공하지 못함.
  53. 53.  팩토리 메서드와 상속 클래스간의 연결 관 계를 느슨하게 만듦  런타임시에 새로운 상속 클래스를 추가할 수 있도록 타입 이름을 객체 생성 콜백 함수 에 연결시키는 맵을 팩토리 클래스에서 사 용
  54. 54. // rendererfactory.h #include "renderer.h" #include <string> #include <map> class RendererFactory { public: typedef IRenderer *(*CreateCallback)(); static void RegisterRenderer(const std::string &type, CreateCallback cb); static void UnregisterRenderer(const std::string &type); static IRenderer *CreateRenderer(const std::string &type); private: typedef std::map<std::string, CreateCallback> CallbackMap; static CallbackMap mRenderers; };
  55. 55. #include "rendererfactory.h" // instantiate the static variable in RendererFactory RendererFactory::CallbackMap RendererFactory::mRenderers; void RendererFactory::RegisterRenderer(const std::string &type, CreateCallback cb) { mRenderers[type] cb; } void RendererFactory::UnregisterRenderer(const std::string &type) { mRenderers.erase(type); } IRenderer *RendererFactory::CreateRenderer(const std::string &type) { CallbackMap::iterator it mRenderers.find(type); if (it ! mRenderers.end()) { // call the creation callback to construct this derived type return (it >second)(); } return NULL; }
  56. 56. class UserRenderer : public IRenderer { public: UserRenderer() {} bool LoadScene(const std::string &filename) { return true; } void SetViewportSize(int w, int h) {} void SetCameraPosition(double x, double y, double z) {} void SetLookAt(double x, double y, double z) {} void Render() { std::cout << "User Render" << std::endl; } static IRenderer *Create() { return new UserRenderer(); } }; int main(int, char **) { // register a new renderer RendererFactory::RegisterRenderer("user", UserRenderer::Create); // create an instance of our new renderer IRenderer *r RendererFactory::CreateRenderer("user"); r->Render(); delete r; return 0; }
  57. 57.  한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환 어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해 서 쓸 수 있음.
  58. 58. public class Adapter implements target{ request(){ translatedRequest(); } } 클라이언트와 어댑티는 서로 분리 서로 상대방에 대해서 전혀 모름.
  59. 59. 어댑터에서 타겟 인터페이스를구현 어댑터는 어댑티로 구성 모든 요청은 어댑티에게 위임 public class Adapter implements target{ request(){ specificRequest(); } } 객체 구성(Composition) 사용 어댑티의 어떤 서브클래스에 대해서도 어댑터 를 쓸 수 있다는 장점 클라이언트를 특정 구현이 아닌 인터페이스에 연결 - 여러 어댑터 사용 가능 - 타겟 인터페이스만 제대로 지키다면 다른 구현 추가 가능
  60. 60. 다중상속을 사용해서 어댑 터를 어댑티와 타켓 클랙스 모두의 서브클래스로 만듦. ※ 자바에서는 실제로 동작하지 않는 코드입니다 public class Adapter extends Target, Adaptee{ public void request(){ specificRequest(); } }
  61. 61. - 어댑티 전체는 다시 구현하지 않아도 됨 - 어댑티의 행동을 오버라이딩 가능 - 구성을 사용하기 때문에 그 서 브클래스에 대해서도 어댑터 역할을 할 수 있음
  62. 62.  어떤 서브시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공 퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브시스템을 더 쉽게 사용할 수 있 음
  63. 63. 클래스 다이어그램 1. 일대다 관계 (one-to-many) • 상태를 저장하고 지배하는 것은 주제 객체. • 옵저버는 여러 개가 있을 수 있지만, 주제 객체에서 상태가 바뀌었다는 것을 알려주기를 기다리는 주제에 의존 적인 성질을 가짐. 2. 의존성 1. 옵저버는 주제 객체가 데이터를 갱신해 주기를 기다리는 입장 이기 때문에 의존성을 가짐. 2. 이런 방식으로 인해 깔끔한 객체지향 디자인을 만들 수 있음. update() <interface> Observer regosterObserver() { … } removeObserver() { … } notifyObservers() { … } getState( ) setState( ) ConcreateSubject regosterObserver() removeObserver() notifyObservers() <interface> Subject update() //기타 해당 옵저버의 메소 드 ConcreateObserver 옵저버 주제 옵저버가 될 가능성 이 있는 객체는 해당 인터페이스 상속. 옵저버를 등록/탈퇴/공지 하는 기능 상태를 설정하는 메소드가 필요 • 옵저버 패턴 한 객체의 상태가 바뀌면 그 객체 에 의존하는 다른 객체들한테 연락 이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의 존성을 정의
  64. 64.  옵저버 패턴에서는 주제와 옵저버가 느슨하게 결합되어 있는 객체 디자인을 제공.  주제가 옵저버에 대해 아는 것은 옵저버가 특정 인터페이스를 구현(implements) 한다는 것 뿐.  옵저버는 언제든지 새로 추가할 수 있음  새로운 형식의 옵저버를 추가하려고 할 때도 주제를 전혀 변경 할 필요가 없음.  주제와 옵저버는 서로 독립적으로 재사용할 수 있음.  주제나 옵저버가 바뀌더라도 서로한테 영향을 미치지 않음.
  65. 65. 감사합니다

×