Windows7상에서 SSD속도향상 팁

지난번에 몸상태가 안 좋은채로 전자전을 갔던 것이 문제였는지 감기로 엄청 고생하고 있습니다. 그때문에 마저 올려야 할 글도 많은데 영 올리질 못하네요.(계속해서 올릴 내용은 쌓여만  갑니다.)

오늘은 소개할 것은 간만에 올리는 팁입니다. 내용은 얼마전 출시된 Windows 7에서 SSD의 설정을 통해 시스템의 속도를 향상시키는 방법입니다.

시스템 속도 향상

색인기능 사용 안함

설명: 색인기능은 파일 속성 데이터베이스를 생성하고 보관합니다. 색인기능은 파일이 생성/삭제/변경시 여러개의 작은 쓰기를 유도합니다[각주:1]. 파일 검색을 위해 작업은  계속해서 진행됩니다.
방법: 시작 메뉴. → 컴퓨터에서 오른쪽 클릭 → 관리 → 서비스 및 응용프로그램 → 서비스 → Windows Search(윈도우 검색)에서 오른쪽  클릭 → 속성 → 시작 유형 : 사용안함 → 확인

조각모음기능 사용 안함

설명: 하드 디스크 사용공간 조각모음 기능은 수 밀리초 대기시간을 가진 기계식 디스크에서 유용합니다. 여유 공간 조각모음 기능은 SSD에서 유용할 수 있습니다만, 이 기능은 기본 윈도우 조각모음에서는 사용할 수 없습니다.
방법: 시작메뉴. → 컴퓨터에서 오른쪽 클릭 → 관리 → 서비스 및 응용프로그램 → 서비스 → Disk Defragmenter(디스크 조각모음) 에서 오른쪽 클릭 → 속성 → 시작 유형 : 사용안함 → 확인

쓰기 캐쉬 사용 안함

설명: SSD에는 캐쉬가 없어서, 쓰기 캐쉬로 인한 이점이 없다. 속도 향상이 있는지 없는지에 대한 상반된 보고가 있는 상황이다.
방법: 시작메뉴 → 컴퓨터에서 오른쪽 클릭 → 관리 → 장치 관리자 → 디스크 드라이브 → STEC PATA에서 오른쪽 클릭 → 속성 → 정책 탭 → 쓰기 캐쉬 사용을 체크 해제 → 확인

슈퍼페치 설정

설명: 프로그램 파일을 사전에 올리지 않게해 RAM을 확보합니다.
방법: 시작메뉴 → regedit 입력 → regedit → HKEY_LOCAL_MACHINE → SYSTEM → CurrentControlSet → Control → Session Manager → Memory Management → PrefetchParameters의 값을 변경 → 컴퓨터 재시작
PrefetchParameters 값
0: 캐쉬 사용 안함.
1: 응용프로그램만 캐쉬 사용
2: 부팅 파일만 캐쉬 사용
3: 모두 캐쉬 사용(기본값)

FireFox 사용자: 디스크 캐쉬 대신 메모리 캐쉬 사용

설명: Firefox사용하는 경우, 하드 디스크 대신 RAM에 캐쉬 파일을 씁니다. 이것이 더 빠르진 않지만 브라우저  사용시 SSD로의 쓰기시도를 엄청나게 줄여줍니다.
방법: Firefox열기 → 주소창에 about:config 입력 → 동의 → browser.cache.disk.enable 항목을 더블클릭해 값을 false로 설정 → 아무곳이나 오른쪽 클릭 → 새로 만들기 → 정수 → 환경  설정 이름을 "disk.cache.memory.capacity"로 → 메모리 크기 값을 KB단위로. 32MB는 32768, 64MB는 65536, 128MB는 131072, 등 으로 입력. → Firefox 재시작


드라이브 공간 추가 확보

페이지 파일 사용 안함

설명: 디스크의 여유공간이 2GB이상 확보하기 위해 쓰기 메모리를 제거합니다. 주의- 만약 메모리 용량이 부족하면 프로그램이 멈춰버리게 됩니다.
방법: 시작메뉴. → 컴퓨터에서 오른쪽 클릭 → 속성 →고급 시스템 설정 → 설정(성능) → 고급 탭 → 변경 → 자동 관리 체크해제 → 페이지 파일 없음 → 설정 → 확인 → 컴퓨터 새시작
다른 방법으로, 만약  더 안전하게 하고 싶다면, 최대값과 최소값을 200MB로 설정합니다.

시스템 복원 사용 안함

설명: 새 프로그램을 설치하거나 시스템이 변경될 때 파일의 복사본을 저장하지 않습니다. 적게는 수백 MB에서 수GB를 확보할 수 있습니다.
방법: 시작메뉴 → 컴퓨터에서 오른쪽 클릭 → 속성 → 고급 시스템 설정 → 시스템 보호 탭 → 구성 → 시스템 보호 해제 → 삭제 → 확인

최대 절전 사용 안함

설명: 만약 1GB의 메모리를 가지고 있다면, SSD상의 1GB의 공간을 확보할 수 있고, 2GB의 메모리를 가지면 2GB의 공간을 확보할 수 있습니다. 빠른 부팅 및 종료와 맞먹는 최대 절전 기능을 잃게 됩니다.
방법: 시작 메뉴 → cmd 입력 → cmd 아이콘에서 오른쪽 클릭 → 관리자 권한으로 실행 → powercfg -h off 입력→ exit 입력


현재 Windows7를 쓰고 있지 않아 번역과 일부 메뉴의 이름이 다를것 같아 저녁까지 한글판의 메뉴를 확인한 후 한글판과 맞지 않는  내용을 수정하며 나머지 내용을 올리겠습니다.

Windows 7는 SSD를 위해 TRIM 명령어 기능이 추가되는 등 SSD를 제대로 지원하기 시작한 최초의 Windows버전입니다만, 좀 더 SSD가 제 성능을 발휘 하길 원하시는 분들에게 많은 도움이 되었으면 하네요.

 이 글의 출처: http://www.wtspout.pe.kr/2009/10/windows7%EC%83%81%EC%97%90%EC%84%9C-ssd%EC%86%8D%EB%8F%84%ED%96%A5%EC%83%81-%ED%8C%81.html
 
  1. SSD에서 사용하는 Flash메모리의 특성상 대용량의 파일 전송에는  속도가 빠르지만 작은 여러개의 파일 전송에는 속도가  많이 떨어집니다. 그러므로 작은 파일쓰기가 잦은 색인작업은 SSD의 제성능을 발휘할 수 없으므로 꺼두는 것이 좋겠죠. [본문으로]
Vector3.Angle을 사용, 하지만 각도가 90도 이내로만 나온다.(0~360도 사이의 값으로 각도를 구하고 싶다면 문제가 됨) 이에 대한 해결책은 아래와 같다
public float ContAngle(Vector3 fwd, Vector3 targetDir)
{
    float angle = Vector3.Angle(fwd, targetDir);

    if (AngleDir(fwd, targetDir, Vector3.up) == -1)
    {
        angle = 360.0f - angle;
        if( angle > 359.9999f )
            angle -= 360.0f;
        return angle;
    }
    else
        return angle;
}

public int AngleDir( Vector3 fwd, Vector3 targetDir, Vector3 up)
{
    Vector3 perp = Vector3.Cross(fwd, targetDir);
    float dir = Vector3.Dot(perp, up);

    if (dir > 0.0)
        return 1;
    else if (dir < 0.0)
        return -1;
    else
        return 0;
}
참고 사이트: http://blog.naver.com/sdragoon/150108963945
http://forum.unity3d.com/threads/31420-Left-Right-test-function
http://answers.unity3d.com/questions/18105/how-to-calculate-the-angle-between-two-vectors
Container<int> c;
라는 컨테이너가 있을때

1963이라는 값을 가진것을 모두 지우고 싶다면

연속 메모리 컨테이너(vector, deque, string)의 경우에 가장 좋은 방법은 erase-remove 합성문이다.
 
erase( remove(c.begin(), c.end(), 1963), c.end()); 
// 컨테이너가 vector, string 혹은 deque일 때 특정한 값을 가진 요소를 없애는
// 가장 좋은 방법은 erase-remove 합성문을 사용하는 것이다.

이 방법은 양방향 반복자를 지원하는 list에도 통하지만, list의 경우엔 멤버함수인 remove가 더 효율적이다.
 
c.remove(1963);
// 컨테이너가 list일 때에는 remove 멤버함수가 특정한 값을 가진 요소를 모두 없애는 데에 더 좋다.

c가 표준 연관 컨테이너일 때에는(set, multiset, map, multimap) remove라는 이름을 가진 어떤것도 소용이 없다.

멤버함수로는 remove가 없을뿐더러 remove를 사용하면 컨테이너에 값을 덮어써서 컨테이너가 변형된다.

연관컨테이너에서 특정한 값을 가진 요소를 지울땐 erase를 사용한다.

c.erase(1963);
// 컨테이너가 표준 연관 컨테이너일 때에는 erase 멤버 함수가 특정한 값을 가진 요소를
// 모두 없애는 데에 가장 좋다.

이렇게 하면 삭제도 제대로되고 효율적이다.

여기서 특정한 값을 가진 요소를 모두 없애지 않고다음의 술어구문이 true를 반환하는 요소를 모두 없앤다고 할때
 
bool badValue(int x);  // x가 "나쁘다"면 true를 반환합니다.

c.erase( remove_if(c.begin(), c.end(), badValue), c.end());
// 컨테이너가 vector, string, deque일 때 badValue가
// true를 반환하는 요소를 모두 없애는 가장 좋은 방법입니다.

c.remove_if(badValue);
// 컨테이너가 list일 때 badValue가 true를 반환하는 요소를
// 모두 없애는 가장 좋은 방법입니다.

표준 연관 컨테이너의 경우(set, map 등)
이렇게 쉽게 문제가 해결되지는 않는다.
따라서 처음부터 끝까지 도는 루프를 넣어서 요소를 직접 검사해서 지워야 한다.
이 작업은 소스코드도 간단하다.

Container c;
...
for(Container::iterator i = c.begin(); i != c.end(); /* 비어있음 */)
// for 루프의3번째 부분이 비어있고 아래쪽에서 증가된다.
{
    if(badValue(*i)) // i가 가리키는 값이 "나쁜값"이면
        c.erase( i++ ); // 후위증가에 의해 i를 증가시킨다.
    else
        ++i;
}

만약 vector, string, deque로 루프를 도는 중간에 erase를 해야 하는 상황이 생긴다면 이렇게 바꾸면 된다.

for(Container::iterator i = c.begin(); i!= c.end(); /* 비어있음 */)
{
    ...
    ...
    ...

    if(badValue(*i)
    {
        i = c.erase(i); // erase의 반환값을 i에 저장해서 반복자의 유효성을 유지시킨다.
    }
    else
    {
        ++i;
    }
}

이 글은 이펙티브 STL 책을 참고하여 작성하였으며, 만약 저작권적으로 문제가 된다면 댓글등으로 삭제요청하시면 삭제조치 하도록 하겠습니다.

http://sdmeter.tistory.com  <= 여기 블로그는 따로 있습니다.

출처 카페 > C언어를 배우자 | 라온
원문 http://cafe.naver.com/cafec/505
말이 좀 어려운 것 같은데 그냥 훑어 보세요. 프로그램 해 보면 차츰 익숙해 질겁니다.

예제를 만들어 하나씩 살펴보고 직접 해보시면 꽤 간단하게 프로그램이 만들어 지는 것을 경험 하실 겁니다. 우선 STL에 대해 살펴 보겠습니다.

STL에는 6가지의 주요 component가 있습니다.

1. container : data set 이라고 생각하시면 쉽게 이해 됩니다.

2. generic algorithm : <algorithm> 과 container 자체에 있는 Algorithm 입니다.

3. iterator : C/C++의 포인터와 같이 Conatiner의 요소를 지정할 수 있는 객체입니다.

container를 잘 사용할려면 반드시 알아야 합니다.

보통 서적에서는 반복자라는 말을 쓰던데 특성을 보고 이름을 그리 붙인것 같습니다.

4. function object : accumlate 같은 것인데 그리 범용이라고 보기는 힘들지만..

5. adaptor : component의 interface를 변경하는 component입니다.

6. allocator : 할당기라고 하는데요. STL container는 allocator class를 사용하여 프로그램에서

사용하고 있는 메모리 할당 관련 정보를 캡슐화 하고 있습니다.

우선 Container를 보구요 다른 내용은 예제로 자세히 보는 것이 좋을 것 같습니다.

아래 내용은 STL Tutorial and Reference Guide 2nd 내용입니다.

Container

객체들의 컬렉션을 저장하고 있는 객체를 STL에서는 컨테이너라고 합니다.

두가지 종류가 있는데 Sequence Container , Sorted Associative Container 입니다.

Sequence Container : 타입이 동일한 객체들을 선형으로 구성한 컬렉션

- vector<T> : 가변길이 시퀀스를 임의 접근 할 수 있으며, 시퀀스 맨 끝에서 수행되는 삽입과

삭제는 아모타이즈드 상수 시간에 수행이 가능합니다.

참고 : 임의접근(random access)이 가능하다는 것은 시퀀스 i 번째 원소를 접근하는데 걸리

는 시간이 상수 시간이라는 것을 의미합니다. 이는 몇 번째이건 관계없이 항상 일정 하다는

것입니다.

- deque<T> : 가변길이 시퀀스를 임의 접근 할 수 있으며 시퀀스 맨 앞과 맨 끝에서 수행되는

삽입과 삭제는 모두 아모타이즈드 상수 시간에 수행이 가능합니다.

- list<T> : 가변길이 시퀀스에 대해서 선형 시간 접근만이 가능하며, 삽입과 삭제는 시퀀스 내

에서라면 어디서든지 상수 시간 내에 수행이 가능합니다.

Sorted Associative Container : 주어진 키로 컬렉션에서 객체를 신속하게 찾을 수 있는

기능을 제공합니다. 컬렉션의 사이즈는 실행시에 변할 수 있고 네 가지 타입이 있습니다.

- set<Key> : 유일키를 지원하며, 원하는 키를 신속하게 찾아 냅니다.

- multiset<Key> : 중복키를 지원하며, 원하는 키를 신속하게 찾아 냅니다.

- map<Key, T> : 유일키를 지원하며, 주어진 키로 원하는 객체를 신속하게 찾아 냅니다.

- multimap<Key, T> : 중복키를 지원하며, 주어진 키로 원하는 객체를 신속하게 찾아 냅니다.


STL Component

 

STL Container Types

출처 카페 > C++ Standard Te.. | 라온
원문 http://cafe.naver.com/cppstl/570

Singleton pattern을 이용하여 Config 파일을 이용하는 예 입니다.

Config는 여러 Application에서 사용할 수 있는 것이기 때문에 Singleton 형태로 한 번만 init하여

전체 공통으로 사용하도록 하고, 실제 프로그램 구성하여 사용 할 때는

System Environment(unix의 경우 getenv ) 설정으로 사용할 수 있습니다.

data container는 map을 이용하고 있으며, key, value 모두 string 입니다.

아래 예에서 사용한 Config.txt 내용은

#--------------------------------------------------------------------
# File Name : config.txt
# Author Name : Jeong il Ahn
# Description : Config file
# version : 1.0
# last edit : 200 . .
#---------------------------------------------------------------------
# Directory Information
#---------------------------------------------------------------------
DEFDIR=TESTDIR
#---------------------------------------------------------------------
# File Information
#---------------------------------------------------------------------
# End of File
EOF=[EOF]
# End of String
EOS=[EOS]
#---------------------------------------------------------------------

이런 형태이며, #은 주석으로 사용합니다.

key=value 형태로 구성되어 있습니다.

예제 main과 Config Source는 다음과 같습니다.

main.cpp

1 /*------------------------------------------------------------------------------------
2 * finename : main.cpp
3 * eng'r name : Jeong-il Ahn(raon_pgm@naver.com)
4 * date : 200 . 00. 00.
5 * title : This source is config for to test
6 *-----------------------------------------------------------------------------------*/
7
8 #include <iostream>
9 #include "config.h"
10
11 int main(int argc, char *argv[])
12 {
13 // config value initialize
14 if( !Config::instance()->init() ){
15 std::cerr << "System failed to initialize config!!!" << std::endl;
16 std::exit(EXIT_FAILURE);
17 }
18
19 std::cout << "End Of File is : ";
20 std::cout << Config::instance()->getCfgValue("EOF") << std::endl;
21
22 std::cout << "System Default Directory : ";
23 std::cout << Config::instance()->getCfgValue("DEFDIR") << std::endl;
24
25 }

config.h

1 #ifndef __CONFIG_H__
2 #define __CONFIG_H__
3
4 #include <map>
5 #include <string>
6 #include <fstream>
7
8 //! default config file declaration
9 static std::string DEFAULT_CONFIG = "config.txt";
10 //! configuration class
11 /*!
12 Programmer : Jeong-il Ahn(raon_pgm@naver.com) \n
13 date : 2001. 05. 14.\n
14 title : system config value define header\n
15 purpose : config value initialize from config.txt into map container\n
16 */
17 class Config{
18 public:
19 //! key/value container Map_STRSTR declaration. key : string, value : string
20 typedef std::map<std::string, std::string> Map_STRSTR;
21 //! config file load in mapConfigValue_
22 bool init();
23 //! singleton pattern - instance() function declaration
24 static Config* Config::instance();
25 //! mapConfigValue_에서 입력된 string으로 key 검색
26 /*!
27 \param key config내에서 찾고자 하는 key string
28 \return map container에서 key에 해당하는 value
29 */
30 std::string getCfgValue( std::string key )
31 {
32 return mapConfigValue_[key];
33 }
34
35 private:
36 //! static config point
37 static Config* the_config;
38 //! config file stream에서 memory로 데이터를 읽어 들인다.
39 /*!
40 \param *ifstr config file stream pointer
41 \return 파일 처리가 정상적으로 완료 되었을일 경우 true 이상이 있을 경우 false
42 */
43 bool readCfgIntoMemory ( std::ifstream &ifs );
44 //! map< string, string > type data container
45 Map_STRSTR mapConfigValue_;
46 };
47
48 #endif //!__CONFIG_H__

config.cpp

1 /*------------------------------------------------------------------------------------
2 * finename : config.cpp
3 * eng'r name : Jeong-il Ahn(raon_pgm@naver.com)
4 * date : 2001. 05. 14.
5 * title : system config value define source
6 * purpose : config value initialize from ./config/config.txt into map container
7 * description : using the singleton design pattern
8 *-----------------------------------------------------------------------------------*/
9
10 #include <iostream>
11 #include <fstream>
12 #include <cstdio>
13 #include <cstdlib>
14 #include <string>
15 #include <sys/types.h>
16
17 #include "config.h"
18
19 Config* Config::the_config = 0;
20
21 Config* Config::instance()
22 {
23 if( !the_config ){
24 the_config = new Config();
25 }
26 return the_config;
27 }
28
29 bool Config::init()
30 {
31 std::ifstream cfgFile( DEFAULT_CONFIG.c_str() );
32 if( ( !cfgFile ) || ( cfgFile.fail() ) ){
33 std::cerr << "Can't open file " << DEFAULT_CONFIG << std::endl;
34 return false;
35 }
36
37 if( !readCfgIntoMemory( cfgFile ) ) return false;
38
39 return true;
40 }
41
42 bool Config::readCfgIntoMemory( std::ifstream &ifs )
43 {
44 std::string oneLine;
45
46 while( !ifs.eof() ){
47 std::getline( ifs, oneLine );
48 if( oneLine == "" || oneLine[0] == '#' )
49 continue;
50 else{
51 size_t idx = oneLine.find( "=" );
52 if( idx == std::string::npos ){
53 std::cout << "Line = " << oneLine << std::endl;
54 std::cout << "Define failure line in config.txt" << std::endl;
55 continue;
56 }
57
58 std::string key, value;
59 key = value = oneLine;
60 key.erase( idx );
61 value.erase( 0, idx+1 );
62
63 mapConfigValue_[key] = value;
64 }
65 }
66 return true;
67 }

  • Vector

- 벡터

  1. 대용량의 데이터를 효과적으로 다룰 수 있는 클래스
  2. 용량 변경이 용의
  3. 벡터에 저장하는 모든 데이터는 Object타입
  4. 어떤 종류의 객체도 함께 담을 수 있다.

- 벡터 생성자

  1. vector : 초기용량이 10, 용량 초과시 크기를 두배 씩 증가
  2. vector(int a) : 지정한 크기의 용량으로 초기화된 Vector 객체를 생성
  3. vector(int a, int b) : 지정한 크기의 용량으로 초기화된 벡터 객체를 생성하고 용량 초과시 b 만큼 증가

- 벡터에 객체 저장

  1. void add(int index, Object object) : 지정한 인덱스의 위치에 객체를 추가
  2. void addElement(Object object) : 벡터의 끝에 객체를 추가

- 벡터 부터 객체 삭제

  1. Object remove(int index) : 지정한 위치의 객체를 벡터에서 제거
  2. boolean remove(Object object) : 지정한 객체를 벡터에서 제거
  3. void clear() : 벡터의 모든 요소를 제거

- 벡터로부터 객체 검색

  1. Object elementAt(int index) : 지정한 위치의 객체를 리턴
  2. Object get(int index) : 지정한 위치의 객체를 리턴

- 벡터의 기타 메소드

  1. int capcity() : 벡터의 현재 용량의 리턴
  2. boolean contains(Object object) : 주어진 요소가 벡터에 있는지 알아봄
  3. int indexof(Object object) : 주어진 요소의 위치를 리턴(없으면 -1)
  4. int size() : 벡터에 포함되어 이쓴 요소의 수를 리턴
  5. void trimToSize() : 벡터의 용량을 현재 벡터의 크기에 맞게 수정

- Enumeration : 벡터에 저장된 객체를 열거형으로 리턴

Enumeration e = v.elemetns();

while(e.hasMoreElements()) {

System.out.println(e.nextElement());

}

- Iterator : Collection에 저장된 객체를 나열 또는 열거하기 위한 인터페이스

Iterator ie = v.iterator();

while(ie.hasNext()) {

System.out.println(ie.next);

}

  • List 인터페이스

- 컬렉션 가운데 List가 갖는 가장 큰 특징은 List가 갖고 있는 객체의 순서가 있다

- List가 가지고 있는 객체마다 순서번호(인덱스)를가지고 있다

- 리스트가 갖고 있는 몇 번째 객체를 직접 참조 가능

- 리스트에 객체를 추가할때 원하는 위치에 추가도 가능

List

Object get(int index) List의 index번째 객체를 꺼냅니다.

int indexOf(Object o) List에 객체 o가 나타나는 첫번째 인덱스

(List에 객체 o가 없다면 - 1)

int lastIndexOf(Object o) List에 객체 o가 나타나는 마지막 인덱스

(List에 객체 o가 없다면 - 1)

ListIterator listIterator() List의 ListIterator

ListIterator listIterator(int index) List의 index부터 시작한 ListIterator

Object set(int index, Object o) List의 index번째에 객체를 객체 o로 바꿉니다.

index번째에 있었던 이전 객체 List subList(int from,int to)

(List의 from번째부터 to번째까지 객체를 List로 리턴)







----------------------------------------------------------------------------------------------


list와 vector중
list는 중간 삽입 및 삭제가 개체 수가 클수록 유리하지만,

저장하는 데이터의 개수가 적고 랜덤 접근을 하고싶을 경우엔 오히려 vector가 유리함


  vector list
크기 변경 가능 O O
중간 삽입 및 삭제가 용이 X O
순차 접근 가능 O O
랜덤(임의) 접근 가능 O X


대부분의 방만들어서 플레이어끼리 겜하는 게임들은 방에 들어온 플레이어들 관리할때 이방식으로 많이 씀
방만들었을때 그방안에 플레이어가 왔다갔다 자주 하지만 그 수가 작아 미치는 영향이 없다 함

C++에서 싱글톤 구현하기 프로그래밍
작년에 다른 팀에 면접 지원을 나간적이 있습니다. 윈도우 프로그래밍 경력자를 뽑고 있었는데 그 팀에는 윈도우 프로그래밍 경험을 가지신 분들이 없었기 때문이죠. 면접을 위해 윈도우 프로그래밍과 C++ 문법, 그리고 알고리즘 질문을 각각 준비했었는데 그 중 C++ 언어 관련 질문으로 제가 준비한 것은 다음과 같습니다.
C++ 에서 싱글톤 패턴을 구현하는 방법들을 아는데로 나열하고 각각의 장/단점을 말해보세요.
전 이전 회사에서부터 면접 때 항상 이 질문을 하곤 했습니다. 왜냐하면 싱글톤을 구현하는 방법에는 C++ 에서 필수적으로 알아야 하는 생성/소멸자, 권한, static의 특성 등 기본적인 문법 사항을 고루 담고 있기 때문입니다. 그런데 비교적 해묵은 주제임에도 불구하고 면접을 보신 분 중 한 분도 제대로 대답을 못해 좀 의외였습니다. 따라서 한번 쯤 공유차원에서 정리해봐야겠다고 벼르고 있었는데 생각난 김에 지금 정리해 봅니다.

C++ 에서 싱글톤을 구현하는 방법에는 우선 다음과 같은 방법이 있습니다.

// .h
class Singleton {
private:
Singleton() {}
Singleton(const Singleton& other);
static Singleton inst;
public:
static Singleton& getInstance() { return inst; }
};

// .cpp
Singleton Singleton::inst;

위처럼 생성자를 private으로 하고 static 멤버 변수를 하나 생성해서 그 객체를 반환하도록 하면 외부에서는 해당 전역 객체만을 참조할 수 있습니다. 간단하지요...클래스 접근 권한과 클래스 내에서의 static 지시 한정자의 역할을 이해하고 있다면 충분히 구현할 수 있는 방법입니다.

그런데 위 방식은 단순한 반면 몇 가지 단점이 있습니다. static 클래스 멤버 변수는 static 전역 변수처럼 프로그램 시작 시 main() 함수 호출 이전에 초기화됩니다. 따라서 위 객체는 만약 프로그램의 진행 상황에 따라 필요가 없는 경우에도 무조건 생성되기 때문에 때에 따라서 비효율적입니다.
게다가 위와 같은 정적 객체는 다른 전역 객체의 생성자에서 참조하고 싶은 경우 문제가 발생할 수 있습니다. 왜냐하면 C++표준에서는 전역 객체들의 생성 순서에 대해서 명확하게 정의하고 있지 않기 때문입니다. 그저 main() 함수가 실행하기 전에만 생성되면 될 뿐입니다. 따라서 어떤 전역 객체의 생성자에서 위 싱글톤 객체를 참조하려고 하는 경우 싱글톤 객체가 미처 생성되기 전인 경우가 발생할 수 있습니다. 결국 객체의 생성 시점을 조절할 필요가 있죠.
아마 effective 시리즈 류의 책을 보신 분들이라면 늦은 초기화에 대해 들어 보셨을 겁니다. 위의 문제점을 피하기 위해선 늦은 초기화 방법을 사용해 다음과 같이 동적 생성을 하면 됩니다.

// .h
class DynamicSingleton {
private:
DynamicSingleton() {}
DynamicSingleton(const DynamicSingleton& other);
~DynamicSingletone() {} // 외부에서 싱글톤 객체를 강제 delete 하는 것을 막기 위해 필요함
static DynamicSingleton* inst;
public:
static DynamicSingleton* getInstance() {
if (inst == 0) inst = new DynamicSingleton();
return inst;
}
};

// .cpp
DynamicSingleton* DynamicSingleton::inst;

이렇게 하면 최초 getInstance()를 호출하는 시점에 객체가 생성되므로 상황에 따라(한번도 해당 객체를 사용하지 않으면) 생성이 되지 않기 때문에 자원을 효율적으로 사용할 수 있을 뿐더러 물론 다른 전역 객체의 생성자에서 참조하는 것도 가능합니다.
여기서 '동적 생성한 객체는 그럼 언제 해제하나요?' 라는 질문을 던질 수 있습니다. 그러나 프로그램이 종료되는 순간 동적 객체는 자동으로 해제되기 때문에 굳이 명시적으로 해제할 필요가 없습니다. 메모리 릭 문제는 지속적으로 메모리 할당이 일어나는데 해제는 안되는 상황에서 발생하는 문제이지 이 객체처럼 한번만 생성되어 프로그램 종료 시까지 유지되는 객체는 문제가 되지 않습니다.
물론 명시적으로 해제해야 하는 경우도 있습니다. 가령 위 객체가 반드시 프로그램 종료 시 반납해야 하는 외부 시스템 자원을 사용하는 경우가 그렇습니다. 이를 위해서는 atexit() 함수에 해제 함수를 등록하거나 혹은 다른 전역 객체의 소멸자를 이용해야 합니다. 각각의 구현 방법은 아래와 같습니다.

// atexit() 이용 방법
class DynamicSingleton {
...
private:
static void destroy() { delete inst; }
public:
static DynamicSingleton* getInstance() {
if (inst == 0) {
inst = new DynamicSingleton();
atexit(destroy);
}
return inst;
}
};

// 전역 객체의 소멸자 이용 방법
// .h
class _SingletonDestroyer;
class DynamicSingleton {
...
friend _SingletonDestroyer;
};

// .cpp
static class _SingletonDestroyer {
public:
~_SingletonDestroyer() {
delete DynamicSingleton::getInstance();
}
} destroyer;

보시다시피 좀 귀찮습니다. 따라서 이런 명시적인 해제 작업을 피하기 위해서는 static 지역 객체를 사용하면 됩니다. 방법은 아래와 같습니다.

class LocalStaticSingleton {
public:
static LocalStaticSingleton& getInstance() {
static LocalStaticSingleton inst;
return inst;
}
private:
LocalStaticSingleton() {}
LocalStaticSingleton(const LocalStaticSingleton& other);
};

지역 static 객체는 전역 객체와 달리 해당 함수를 처음 호출하는 시점에 초기화됩니다. 따라서 위 객체를 한번도 사용하지 않으면 생성도 되지 않습니다. 그러면서도 static 객체이기 때문에 프로그램 종료 시까지 객체가 유지되며 종료시에는 자동으로 소멸자가 호출됩니다. 따라서 소멸자에서 자원 해제를 하도록 구현해놓으면 자원 관리도 신경쓸 필요가 없습니다.

하지만 위 세번째 구현에도 문제가 하나 있습니다. 만약 저 싱글톤 객체를 다른 전역 객체의 소멸자에서 사용하려고 하면 문제가 발생합니다. 왜냐하면 C++ 표준에서는 전역 객체들의 생성 순서만 명시하지 않은 것이 아니라 소멸 순서에 대해서도 명시해 놓지 않았기 때문입니다. 따라서 어떤 전역 객체가 소멸자에서 저 싱글톤 객체를 사용하려고 할 때 싱글톤 객체가 먼저 소멸했다면(이것을 참조 무효화 현상이라고 합니다) 문제가 발생합니다.

이 문제를 해결하기 위해선 다소 고난이도 방법이 필요합니다. 그 중 재밌는 것이 Andrei Alexandrescu가 쓴 Modern C++ Design 이라는 책에 나오는 피닉스 싱글톤입니다. 이 싱글톤은 우선 싱글톤 참조 시 해당 객체의 소멸 여부를 파악하고 만약 소멸되었다면 다시 되살립니다. 구현 코드는 아래와 같습니다.

// .h
class PhoenixSingleton {
public:
static PhoenixSingleton& getInstance() {
if (destroyed) {
new(pInst) PhoenixSingleton; // 2)
atexit(killPhoenix);
destroyed = false;
} else if (pInst == 0) {
create();
}
return *pInst;
}
private:
PhoenixSingleton() {}
PhoenixSingleton(const PhoenixSingleton & other);
~PhoenixSingleton() {
destroyed = true; // 1)
}

static void create() {
static PhoenixSingleton inst;
pInst = &inst;
}

static void killPhoenix() {
pInst->~PhoenixSingleton(); // 3)
}

static bool destroyed;
static PhoenixSingleton* pInst;
};

// .cpp
bool PhoenixSingleton::destroyed = false;
PhoenixSingleton* PhoenixSingleton::pInst = 0;

갑자기 굉장히 복잡해졌는데 핵심만 간단히 설명하자면(자세한 내용은 위에 소개한 책을 참조하세요) 정적 객체가 소멸되면 1) 소멸자에 의해 destroyed 변수가 true가 되면서 소멸 여부를 알 수 있습니다. 그리고 소멸 후에 getInstance() 함수를 통해 해당 객체를 참조하려 하면 2) replacement new 를 이용해서 해당 객체의 생성자를 재호출해서 객체를 되살립니다. 이것이 가능한 이유는 컴파일러는 전역 객체 소멸 시에 해당 메모리를 초기화하지 않기 때문에 해당 메모리를 재 사용해서 객체의 생성자만 다시 호출하면 객체를 재 사용할 수 있기 때문입니다. 그 후 atexit() 함수에 killPhoenix() 함수를 등록해서 3) 프로그램 종료 시에 PhoenixSingleton 객체의 소멸자를 호출해서 리소스 해제를 합니다.

물론 마지막에 소개한 PhoenixSingleton 방법은 상당히 tricky 하며 실제로는 거의 쓸일이 없습니다. 제 경우는 예전에 어떤 윈도우용 프로그램에서 딱 한번 어쩔 수 없이 사용했습니다. 실제 중요한 것은 static 객체의 생성/소멸 시점에 대해 정확히 파악해서 싱글톤 객체를 전역 객체의 생성/소멸자에서 마구잡이로 참조하는 일이 없도록 주의해서 프로그래밍하는 것입니다.

p.s. 물론 구두 면접에서 이 정도까지 상세한 답을 기대하진 않았습니다...
p.p.s. 역시나 실전에 별 쓸일은 없지만 난이도 있는 다른 문제를 하나 내보겠습니다. C++에서는 자바의 final 처럼 상속을 막는 키워드가 아쉽게도 없습니다. 그렇다면 C++에서는 클래스의 상속을 막기 위해서 어떤 방법을 사용할 수 있을까요? 힌트는 위의 코드들에 나온 문법 중에 하나를 사용하면 된다는 것입니다.

아이온 2.5 업데이트 주신의 부름
아이온 2.5 월페이퍼 다운로드
plaync 아이온 더보기


카스토르 / 신도세카이

어제 부산가서 블앤소를 보고, 시연까지 해보았습니다.
아... 이것은 정말 말이 제대로 안나오는 놀랍고 신기하고 멋있고 재미있는 대작이 하나 나왔구나 하는
기분이 들더군요... 정말 놀라운 경이로움 그 자체였습니다.

물론... 그거 하나 시연해볼려고 3시간 서있다가 20분정도밖에 못했다는게 너무 아쉬웠으나....

정말 놀랍기만한, 그런 게임이었습니다.
기본적으로 R키로 평타를 치고 게이지를 모아서 F나 1234등으로, 연계기, 스턴, 넉백, 띄워올리기를 사용하며,
오토타겟팅된 적의 상황에 따라, 자동으로 사용 스킬이 바뀌고, 상대가 넉백등이 되었을때,
탭키를 누르면 올라타서 때린다던가 하는게 영상으로만 가능한것이 아닌 실제 플레이로 가능하다는것이...
정말 놀라웠습니다.
정말 기대됩니다! 블레이드 앤 소울 앞으로가 기대됩니다.

밑에는 가서찍은 사진, 동영상이 첨부되어있습니다

Ps. 가방이 너무 가로로 길어.......

↓움직이는 블앤소 글씨 벽!!

↓ 블앤소 플레이 영상 

0123456789