아꿈사
http://cafe.naver.com/architect1
박진호
http://blog.naver.com/jinojjan
 Pimpl 이디엄
 싱글톤과 팩토리 메서드
 프록시, 어댑터와 퍼사드
 옵저버
 내부의 구체적인 코드를 public 헤더 파일로
부터 완전히 숨김
 기본적으로 private 멤버 변수와 함수를 .cpp
파일에 위치 시킴
공개 인터페이스
Public:
Function1()
Function2()
Protected:
Function3()
Private:
Impl* pImpl 내부 구현
Public:
PrivateFuntion1()
PrivateFunction2()
PrivateData1
privateData2
pImpl 이디엄은 숨겨진 구현 클래스를 내부 포인터로 가리키는 공개
클래스를 말함
// 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
// autotimer.h
#include <string>
class AutoTimer
{
public:
explicit AutoTimer(const std::string &name);
AutoTimer();
private:
class Impl;
Impl *mImpl;
};
#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
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;
}
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;
}
1. Private 멤버 변수만을 포함한다.
2. Private 멤버 변수와 함수를 포함한다.
3. Public 클래스의 모든 함수와 대응되는 함수
를 Impl 클래스에 구현한다.
 기본적으로 얕은 복사가 사용됨.
 다음의 2가지 선택사항을 취해 얕은 복사 시
예상치 못한 오류 발생 예방
 클래스를 복사할 수 없게 만듦
▪ Private으로 복사 생성자 선언 및 할당 연산자 선언
 깊은 복사 기능을 명시적으로 정의
 정보 은닉
 #define prive public
 #include “yourapi.h”
 #undef private
 의존성 제거
 Windows.h 및 sys/time.h에 대한 의존성 없앰
 빠른 컴파일
 Api 계층 구조가 줄어듦
 뛰어난 이진 호환성
 Pimpl 이디엄 포함한 객체의 크기는 절대 바뀌지 않음
 지연 할당
 Pimpl 클래스는 필요할 때 생성되기 때문에 네트워크 연결과
같은 제한된 또는 많은 비용이 드는 리소스를 사용할 때 유용
 Pimpl 구현 객체를 메모리에 할당하고 해제해야하는 문제.
 포인터의 크기 때문에 객체 크기 역시 늘어나서 모든 변수 접근에
필요한 간접 참조의 성능 문제 발생
 New와 delete 호출에 따른 추가적인 비용
▪ 빠른 Pimpl 이디엄(new와 delete 연산자를 오버로드 해서 적은 메모리를 사
용하도록 고정된 크기의 메모리 할당자를 사용하는 효율적인 메모리 사용 방
법)
 코드를 읽는 것, 디버깅 하는것도 어려워 짐.
 상수 함수 안에 있는 변수들은 분리된 객체 안에 존재하기 때문에
컴파일러는 이 변수들의 값 변화를 더 이상 감지할 수없음.
void PimpledObject::ConstMethod() const
{
mImpl->mName = "string changed by a const method";
}
 페이지 112 ~ 114 참고
 어떻게 싱글톤 객체를 할당할 것인가?
 언제 싱글톤 객체를 파괴 시킬 것인가?
 쓰레드 안정성을 지원하는 가?
 쭉정이(알맹이가 없는) 싱글턴을 사용하면
어떻게 되는가?
 발전된 형태의 전역 변수
 프로그램 상에서 하나의 인스턴스만 있고
두번째 인스턴스를 만들 수 없는 기능
 쓰임에 따라 최선의 방법들이 달라짐
 기본 생성자를 private에 선언
 외부에서 new로 객체 생성 불가
 Singleton* pObject = new Singleton();  컴파일 에러
 복사 생성자, 복사 대입연산자 도 private에 선언
 복사로 인한 생성 불가
 정의를 하지 않고 선언만 함
(정의가 있으면 멤버함수 혹은 프렌드 함수가 호출 할 수 있기 때문에 정의
하지 않음)
 복사 생성자
▪ Singleton object1( Singleton::GetInstance() );  컴파일 에러
 복사 대입 연산자
▪ Singleton& object1 = Singleton::GetInstance();
▪ Singleton& object2 = Singleton::GetInstance();
▪ object1 = object2;  컴파일 에러
 .h
 .cpp
 Static 클래스 멤버 변수는 static 전역 변수처럼 프로그램
시작 시 main() 함수 호출 이전에 초기화
 싱글톤 객체 사용하지 않더라도 무조건 생성되기 때문
에 비효율적
 정적 객체는 다른 전역객체의 생성자에서 참고하고 싶
은 경우 문제 발생 할 수 있음
 C++ 표준에서는 전역 객체들의 생성 순서에 대해 명확하게 정
의하고 있지 않기 때문
 Main() 함수가 실행 하기 전에만 생성되면 됨
 어떤 전역 객체의 생성자에서 위 싱글톤 객체를 참조하려고
할 경우 싱글톤 객체가 생성되기 전인 경우 문제 발생 할 수 있
음
 .h
 .cpp
 최초 GetInstace() 를 호출하는 시점에 객체가
생성
 한번도 해당 객체를 생성하지 않으면 객체 생
성 하지 않으므로 자원을 효율적으로 사용
 다른 전역 객체의 생성자에서 참조하는 것도
가능
 프로그램이 종료되는 순간 동적 객체는 자동으
로 해제되기 때문에 굳이 명시적으로 해제할
필요 없음
 프로그램을 종료하기 위해 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 로 넘어감
 두번째 싱글톤 객체가 반드시 프로그램 종료
시 반납해야 하는 외부 시스템 자원을 사용하
는 경우 사용
 싱글턴 객체를 이용하여, OS 리소스 를 만들고 해제
하지 않는다면 "리소스 릭:Resource Leak" 이 발생
 싱글턴 객체의 소멸자에 리소스 해제 기능을 놓고,
싱글턴 객체를 파괴 함 으로써 이러한 문제 해결
 Ateexit() 함수를 사용
 다른 전역 객체의 소멸자를 이용
 #include <stdlib.h>
 int atexit(void (*func)(void));
 함수 호출 성공 시 0, 실패 시 0 아닌 값 반환.
 반환형과 매개변수 형이 void로 선언된 함수의 이름이(주소 값이)
atexit함수의 인자로 전달
 인자로 전달된 함수가 프로그램 종료 시 자동으로 호출되며, 이렇게 자
동으로 호출되어야 할 함수는 32개 이상 등록할 수 있음.
 atexit 함수의 특성
 등록된 순서의 역순으로 호출
 atexit 함수를 통해서 등록된 함수는 프로그램이 정상적으로 종료될 때에만
호출
 .h
.cpp
 .h
.cpp
 명시적 해제 작업을 피하기 위해 static 지역 객
체 사용
1. 지역 static 객체는 전역 static 객체와 달리 해당 함수를
처음 호출 하는 시점에 초기화
2. 위 객체를 한번도 사용하지 않으면 생성되지 않음
3. Static 이므로 프로그램 종료 시 까지 객체가 유지
4. 종료시에는 자동으로 소멸자 호출
 로그 객체, 키보드 객체, 모니터 객체가 존재하며, 네번째 싱글톤으로 구성
(1)키보드 객체를 생성하고, 모니터 객체를 생성 하는 중 실패하여,
(2)로그 객체를 생성하여 로그를 찍음
(3)그리고 프로그램이 종료 작업에 들어 가게 됨.
 에러 상황
(4) 로그 객체는 먼저 삭제됨.
(5)키보드 객체를 파괴할 때, 실패하여,
(6)로그 객체를 이용해 로그를 찍으려 함
하지만, 로그 객체는 이미 파괴된 후이므로, 쭉정이 싱글톤에 접근하게 되어, 크래
쉬가 발생
 상황 분석
 static 을 이용하기 때문에, 스택 형태로 메모리에 올라와져 있어, 키보드 객체가 파괴되기 전 로
그 객체 부터 파괴.
 왜냐하면 로그 객체가 키보드 객체 후에 만들어졌기 때문
 네번째 싱글톤 객체를 다른 전역 객체의 소멸
자에서 사용하려고 하면 문제 발생
 C++ 표준에서는 전역 객체들의 생성 순서만 명시하
지 않은 것이 아니라 소멸 순서에 대해서도 명시해
놓지 않음
 어떤 전역 객체가 소멸자에서 저 싱글톤 객체를 사
용하려고 할 때 싱글톤 객체가 먼저 소멸했다면 문
제가 발생 (참조 무효화)
 싱글톤 참조 시 해당 객체의 소멸 여부를 파악
하고 만약 소멸했다면 되살림
 싱글턴의 파괴 시점을 조작
 Static 지역 변수의 특징
 메모리의 생성은 프로그램이 static 지역변수 가 포함된
함수를 호출해야 생성
 프로그램 종료 시점에 메모리 파괴가 일어난다 해도 그
공간은 빈 공간으로 남아 있음( 다른것으로 채워지지
않음)
 위 특징 사용.
 프로그램 종료 시점에 그 메모리 공간에 다시 쓰기 위
해 replacement new를 사용
 파괴 시점을 제어하기 위해 std::atexit함수 사용
 Replacement new
 이미 할당된 메모리 공간에 생성자만 호출
 일반 new처럼 사용하되 인자로 메모리가 할당되어있는 포인터를
넘겨줌
 내부적으로 void* operator new(size_t, void*) 원형의 new를 호출
 Ex)
 void* pBase = malloc(sizeof(CBase));
 new(pBase) CBase;
 메모리를 재할당 하지 않음
 기존 데이터가 그대로 남아있고, 생성자에서 초기화하는 데이터
만 초기화
 가상함수 테이블을 초기화
 메모리풀링 등에서 유용하게 사용
 .h
 .cpp
Static 객체의 생성/소멸 시점에 대해 정확히 파악해서 싱글톤 객체를 전
역 객체의 생성/ 소멸자에서 마구잡이로 참조하는 일이 없도록 주의해서
프로그래밍 해야함
 고려해야 할 사항
 싱글턴 객체의 생성 동기화
 고려해야 할 사항
 싱글턴 객체의 생성 동기화
 멀티 스레드 환경 지원 안함
 쓰레드A가 GetInstance() 함수 진입
 1번 조건 체크 하고 다음 줄 실행 전에 중지
 쓰레드 B가 GetInstance() 함수에 진입
 쓰레드 B는 s_pInstance가 NULL 이므로 1번 계속 진행.

 쓰레드 B는 2번 실행하여 객체 생성 후 호출자에게 instance 리턴
 쓰레드A는 다시 진행 됨. 2번이 다시 호출되어 또다른 인스턴스 생성
A쓰레드 B쓰레드
2번
1번
2번
1번
B쓰레드
널일 경우에만 락을 획득 하기 위함
위 조건 체크와 락을 얻을 때 사이에 다른 스
레드가 먼저 들어와서 pInstance를 초기화 할
수 있기 때문에 한번더 null 인지 체크
 s_pInstance = new LazySingleton(); 의미
 1. Singleton 객체를 담기 위한 메모리를 할당
 2. 할당된 메모리에 Singleton 객체를 생성
 3. s_pInstance가 할당된 메모리를 가리키도록 함
 컴파일러가 이 순서대로 수행되도록 제한하지
않음. 컴파일러는 때때로 스텝 2와 스텝 3사이의
순서를 바꾸기도 함
 A쓰레드에서 s_pInstance가 할당된 메모리
를 가리키도록 하고 그 후에 할당된 메모리
에 객체를 생성 해야 하는데 할당 전에
 B 쓰레드에서 싱글턴 객체를 바로 사용할 경
우 껍데기를 접근하여 문제 발생
 (싱글턴 객체는 메모리를 가리키기만 하고
실제 객체객체 생성은 안된 상황)
 Volatile 한정 지시자 사용
 volatile로 선언된 개체(변수, 메모리 위치, 코드)는
optimize 룰을 적용하지 말라.
 ( Objects declared as volatile are not used in certain
optimizations ( 출처 - MSDN ))
 컴파일러가 효율 등을 고려해 optimize 관점에서 코
드를 임의로 변경(의미 없는 코드 삭제나 실행 순서
변경)을 시키지 않음
 volatile로 선언된 개체는 메모리에 할당되고 작업 요
청시 직접(해당 주소에), 바로(효율을 위한 지연 없
이) 처리된다.
 volatile 키워드를 사용해야 할 곳이 있다면 C++11에
서는 atomic 변수를 사용
 C++11에서는 '서로 다른 스레드 간에 순서 관계가 정
의 되지 않은 메모리 읽기 쓰기 조작은 data race에
의한 알 수 없는 동작을 한다' 라고 되어 있음.
 즉 C++11에서 volatile 변수는 Memory Barrier 동작을
한다고 명시하지 않음
 메모리 바리어는 대부분 낮은 수준의 기계어에서 사
용되며 여러 장치가 공유하는 메모리를 사용하는 데
쓰임
 Lazy Instance 모델 대신 코드가 실행되는 시점,
main() 이 호출되기 전이나 뮤텍스 잠금으로
API가 초기화 시점에 초기화
 정적 초기화
static Singleton& foo = Singleton::GetInstance();
 명시적 API 초기화
Void APIInitialize()
{
Mutex mutex;
ScopredLock(&mutex);
Singleton::GetInstance();
}
 의존성 삽입
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;
}
 모노스테이트 패턴
 싱글톤의 문제는 전역 상태를 유지하고 접근하는데서 발생
// monostate.h
class MonoState
{
public:
int GetTheAnswer() const { return sAnswer; }
private:
static int sAnswer;
};
// monostate.cpp
int MonoState::sAnswer = 42;
 세션 문맥 사용
 C++ 생성자에서의 제약 사항
 결과 값 리턴 불가
 명명 규칙의 제약
 정적 형식의 객체 생성
 가상 생성자의 부재
 팩토리 메서드는 위 제약사항에 구애받지
않음
// 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;
};
// 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;
 사용자가 원하는 타입의 인스턴스를 런타임
시에 생성하기 때문에 유연성 제공
 상속 클래스의 헤더 파일은 팩토리를 구현
하는 .cpp 파일에만 존재하며 public으로 선
언된 RenererFacory.h 파일에는 포함하지 않
음
 그러나 이것은 사용자에게 새로운 그래픽
처리기를 제공하지 못함.
 팩토리 메서드와 상속 클래스간의 연결 관
계를 느슨하게 만듦
 런타임시에 새로운 상속 클래스를 추가할
수 있도록 타입 이름을 객체 생성 콜백 함수
에 연결시키는 맵을 팩토리 클래스에서 사
용
// 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;
};
#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;
}
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;
}
 한 클래스의 인터페이스를 클라이언트에서
사용하고자 하는 다른 인터페이스로 변환
어댑터를 이용하면 인터페이스 호환성 문제
때문에 같이 쓸 수 없는 클래스들을 연결해
서 쓸 수 있음.
public class Adapter implements target{
request(){
translatedRequest();
}
}
클라이언트와 어댑티는 서로 분리
서로 상대방에 대해서 전혀 모름.
어댑터에서 타겟 인터페이스를 구현
어댑터는 어댑티로 구성
모든 요청은 어댑티에게 위임
public class Adapter implements target{
request(){
specificRequest();
}
}
객체 구성(Composition) 사용
어댑티의 어떤 서브클래스에 대해서도 어댑터
를 쓸 수 있다는 장점
클라이언트를 특정 구현이 아닌 인터페이스에 연결
- 여러 어댑터 사용 가능
- 타겟 인터페이스만 제대로 지키다면 다른 구현
추가 가능
다중상속을 사용해서 어댑
터를 어댑티와 타켓 클랙스
모두의 서브클래스로 만듦.
※ 자바에서는 실제로 동작하지 않는 코드입니다
public class Adapter extends Target, Adaptee{
public void request(){
specificRequest();
}
}
- 어댑티 전체는 다시 구현하지 않아도 됨
- 어댑티의 행동을 오버라이딩 가능
- 구성을 사용하기 때문에 그 서
브클래스에 대해서도 어댑터
역할을 할 수 있음
 어떤 서브시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공
퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브시스템을 더 쉽게 사용할 수 있
음
클래스 다이어그램
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) 의
존성을 정의
 옵저버 패턴에서는 주제와 옵저버가 느슨하게 결합되어 있는
객체 디자인을 제공.
 주제가 옵저버에 대해 아는 것은 옵저버가 특정 인터페이스를
구현(implements) 한다는 것 뿐.
 옵저버는 언제든지 새로 추가할 수 있음
 새로운 형식의 옵저버를 추가하려고 할 때도 주제를 전혀 변경
할 필요가 없음.
 주제와 옵저버는 서로 독립적으로 재사용할 수 있음.
 주제나 옵저버가 바뀌더라도 서로한테 영향을 미치지 않음.
감사합니다

Api design for c++ ch3 pattern

  • 1.
  • 2.
     Pimpl 이디엄 싱글톤과 팩토리 메서드  프록시, 어댑터와 퍼사드  옵저버
  • 3.
     내부의 구체적인코드를 public 헤더 파일로 부터 완전히 숨김  기본적으로 private 멤버 변수와 함수를 .cpp 파일에 위치 시킴
  • 4.
    공개 인터페이스 Public: Function1() Function2() Protected: Function3() Private: Impl* pImpl내부 구현 Public: PrivateFuntion1() PrivateFunction2() PrivateData1 privateData2 pImpl 이디엄은 숨겨진 구현 클래스를 내부 포인터로 가리키는 공개 클래스를 말함
  • 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.
    // autotimer.h #include <string> classAutoTimer { public: explicit AutoTimer(const std::string &name); AutoTimer(); private: class Impl; Impl *mImpl; };
  • 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.
    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.
    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.
    1. Private 멤버변수만을 포함한다. 2. Private 멤버 변수와 함수를 포함한다. 3. Public 클래스의 모든 함수와 대응되는 함수 를 Impl 클래스에 구현한다.
  • 11.
     기본적으로 얕은복사가 사용됨.  다음의 2가지 선택사항을 취해 얕은 복사 시 예상치 못한 오류 발생 예방  클래스를 복사할 수 없게 만듦 ▪ Private으로 복사 생성자 선언 및 할당 연산자 선언  깊은 복사 기능을 명시적으로 정의
  • 12.
     정보 은닉 #define prive public  #include “yourapi.h”  #undef private  의존성 제거  Windows.h 및 sys/time.h에 대한 의존성 없앰  빠른 컴파일  Api 계층 구조가 줄어듦  뛰어난 이진 호환성  Pimpl 이디엄 포함한 객체의 크기는 절대 바뀌지 않음  지연 할당  Pimpl 클래스는 필요할 때 생성되기 때문에 네트워크 연결과 같은 제한된 또는 많은 비용이 드는 리소스를 사용할 때 유용
  • 13.
     Pimpl 구현객체를 메모리에 할당하고 해제해야하는 문제.  포인터의 크기 때문에 객체 크기 역시 늘어나서 모든 변수 접근에 필요한 간접 참조의 성능 문제 발생  New와 delete 호출에 따른 추가적인 비용 ▪ 빠른 Pimpl 이디엄(new와 delete 연산자를 오버로드 해서 적은 메모리를 사 용하도록 고정된 크기의 메모리 할당자를 사용하는 효율적인 메모리 사용 방 법)  코드를 읽는 것, 디버깅 하는것도 어려워 짐.  상수 함수 안에 있는 변수들은 분리된 객체 안에 존재하기 때문에 컴파일러는 이 변수들의 값 변화를 더 이상 감지할 수없음. void PimpledObject::ConstMethod() const { mImpl->mName = "string changed by a const method"; }
  • 14.
     페이지 112~ 114 참고
  • 15.
     어떻게 싱글톤객체를 할당할 것인가?  언제 싱글톤 객체를 파괴 시킬 것인가?  쓰레드 안정성을 지원하는 가?  쭉정이(알맹이가 없는) 싱글턴을 사용하면 어떻게 되는가?
  • 16.
     발전된 형태의전역 변수  프로그램 상에서 하나의 인스턴스만 있고 두번째 인스턴스를 만들 수 없는 기능  쓰임에 따라 최선의 방법들이 달라짐
  • 17.
     기본 생성자를private에 선언  외부에서 new로 객체 생성 불가  Singleton* pObject = new Singleton();  컴파일 에러  복사 생성자, 복사 대입연산자 도 private에 선언  복사로 인한 생성 불가  정의를 하지 않고 선언만 함 (정의가 있으면 멤버함수 혹은 프렌드 함수가 호출 할 수 있기 때문에 정의 하지 않음)  복사 생성자 ▪ Singleton object1( Singleton::GetInstance() );  컴파일 에러  복사 대입 연산자 ▪ Singleton& object1 = Singleton::GetInstance(); ▪ Singleton& object2 = Singleton::GetInstance(); ▪ object1 = object2;  컴파일 에러
  • 18.
  • 19.
     Static 클래스멤버 변수는 static 전역 변수처럼 프로그램 시작 시 main() 함수 호출 이전에 초기화  싱글톤 객체 사용하지 않더라도 무조건 생성되기 때문 에 비효율적  정적 객체는 다른 전역객체의 생성자에서 참고하고 싶 은 경우 문제 발생 할 수 있음  C++ 표준에서는 전역 객체들의 생성 순서에 대해 명확하게 정 의하고 있지 않기 때문  Main() 함수가 실행 하기 전에만 생성되면 됨  어떤 전역 객체의 생성자에서 위 싱글톤 객체를 참조하려고 할 경우 싱글톤 객체가 생성되기 전인 경우 문제 발생 할 수 있 음
  • 20.
  • 21.
     최초 GetInstace()를 호출하는 시점에 객체가 생성  한번도 해당 객체를 생성하지 않으면 객체 생 성 하지 않으므로 자원을 효율적으로 사용  다른 전역 객체의 생성자에서 참조하는 것도 가능  프로그램이 종료되는 순간 동적 객체는 자동으 로 해제되기 때문에 굳이 명시적으로 해제할 필요 없음
  • 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.
     두번째 싱글톤객체가 반드시 프로그램 종료 시 반납해야 하는 외부 시스템 자원을 사용하 는 경우 사용  싱글턴 객체를 이용하여, OS 리소스 를 만들고 해제 하지 않는다면 "리소스 릭:Resource Leak" 이 발생  싱글턴 객체의 소멸자에 리소스 해제 기능을 놓고, 싱글턴 객체를 파괴 함 으로써 이러한 문제 해결  Ateexit() 함수를 사용  다른 전역 객체의 소멸자를 이용
  • 24.
     #include <stdlib.h> int atexit(void (*func)(void));  함수 호출 성공 시 0, 실패 시 0 아닌 값 반환.  반환형과 매개변수 형이 void로 선언된 함수의 이름이(주소 값이) atexit함수의 인자로 전달  인자로 전달된 함수가 프로그램 종료 시 자동으로 호출되며, 이렇게 자 동으로 호출되어야 할 함수는 32개 이상 등록할 수 있음.  atexit 함수의 특성  등록된 순서의 역순으로 호출  atexit 함수를 통해서 등록된 함수는 프로그램이 정상적으로 종료될 때에만 호출
  • 25.
  • 26.
  • 27.
     명시적 해제작업을 피하기 위해 static 지역 객 체 사용 1. 지역 static 객체는 전역 static 객체와 달리 해당 함수를 처음 호출 하는 시점에 초기화 2. 위 객체를 한번도 사용하지 않으면 생성되지 않음 3. Static 이므로 프로그램 종료 시 까지 객체가 유지 4. 종료시에는 자동으로 소멸자 호출
  • 28.
     로그 객체,키보드 객체, 모니터 객체가 존재하며, 네번째 싱글톤으로 구성 (1)키보드 객체를 생성하고, 모니터 객체를 생성 하는 중 실패하여, (2)로그 객체를 생성하여 로그를 찍음 (3)그리고 프로그램이 종료 작업에 들어 가게 됨.  에러 상황 (4) 로그 객체는 먼저 삭제됨. (5)키보드 객체를 파괴할 때, 실패하여, (6)로그 객체를 이용해 로그를 찍으려 함 하지만, 로그 객체는 이미 파괴된 후이므로, 쭉정이 싱글톤에 접근하게 되어, 크래 쉬가 발생  상황 분석  static 을 이용하기 때문에, 스택 형태로 메모리에 올라와져 있어, 키보드 객체가 파괴되기 전 로 그 객체 부터 파괴.  왜냐하면 로그 객체가 키보드 객체 후에 만들어졌기 때문
  • 29.
     네번째 싱글톤객체를 다른 전역 객체의 소멸 자에서 사용하려고 하면 문제 발생  C++ 표준에서는 전역 객체들의 생성 순서만 명시하 지 않은 것이 아니라 소멸 순서에 대해서도 명시해 놓지 않음  어떤 전역 객체가 소멸자에서 저 싱글톤 객체를 사 용하려고 할 때 싱글톤 객체가 먼저 소멸했다면 문 제가 발생 (참조 무효화)
  • 30.
     싱글톤 참조시 해당 객체의 소멸 여부를 파악 하고 만약 소멸했다면 되살림  싱글턴의 파괴 시점을 조작
  • 31.
     Static 지역변수의 특징  메모리의 생성은 프로그램이 static 지역변수 가 포함된 함수를 호출해야 생성  프로그램 종료 시점에 메모리 파괴가 일어난다 해도 그 공간은 빈 공간으로 남아 있음( 다른것으로 채워지지 않음)  위 특징 사용.  프로그램 종료 시점에 그 메모리 공간에 다시 쓰기 위 해 replacement new를 사용  파괴 시점을 제어하기 위해 std::atexit함수 사용
  • 32.
     Replacement new 이미 할당된 메모리 공간에 생성자만 호출  일반 new처럼 사용하되 인자로 메모리가 할당되어있는 포인터를 넘겨줌  내부적으로 void* operator new(size_t, void*) 원형의 new를 호출  Ex)  void* pBase = malloc(sizeof(CBase));  new(pBase) CBase;  메모리를 재할당 하지 않음  기존 데이터가 그대로 남아있고, 생성자에서 초기화하는 데이터 만 초기화  가상함수 테이블을 초기화  메모리풀링 등에서 유용하게 사용
  • 33.
  • 34.
     .cpp Static 객체의생성/소멸 시점에 대해 정확히 파악해서 싱글톤 객체를 전 역 객체의 생성/ 소멸자에서 마구잡이로 참조하는 일이 없도록 주의해서 프로그래밍 해야함
  • 35.
     고려해야 할사항  싱글턴 객체의 생성 동기화
  • 36.
     고려해야 할사항  싱글턴 객체의 생성 동기화
  • 37.
     멀티 스레드환경 지원 안함
  • 38.
     쓰레드A가 GetInstance()함수 진입  1번 조건 체크 하고 다음 줄 실행 전에 중지  쓰레드 B가 GetInstance() 함수에 진입  쓰레드 B는 s_pInstance가 NULL 이므로 1번 계속 진행.   쓰레드 B는 2번 실행하여 객체 생성 후 호출자에게 instance 리턴  쓰레드A는 다시 진행 됨. 2번이 다시 호출되어 또다른 인스턴스 생성 A쓰레드 B쓰레드 2번 1번 2번 1번
  • 39.
  • 40.
    널일 경우에만 락을획득 하기 위함 위 조건 체크와 락을 얻을 때 사이에 다른 스 레드가 먼저 들어와서 pInstance를 초기화 할 수 있기 때문에 한번더 null 인지 체크
  • 41.
     s_pInstance =new LazySingleton(); 의미  1. Singleton 객체를 담기 위한 메모리를 할당  2. 할당된 메모리에 Singleton 객체를 생성  3. s_pInstance가 할당된 메모리를 가리키도록 함  컴파일러가 이 순서대로 수행되도록 제한하지 않음. 컴파일러는 때때로 스텝 2와 스텝 3사이의 순서를 바꾸기도 함
  • 42.
     A쓰레드에서 s_pInstance가할당된 메모리 를 가리키도록 하고 그 후에 할당된 메모리 에 객체를 생성 해야 하는데 할당 전에  B 쓰레드에서 싱글턴 객체를 바로 사용할 경 우 껍데기를 접근하여 문제 발생  (싱글턴 객체는 메모리를 가리키기만 하고 실제 객체객체 생성은 안된 상황)
  • 43.
     Volatile 한정지시자 사용
  • 44.
     volatile로 선언된개체(변수, 메모리 위치, 코드)는 optimize 룰을 적용하지 말라.  ( Objects declared as volatile are not used in certain optimizations ( 출처 - MSDN ))  컴파일러가 효율 등을 고려해 optimize 관점에서 코 드를 임의로 변경(의미 없는 코드 삭제나 실행 순서 변경)을 시키지 않음  volatile로 선언된 개체는 메모리에 할당되고 작업 요 청시 직접(해당 주소에), 바로(효율을 위한 지연 없 이) 처리된다.
  • 45.
     volatile 키워드를사용해야 할 곳이 있다면 C++11에 서는 atomic 변수를 사용  C++11에서는 '서로 다른 스레드 간에 순서 관계가 정 의 되지 않은 메모리 읽기 쓰기 조작은 data race에 의한 알 수 없는 동작을 한다' 라고 되어 있음.  즉 C++11에서 volatile 변수는 Memory Barrier 동작을 한다고 명시하지 않음  메모리 바리어는 대부분 낮은 수준의 기계어에서 사 용되며 여러 장치가 공유하는 메모리를 사용하는 데 쓰임
  • 47.
     Lazy Instance모델 대신 코드가 실행되는 시점, main() 이 호출되기 전이나 뮤텍스 잠금으로 API가 초기화 시점에 초기화  정적 초기화 static Singleton& foo = Singleton::GetInstance();  명시적 API 초기화 Void APIInitialize() { Mutex mutex; ScopredLock(&mutex); Singleton::GetInstance(); }
  • 48.
     의존성 삽입 classMyClass { public: MyClass() : mDatabase( new Database ("mydb, "localhost", "user", "pass")) {} private: Database* mDatabase; } class MyClass { public: MyClass(Database* db) : mDatabase(db) {} private: Database* mDatabase; }
  • 49.
     모노스테이트 패턴 싱글톤의 문제는 전역 상태를 유지하고 접근하는데서 발생 // monostate.h class MonoState { public: int GetTheAnswer() const { return sAnswer; } private: static int sAnswer; }; // monostate.cpp int MonoState::sAnswer = 42;  세션 문맥 사용
  • 50.
     C++ 생성자에서의제약 사항  결과 값 리턴 불가  명명 규칙의 제약  정적 형식의 객체 생성  가상 생성자의 부재  팩토리 메서드는 위 제약사항에 구애받지 않음
  • 51.
    // renderer.h #include <string> classIRenderer { 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; };
  • 52.
    // 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;
  • 53.
     사용자가 원하는타입의 인스턴스를 런타임 시에 생성하기 때문에 유연성 제공  상속 클래스의 헤더 파일은 팩토리를 구현 하는 .cpp 파일에만 존재하며 public으로 선 언된 RenererFacory.h 파일에는 포함하지 않 음  그러나 이것은 사용자에게 새로운 그래픽 처리기를 제공하지 못함.
  • 54.
     팩토리 메서드와상속 클래스간의 연결 관 계를 느슨하게 만듦  런타임시에 새로운 상속 클래스를 추가할 수 있도록 타입 이름을 객체 생성 콜백 함수 에 연결시키는 맵을 팩토리 클래스에서 사 용
  • 55.
    // 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; };
  • 56.
    #include "rendererfactory.h" // instantiatethe 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; }
  • 57.
    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; }
  • 58.
     한 클래스의인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환 어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해 서 쓸 수 있음.
  • 59.
    public class Adapterimplements target{ request(){ translatedRequest(); } } 클라이언트와 어댑티는 서로 분리 서로 상대방에 대해서 전혀 모름.
  • 60.
    어댑터에서 타겟 인터페이스를구현 어댑터는 어댑티로 구성 모든 요청은 어댑티에게 위임 public class Adapter implements target{ request(){ specificRequest(); } } 객체 구성(Composition) 사용 어댑티의 어떤 서브클래스에 대해서도 어댑터 를 쓸 수 있다는 장점 클라이언트를 특정 구현이 아닌 인터페이스에 연결 - 여러 어댑터 사용 가능 - 타겟 인터페이스만 제대로 지키다면 다른 구현 추가 가능
  • 61.
    다중상속을 사용해서 어댑 터를어댑티와 타켓 클랙스 모두의 서브클래스로 만듦. ※ 자바에서는 실제로 동작하지 않는 코드입니다 public class Adapter extends Target, Adaptee{ public void request(){ specificRequest(); } }
  • 62.
    - 어댑티 전체는다시 구현하지 않아도 됨 - 어댑티의 행동을 오버라이딩 가능 - 구성을 사용하기 때문에 그 서 브클래스에 대해서도 어댑터 역할을 할 수 있음
  • 63.
     어떤 서브시스템의일련의 인터페이스에 대한 통합된 인터페이스를 제공 퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브시스템을 더 쉽게 사용할 수 있 음
  • 64.
    클래스 다이어그램 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) 의 존성을 정의
  • 65.
     옵저버 패턴에서는주제와 옵저버가 느슨하게 결합되어 있는 객체 디자인을 제공.  주제가 옵저버에 대해 아는 것은 옵저버가 특정 인터페이스를 구현(implements) 한다는 것 뿐.  옵저버는 언제든지 새로 추가할 수 있음  새로운 형식의 옵저버를 추가하려고 할 때도 주제를 전혀 변경 할 필요가 없음.  주제와 옵저버는 서로 독립적으로 재사용할 수 있음.  주제나 옵저버가 바뀌더라도 서로한테 영향을 미치지 않음.
  • 66.

Editor's Notes

  • #6 플랫폼 종속적인 정의 포함 모든 이가 볼 수 있는 헤더파일에 타이머가 어떤 형태로 다른 플랫폼에 저장된는지 구체적인 내용들을 노출
  • #7 Private 클래스로 선언 AutoTime 클래스의 메서드만이 Impl 클래스의 멤버에 접근 할 수 있다는 제약 조건 Cpp 파일에 정의된 다른 클래스 또는 함수가 Iimpl클래스의 멤버에 접근하고자 할 경우 public으로 Impl을 선언 후 접근
  • #10 Pimpl이 불편하면서 쉽게 오류를 발생시키는 이유는 객체의 메모리 할당과 해제가 필요하기 때문 Shared_ptr 사용해서 할당과 해제 신경 쓰지 않도록 함. 혹은 scoped_ptr을 사용. 이것은 복사가 불가능하며 이것을 사용하면 private 복사 생성자 및 할당 연산자를 선언하지 않아도 되기 때문.
  • #11 보편적으로 2번을 사용하여 모두 포함하길 권장.
  • #22 메모리릭 문제는 지속적으로 메모리 할당이 일어나는데 해제는 안되는 상황에서 발생하는 문제이지 이 객체 처럼 한번만 생성되어 프로그램 종료 시 까지 유지되는 객체는 문제가 되지 않음
  • #23 static storage duration 함수 정의의 바깥에서 정의된 변수 또는 키워드 static을 사용하여 정의된 변수 Automatic storage duration 함수 매개변수를 포함한 함수 정의 안에 선언된 변수. 그들은 함수나 블록안으로 들어갈 떄 생성되어 나올때 해제 dynamic storage duration new 연산자를 사용하여 할당된 메모리는 delete 연산자로 해제되거나 프로그램이 종료 될 때까지 존속
  • #36 쓰레드가 동시에 싱글턴 객체를 생성할 경우, 마지막에 생성된 싱글턴 객체만 정상적으로 사용할 수 있고, 그 전에 생성된 객체는 사용할 수 있게 되므로, 싱글턴 객체를 생성할 때, 쓰레드에 안전하도록 유의
  • #37 쓰레드가 동시에 싱글턴 객체를 생성할 경우, 마지막에 생성된 싱글턴 객체만 정상적으로 사용할 수 있고, 그 전에 생성된 객체는 사용할 수 있게 되므로, 싱글턴 객체를 생성할 때, 쓰레드에 안전하도록 유의
  • #38 쓰레드가 동시에 싱글턴 객체를 생성할 경우, 마지막에 생성된 싱글턴 객체만 정상적으로 사용할 수 있고, 그 전에 생성된 객체는 사용할 수 있게 되므로, 싱글턴 객체를 생성할 때, 쓰레드에 안전하도록 유의
  • #39 쓰레드가 동시에 싱글턴 객체를 생성할 경우, 마지막에 생성된 싱글턴 객체만 정상적으로 사용할 수 있고, 그 전에 생성된 객체는 사용할 수 있게 되므로, 싱글턴 객체를 생성할 때, 쓰레드에 안전하도록 유의
  • #40 호출 할 때마다 항상 락이 걸리기 때문에 성능 문제 발생
  • #53 Irenderer 클래스를 상속받은 OpenglRenderer, DirectxRenderer, MesaRenderer 세개의 클래스를 구현했다고 가정
  • #58 주의 사항 : Create() 함수를 UserRenderer 클래스에 추가한 부분 팩토리의 RegisterRenderer 함수는 객체를 하나 리턴하는 콜백 함수를 취하는데 이 콜백 함수는 Irenderer 클래스의 멤버가 되어서는 안되기 때문에 static으로 선언.