34-1-다.네임 스페이스 사용

네임 스페이스는 명칭의 선언 영역을 분리하여 충돌을 방지한다. 그래서 네임 스페이스안에 명칭을 선언하면 이름을 붙일 때 충돌을 걱정하지 않고 자유롭게 이름을 붙일 수 있다. 그러나 이렇게 작성된 명칭을 사용하려면 매번 소속을 밝히고 참조해야 하므로 무척 번거롭다. 다음과 같이 선언된 네임 스페이스가 있다고 하자.

 

namespace MYNS {

     int value;

     double score;

     void func() { printf("I am func\n"); }

}

 

MYNS 네임 스페이스 안에 변수 둘, 함수 하나가 포함되어 있는데 이 명칭들을 사용하려면 항상 앞에 MYNS::을 붙여야 한다.

 

void main()

{

     MYNS::value=3;

     MYNS::score=1.2345;

     MYNS::func();

}

 

네임 스페이스의 이름이 길어지면 타이프하는 것도 힘들고 소스의 가독성도 떨어져 여러 모로 좋지 않다. 그래서 이런 불편함을 해소할 수 있는 세 가지 방법이 제공된다.

using 지시자(Directive)

using namespace 다음에 네임 스페이스를 지정하는 방식이다. 지정한 네임 스페이스의 모든 명칭을 이 선언이 있는 영역으로 가져와 소속 지정없이 명칭을 바로 사용할 수 있도록 한다.

 

: usingdirective

#include <Turboc.h>

 

namespace MYNS {

     int value;

     double score;

     void func() { printf("I am func\n"); }

}

 

using namespace MYNS;

void main()

{

     value=3;

     score=1.2345;

     func();

}

 

전역 영역에 using 지시자가 있고 MYNS를 이 영역에서 사용하겠다고 지시했다. 이후 전역 영역에서 MYNS에 속한 명칭은 MYNS::이 없어도 바로 사용할 수 있다. 컴파일러는 value, score 등의 명칭이 전역 네임 스페이스에 없을 경우 using 지시자에 의해 지정된 MYNS 네임 스페이스도 검색해 보고 여기서 명칭이 발견되면 이 네임 스페이스의 명칭을 참조하도록 코드를 컴파일할 것이다. using 지시자는 컴파일러가 일일이 MYNS::을 명칭앞에 붙이도록 한다.

using 지시자가 영향을 미치는 범위는 이 지시자가 있는 영역에 국한된다. 특정 함수나 블록 안에 using 지시자를 사용하면 이 블록에서만 지정한 명칭을 바로 사용할 수 있으며 그외의 영역에서는 여전히 소속 지정이 필요하다. 다음 코드를 보자.

 

void main()

{

     using namespace MYNS;

 

     value=5;

}

 

void subfunc()

{

     MYNS::score=1.2;

}

 

using 지시자가 main 함수 내부에 있으므로 이 영역에 대해서만 MYNS의 명칭을 바로 사용할 수 있다. subfunc에서는 MYNS::을 꼭 붙여야 한다.

using 선언(Declaration)

using 지시자는 지정한 네임 스페이스의 모든 명칭을 가져 오지만 using 선언은 하나의 명칭만을 가져온다. 키워드 using 다음에 가져오고 싶은 명칭의 소속과 이름을 밝히면 이후 이 명칭은 소속을 다시 밝힐 필요없이 바로 사용할 수 있다.

 

: usingdecl

#include <Turboc.h>

 

namespace MYNS {

     int value;

     double score;

     void func() { printf("I am func\n"); }

}

 

void main()

{

     using MYNS::value;

 

     value=3;

     MYNS::score=1.2345;

     MYNS::func();

}

 

void subfunc()

{

     MYNS::value=3;

}

 

main 함수의 선두에서 MYNS::value에 대해서만 using 선언을 했다. 이후 main 함수에서 value를 참조할 때 MYNS::을 붙이지 않아도 된다. score나 func는 별도의 선언이 없으므로 여전히 MYNS::을 명칭앞에 붙여 정확한 소속을 밝혀야 한다.

using 선언도 using 지시자와 마찬가지로 이 선언이 있는 블록에 대해서만 영향을 미친다. MYNS::value에 대한 using 선언이 main 함수 내부에 있으므로 이 선언은 main 함수 내에서만 유효하며 subfunc에서 value를 참조할 때는 MYNS::을 붙여야 한다. using 선언을 main 함수 이전의 전역 영역으로 옮기면 subfunc에서도 value를 바로 참조할 수 있을 것이다.

using에 의한 충돌

using 지시자와 using 선언은 지정한 네임 스페이스 전체 또는 특정 명칭을 이 선언이 있는 영역으로 가져와 소속 지정없이 명칭을 바로 쓸 수 있도록 해 준다. 이 방법이 편리하기는 하지만 소속을 밝히지 않고 사용하다 보니 이 영역에 이미 존재하는 명칭과 충돌하는 경우가 있을 수 있다. 이 경우 컴파일러가 충돌을 어떻게 처리하는지 연구해 보자. 먼저 using 선언의 경우를 보자.

 

: usingdeclconflict

#include <Turboc.h>

 

namespace MYNS {

     int value;

     double score;

     void func() { printf("I am func\n"); }

}

 

int value;

void main()

{

     using MYNS::value;

     int value=3;        // 에러

 

     value=1;            // MYNS의 value

     ::value=2;          // 전역변수 value

}

 

세 개의 value가 선언되어 있는데 이들은 모두 소속이 다르므로 일단 선언은 가능하다. value에 대한 using 선언이 main 함수의 선두에 있으며 MYNS::value가 main 함수의 지역 영역에 들어온다. 이렇게 되면 MYNS::value를 value라는 이름으로 참조할 수 있으므로 같은 이름의 지역변수를 선언할 수 없다. value라는 명칭이 main에서 선언한 지역변수인지 MYNS의 변수인지 구분되지 않으며 그래서 이 상황은 에러로 처리된다. 같은 이름의 전역변수가 있다면 이는 별 문제가 되지 않는데 전역 명칭은 지역 명칭에 의해 가려지며 :: 연산자로 전역 명칭을 참조할 수 있는 별도의 문법이 제공되기 때문이다.

using 선언에 의해 지정한 명칭을 이 영역에서 사용할 수 있게 되었으므로 같은 이름의 명칭을 사용할 수 없다. 이 문제를 해결하려면 지역변수 value의 이름을 바꾸든가 아니면 using 선언을 취소하고 MYNS::value로 써야 한다. 다음은 동일한 코드로 using 지시자의 경우를 보자.

 

: usingdireconflict

#include <Turboc.h>

 

namespace MYNS {

     int value;

     double score;

     void func() { printf("I am func\n"); }

}

 

int value;

void main()

{

     using namespace MYNS;

     int value=3;             // 지역변수 선언

 

     value=1;                 // 지역변수 value

     ::value=2;               // 전역변수 value

     MYNS::value=3;           //

}

 

using 지시자의 경우 MYNS의 명칭 전체를 main 블록에서 참조할 수 있도록 한다. using 선언과 다른 점은 지정한 네임 스페이스 소속의 명칭과 같은 이름의 지역변수를 선언할 수 있다는 점이다. 이 경우 main의 지역변수 value에 의해 MYNS::value가 가려지며 main 내에서 value를 단독으로 사용하면 지역변수 value를 의미한다. 지역변수에 의해 같은 이름의 전역변수가 가려지는 것과 동일하다. 물론 MYNS의 value를 꼭 참조하려면 MYNS::value 형식으로 계속 참조할 수 있다.

요약하자면 using 선언은 명칭이 충돌할 경우 에러로 처리하는데 비해 using 지시자는 네임 스페이스의 명칭에 대한 가시성이 제한될 뿐 에러나 경고를 내지 않는다. 언뜻 생각하기에 using 지시자가 더 관대한 것 같지만 사실 이런 상황이 골치 아픈 에러의 원인이 될 수도 있다. 개발자는 자신이 어떤 명칭을 액세스하는지 정확히 모르는 상태에서 위험한 코드를 계속 작성하게 될 것이다. 일반적으로 애매한 상황보다는 명확하게 에러 처리를 하는 것이 훨씬 더 바람직하다. 그래서 가급적이면 using 지시자로 네임 스페이스의 전체 명칭을 가져 오는 것보다 using 선언으로 꼭 필요한 것만 선별적으로 가져오는 것이 더 좋다.

모호한 상황

using 지시자에 의해 코드가 모호해지는 다른 경우를 보도록 하자. 다음 코드는 명백한 에러로 처리된다.

 

namespace A {

     double i;

}

namespace B {

     int i;

}

 

void main()

{

     using namespace A;

     using namespace B;

 

     i=3;                   // 모호하다는 에러 발생

}

 

i라는 명칭을 두 네임 스페이스에 동시에 선언하는 것은 분명히 가능하다. main에서 using 지시자로 A, B의 네임 스페이스 명칭을 가져오도록 했는데 이렇게 될 경우 main에서 참조하는 i가 어떤 네임 스페이스의 i인지가 모호해진다. A, B의 수준이 같아서 지역, 전역의 경우처럼 한쪽의 가시성을 제한하는 것도 불가능하다. A::i, B::i로 소속을 명확하게 밝히든지 아니면 한쪽의 using 지시자를 제거해야 한다. 다음은 using 선언에 의해 모호해지는 상황을 보자.

 

void main()

{

     using A::i;

     using B::i;         // 중복된 선언이라는 에러 발생

 

     i=3;

}

 

using 선언은 지정한 명칭을 블록으로 가져 오는데 A::i를 가져오는 것은 성공하지만 B::i는 실패한다. 왜냐하면 A::i가 이미 main 함수 영역에 들어와 있기 때문에 같은 이름의 i를 또 가져올 수 없는 것이다. 두 선언 중 하나를 취소하고 한쪽은 :: 연산자로 소속을 밝히는 수밖에 없다.

네임 스페이스에 속한 명칭을 참조할 때는 소속을 밝히는 것이 원칙적이며 이렇게 하면 아무런 문제가 없을 것이다. 그러나 매번 그렇게 하기에는 너무 번거롭기 때문에 using 선언이나 using 지시자를 사용하는데 이 두 방법은 어디까지나 명칭의 소속을 찾는 임시 방편일 뿐 완벽할 수가 없다. 조금 편해 보고자 이런 애매한 방법을 쓰는 것보다는 차라리 일일이 소속을 밝히고 쓰는 것이 가장 완벽한 방법이다.

네임 스페이스는 이름 충돌을 제거하기 위해 도입된 것인데 충돌을 해결하는 목적은 달성할 수 있지만 쓰기에 너무 불편하다. 그래서 using 지시자나 선언으로 네임 스페이스의 명칭을 참조하는 조금 편리한 방법이 제공되는데 이들은 구분해 놓은 소속을 다시 합치는 반대 동작을 하기 때문에 다소 부작용이 있다. 대개의 경우 별 문제가 없지만 가끔 말썽을 부리는 경우가 있다. 그래서 using 선언은 큰 부작용없이 불편하지 않는 정도의 적당한 수준에서만 사용해야 한다.

별명

네임 스페이스는 우연한 충돌을 방지하기 위해 보통 긴 이름을 주는데 이름이 너무 길면 입력하기에 번거롭고 코드도 지저분해진다. 이럴 경우 namespace 키워드 다음에 A=B; 형태로 긴 이름 대신 짧은 별명을 정의할 수 있다. 별명은 동일한 대상에 대한 다른 이름이므로 이후 B라는 이름 대신 A를 사용하면 된다. 다음 예를 보자.

 

namespace VeryVeryLongNameSpaceName {

     struct Person { };

}

 

void main()

{

     namespace A=VeryVeryLongNameSpaceName;

     A::Person P;

}

 

아주 긴 이름의 네임 스페이스 이름을 A라는 짧은 별명으로 정의했다. 이후 이 네임 스페이스에 속한 명칭을 참조할 때 A::을 대신 붙이면 된다. 이런 용도라면 #define 매크로 상수를 사용할 수도 있는데 매크로는 전역적이라는 점이 불편하다. 별명 선언문은 이 선언문이 있는 블록에서만 효력을 발휘한다. 여러 단계로 중첩된 네임 스페이스를 사용할 때는 별명이 특히 유용하다.

 

namespace MRG=MyCompany::Research::GameEngine;

 

이상으로 C/C++에 최근 추가된 네임 스페이스에 대해 알아보았다. 명칭 충돌을 해결하기 위한 근본적인 방법으로서 제시된 것이기는 하지만 문법의 복잡성에 비해 실용성이 높다고 보기는 어렵다. 범용 라이브러리를 작성할 때나 한 번 써 볼만하며 또 남이 만들어 놓은 라이브러리를 활용할 때도 네임 스페이스에 대한 사용법을 알고 있어야 한다. C++ 표준 라이브러리는 모두 std 네임 스페이스에 선언되어 있다. 그래서 C++ 프로그램은 통상 using namespace std;로 시작한다.