C++: 전역/정적 객체 우선 순위 문제

C++ 프로그래밍을 하다보면 전역/정적 객체가 필요할 때가 있다. 전역/정적 객체의 용도 중의 하나는 생성과 소멸이 자동으로 이루어지는 객체의 속성을 이용해서, 전역/정적 객체를 가지고  프로그램 전체의 초기화 작업과 마무리 작업을 수행하는 것이다.

하지만, 이러한  전역/정적 객체의 골칫거리가 하나 있는데, 바로 전역/정적 객체의 생성/소멸 순서이다. 어느 전역/정적 객체가 먼저 생성되고 나중에 생성될지 알 수 없기 때문에, 한 전역/정적 객체의 생성자에서 다른 전역/정적 객체를 참조하게 되면, 예상치 못한 오류를 만날 수 있다.

그래서 이를 해결하기 위해 여러가지 방법이 나왔다. 우선 컴파일러 차원에서 생성자들의 우선순위를 정할 수 있는 방법을 제공한다. #pragma 를 지시자를 이용해서 지원하는데, 이 역시도 우선 순위가 같은 객체 사이에서는 마찬가지 문제가 발생할 수 있기 때문에 근본적인 해결책은 될 수 없다.

그렇다면 근본적인 해결 방법은 없는 것일까 ? 다행히도, 그렇지는 않다. Effective C++ 항목 4 를 보면 해결 방법이 나온다. 전역/정적 객체 문제의 핵심은 객체가 이미 생성됐다는 것을 보증할 수 없다는 것이다. 하지만 조금만 틀어보면 이것이 불가능한 것은 아니다. 바로 함수 내부에 정적 객체를 선언하는 것이다. 함수 내부의 정적 객체들은 함수가 처음 호출될 때 반드시 한 번만 초기화되기 때문에, 함수를 이용해서 정적 객체에 대한 참조나 포인터를 되돌려 주면, 해당 정적 객체는 반드시 초기화되었다는 보증할 수 있다.

예를 들어, 클래스 A 와 클래스 B 가 각각 a.cpp 와 b.cpp 에 있다고 하자.

1
2
3
4
5
6
7
8
9
// a.cpp
class A
{
public:
    A();
    ~A() {};

    void af();
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
// b.cpp
class B
{
public:
    B();
    ~B() {};
};

extern A a;

void B::B()
{
    a.af();
}


만일 클래스 B의 객체를 생성하게 되면 문제가 발생할 수 있다는 것이다. 이를 해결하기 위해서 EC++ 에서 제시한 방법이 다음과 같은 것이다.

1
2
3
4
5
6
7
8
9
// a2.cpp
class A {...};    // 생략

A& ao()
{
    static A a;

    return a;
}


1
2
3
4
5
6
7
8
9
// b2.cpp
class B {...};    // 생략

extern A& ao();

void B::B()
{
    ao().af();
}


이제는 클래스 B 의 객체를 생성하더라도 아무 문제 없이 프로그램이 실행될 것이다. 이로써 초기화 순서 문제를 근본적으로 해결할 수 있다.

그렇다면 모든 문제가 해결되었을까 ? 그렇지는 않다. 초기화 순서는 해결되었을 지도 모르지만, 마무리 순서는 여전히 미해결로 남아있다. 왜 그럴까 ?

이번에는 B 의 소멸자에서 생성자처럼 ao().af() 를 호출한다고 생각해 보자.이렇게 되면, 처음의 문제로 다시 되돌아가게 된다. B 의 소멸자에서 ao().af() 를 호출할 때 ao()의 지역 정적 객체가 이미 소멸되었을 수도 있기 때문이다. 이런 경우 잘못된 결과가 나오거나 예외가 발생하게 될 것이다.

이에 대한 해결방법은 없는 것으로 보인다. 생성 순서와는 달리 소멸 순서는 그 순서를 강제할 방법이 없기 딱히 때문이다. 그렇다면 어떻게 해야 할까 ? 어쩔 수 없다. 전역/정적 객체의 소멸자에서는 전역/정적 객체를 쓰지 말아야 한다.

댓글

이 블로그의 인기 게시물

토렌트: < 왕좌의 게임 > 시즌 1 ~ 시즌 8 완결편 마그넷

토렌트: NGC < 코스모스 > 우리말 더빙 전편(1편~13편) 마그넷

토렌트: < 스타워즈 > Ep.1 ~ Ep.6 마그넷