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 책을 참고하여 작성하였으며, 만약 저작권적으로 문제가 된다면 댓글등으로 삭제요청하시면 삭제조치 하도록 하겠습니다.

출처 카페 > 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++에서는 클래스의 상속을 막기 위해서 어떤 방법을 사용할 수 있을까요? 힌트는 위의 코드들에 나온 문법 중에 하나를 사용하면 된다는 것입니다.