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>
class AutoTimer
{
public:
explicit AutoTimer(const std::string &name);
AutoTimer();
private:
class Impl;
Impl *mImpl;
};
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";
}
15. 어떻게 싱글톤 객체를 할당할 것인가?
언제 싱글톤 객체를 파괴 시킬 것인가?
쓰레드 안정성을 지원하는 가?
쭉정이(알맹이가 없는) 싱글턴을 사용하면
어떻게 되는가?
16. 발전된 형태의 전역 변수
프로그램 상에서 하나의 인스턴스만 있고
두번째 인스턴스를 만들 수 없는 기능
쓰임에 따라 최선의 방법들이 달라짐
17. 기본 생성자를 private에 선언
외부에서 new로 객체 생성 불가
Singleton* pObject = new Singleton(); 컴파일 에러
복사 생성자, 복사 대입연산자 도 private에 선언
복사로 인한 생성 불가
정의를 하지 않고 선언만 함
(정의가 있으면 멤버함수 혹은 프렌드 함수가 호출 할 수 있기 때문에 정의
하지 않음)
복사 생성자
▪ Singleton object1( Singleton::GetInstance() ); 컴파일 에러
복사 대입 연산자
▪ Singleton& object1 = Singleton::GetInstance();
▪ Singleton& object2 = Singleton::GetInstance();
▪ object1 = object2; 컴파일 에러
19. Static 클래스 멤버 변수는 static 전역 변수처럼 프로그램
시작 시 main() 함수 호출 이전에 초기화
싱글톤 객체 사용하지 않더라도 무조건 생성되기 때문
에 비효율적
정적 객체는 다른 전역객체의 생성자에서 참고하고 싶
은 경우 문제 발생 할 수 있음
C++ 표준에서는 전역 객체들의 생성 순서에 대해 명확하게 정
의하고 있지 않기 때문
Main() 함수가 실행 하기 전에만 생성되면 됨
어떤 전역 객체의 생성자에서 위 싱글톤 객체를 참조하려고
할 경우 싱글톤 객체가 생성되기 전인 경우 문제 발생 할 수 있
음
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 함수를 통해서 등록된 함수는 프로그램이 정상적으로 종료될 때에만
호출
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;
메모리를 재할당 하지 않음
기존 데이터가 그대로 남아있고, 생성자에서 초기화하는 데이터
만 초기화
가상함수 테이블을 초기화
메모리풀링 등에서 유용하게 사용
38. 쓰레드A가 GetInstance() 함수 진입
1번 조건 체크 하고 다음 줄 실행 전에 중지
쓰레드 B가 GetInstance() 함수에 진입
쓰레드 B는 s_pInstance가 NULL 이므로 1번 계속 진행.
쓰레드 B는 2번 실행하여 객체 생성 후 호출자에게 instance 리턴
쓰레드A는 다시 진행 됨. 2번이 다시 호출되어 또다른 인스턴스 생성
A쓰레드 B쓰레드
2번
1번
2번
1번
40. 널일 경우에만 락을 획득 하기 위함
위 조건 체크와 락을 얻을 때 사이에 다른 스
레드가 먼저 들어와서 pInstance를 초기화 할
수 있기 때문에 한번더 null 인지 체크
41. s_pInstance = new LazySingleton(); 의미
1. Singleton 객체를 담기 위한 메모리를 할당
2. 할당된 메모리에 Singleton 객체를 생성
3. s_pInstance가 할당된 메모리를 가리키도록 함
컴파일러가 이 순서대로 수행되도록 제한하지
않음. 컴파일러는 때때로 스텝 2와 스텝 3사이의
순서를 바꾸기도 함
42. A쓰레드에서 s_pInstance가 할당된 메모리
를 가리키도록 하고 그 후에 할당된 메모리
에 객체를 생성 해야 하는데 할당 전에
B 쓰레드에서 싱글턴 객체를 바로 사용할 경
우 껍데기를 접근하여 문제 발생
(싱글턴 객체는 메모리를 가리키기만 하고
실제 객체객체 생성은 안된 상황)
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 동작을
한다고 명시하지 않음
메모리 바리어는 대부분 낮은 수준의 기계어에서 사
용되며 여러 장치가 공유하는 메모리를 사용하는 데
쓰임
46.
47. Lazy Instance 모델 대신 코드가 실행되는 시점,
main() 이 호출되기 전이나 뮤텍스 잠금으로
API가 초기화 시점에 초기화
정적 초기화
static Singleton& foo = Singleton::GetInstance();
명시적 API 초기화
VoidAPIInitialize()
{
Mutex mutex;
ScopredLock(&mutex);
Singleton::GetInstance();
}
49. 모노스테이트 패턴
싱글톤의 문제는 전역 상태를 유지하고 접근하는데서 발생
// monostate.h
class MonoState
{
public:
int GetTheAnswer() const { return sAnswer; }
private:
static int sAnswer;
};
// monostate.cpp
int MonoState::sAnswer = 42;
세션 문맥 사용
50. C++ 생성자에서의 제약 사항
결과 값 리턴 불가
명명 규칙의 제약
정적 형식의 객체 생성
가상 생성자의 부재
팩토리 메서드는 위 제약사항에 구애받지
않음
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. 팩토리 메서드와 상속 클래스간의 연결 관
계를 느슨하게 만듦
런타임시에 새로운 상속 클래스를 추가할
수 있도록 타입 이름을 객체 생성 콜백 함수
에 연결시키는 맵을 팩토리 클래스에서 사
용
56. #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;
}
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 Adapter implements 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) 한다는 것 뿐.
옵저버는 언제든지 새로 추가할 수 있음
새로운 형식의 옵저버를 추가하려고 할 때도 주제를 전혀 변경
할 필요가 없음.
주제와 옵저버는 서로 독립적으로 재사용할 수 있음.
주제나 옵저버가 바뀌더라도 서로한테 영향을 미치지 않음.