본문 바로가기

프로그래밍/C++

[C++] 이름 공간(Name space) - 2

들어가며

오랜만이다.

꽤 며칠동안 글 써야지 써야지 각만 재다가

각만 쟀다.

사실 병원이다, 졸업식이다, 설이다, 요다, 소다, 뭐다 아무튼

마냥 놀기만 한 것은 아니었다.

나름 따로 시스템 기획 공부도 찬찬히 하고 있었으니

나름의 합리화를 해본다.

아무튼 오늘의 주제

'이름공간'에 관하여 공부해 보겠다.

솔직히 이제 20살인데 좀 놀아야지. 안 놀면 청춘 낭비지.

 

이름 공간의 중첩

이름 공간의 마지막 특성인 '중첩'이다.

이름 공간은 다른 이름 공간 내부에 삽입 될 수 있다.

즉, 다음과 같은 형태로 중첩이 가능하다.

namespace Brain
{
    int Lv = 1;
    
    namespace Dopamine
    {
        int Lv = 2;
    }
    
    namespace Serotonin
    {
        int Lv = 3;
    }
    
    namespace Norepinephrine
    {
        int Lv = 4;
    }
}

 

위 코드는 논리적인 형태를 보이기에 해석하는 데에 큰 지장은 없으리라 생각한다.

총 4개의 Lv가 존재하는데, 각각이 선언된 이름 공간이 다르기 때문에

이름 충동 문제가 발생하지 않는다.

그럼 어떻게 접근할 수 있을까?

전 시간에 공부 했던 연산자 하나를 활용해 보자.

std::cout << Brain::Lv << std::endl;                      // 1 출력
std::cout << Brain::Dopamine::Lv << std::endl;            // 2 출력
std::cout << Brain::Serotonin::Lv << std::endl;           // 3 출력
std::cout << Brain::Norepinephrine::Lv << std::endl;      // 4 출력

 

이정도면 이름 공간과 관련한 일정한 문법적 규칙을 거의 파악하였으리라 예상해 본다.

 

콘솔 입출력

지금까지 본 필자는 콘솔 입출력을 진행할 때에는 std::cout과 std::cin을 사용해 왔다.

우리는 이것의 정확한 정체를 아직 모르지만, 사용법은 알고 있다.

하지만 이제는 조금이나마 이 녀석들의 정체에 한 발자국이나마 가까이 갈 수 있게 되었다.

'::'연산자를 공부하고 의미를 이해했기 때문이다.

그렇다면 std::cout, std::cin, std::endl 이 셋이 뜻하는 바가 무엇일까?

그렇다.

'이름 공간 'std'에 선언된 cout, cin, endl 함수'를 호출하겠다는 의미이다.

그럼 대충 이 녀석들이 있는 'std'이라는 이름 공간에서 이 녀석들이

namespace std
{
    cout ······
    cin ······
    endl ······
}

 

뭐 이런 식으로 있으리라 충분히 예상해 볼 수 있지 않은가?

비록 정확한 정체를 모르지만 말이다.

 

아무튼 헤더파일 <iostream>에 선언되어 있는 cout, cin, endl은

이름 공간 std안에 선언되어 있다는 결론을 내릴 수 있다.

이렇듯 C++ 표준에서 이름 충돌을 막기 위해 다양한 요소들을 제공하는데

이것들 대부분은 이름 공간 'std' 내부에 선언되어 있다.

 

using을 이용한 이름 공간 명시

이제 cout, cin, endl 을 참조할 때마다 왜 'std::' 을 붙여야 하는지 알게 되었다.

근데, 좀 귀찮다. 안 그런가?

우리는 게으른 프로그래머를 지향하는데,

우리의 신념을 전면으로 도전하는 녀석이지 않은가?

여기서 이제 선대 프로그래머 분들의 사소한 지혜를 볼 수 있다.

#include <iostream>

namepspace Hybrid
{
    void HybFunc()
    {
        std::cout << "So simple function!" << std::endl;
        std::cout << "In namespace Hybrid!" << std::endl;
    }
}

int main()
{
    using Hybrid::HybFunc;        // 키워드 using을 사용하여
    HybFunc();                    // 뭔가 한 것 같다.
    return 0;
}

 

위 코드에서도 확인할 수 있듯이

using Hybrid::HybFunc;

 

이 선언은 'HybFunc 함수를 이름 공간 Hybrid에서 찾으라'는 일종의 선언이다.

형태를 분석해 보자면

  • using (이름 공간) :: (함수명 or 변수명);

이쯤 되겠다.

 

using을 이용한 이름 공간 생략

근데 생각해보니 이 마저도 귀찮다.

가령 iostream의 입출력 함수들을 사용하려고 한다면

using std::cin;
using std::cout;
using std::endl;

매번 이렇게 코드를 적는 것도 여간 귀찮은 일이 아니다.

좀 더 확장해서, 가령 우리가 이름 공간 std에 있는 함수 전부를

위와 같은 식으로 한 줄 한 줄 코드를 추가 한다면...

아마 프로그래머를 그만두고 싶어질 것이다.

그리고 여기서 한번 더 선대 분들의 지혜가 또 다시 빛을 발한다.

using namespace std;

위의 선언은 '이름 공간 std에 선언된 모든 것에 대해서 이름 공간 지정을 생략하겠다.' 라는 의미다.

하지만,

그렇다고 '옳커니!' 하며 남발해서는 안 된다.

이름 공간의 의의에 대해서 다시 한 번 생각해 보자.

이름 충돌을 최소화하고자 사용하는 것인데,

위 코드를 남발하게 되면 그만큼 상대적으로 이름 충돌이 발생할 확률이 증가한다.

 

따라서 무조건 게을러지는 것 보다는 상황을 잘 판단해서 적절히 혼용하는 지혜가 필요하다.

 

이름 공간의 선언 위치

우리는 '지역 변수'와 '전역 변수'가 무엇인지, 어떻게 다른지, 선언의 효력은 어떻게 되는지 알고 있다.

모르는가?

그렇다면 간단하게 집고 넘어가자.

#include <iostream>

int global = 5;                         // 전역 변수 global -> 함수 외부 변수

int main()
{
    int local = 4;                      // 지역 변수 local -> 함수 내부 변수
    std::cout << global << std::endl;   // 5출력
    int global = 4;
    
    // 동일한 이름의 전역 변수가 지역 변수 global에 의해 가려짐
    std::cout << global << std::endl;   // 5가 아닌 4출력
    std::cout << local << std::endl;    // 4출력
    
    return 0;
}

 

요점만 요약하자면,

  • 전역 변수: 함수 외부에 선언된 변수
  • 지역 변수: 함수 내부에 선언된 변수
  • 전역변수코드 전체 영역에 효력을 미칠 수 있다.
  • 전역 변수는 지역변수에 가려진다.
  • 지역 변수는 선언된 함수를 벗어나면 효력을 잃는다.

전역변수와 지역변수에 관한 매우 중요한 특징이다.

마찬가지로,

이름 공간도 같은 맥락으로 위와 같은 특징들을 가진다.

그럼 일단 다음의 두 코드를 보며 분석해 보도록 해보자.

#include <iostream>

using namespace std;   // using 선언을 전역의 형태로 삽입하였다.

int main()
{
    int num = 20;
    cout << "Hello World!" << endl;
    cout << "Hello " << "World!" <<endl;
    cout << num << ' ' << 'A';
    cout << ' ' << 3.14 << endl;
    
    return 0;
}
#include <iostream>

int main()
{
    using namespace std;   // using 선언을 지역의 형태로 삽입하였다.

    int num = 20;
    cout << "Hello World!" << endl;
    cout << "Hello " << "World!" <<endl;
    cout << num << ' ' << 'A';
    cout << ' ' << 3.14 << endl;
    
    return 0;
}

 

첫 번째 코드는 using 선언을 전역의 형태로 삽입하여 이 코드 전역에 효력을 미치고,

두 번째 코드는 using 선언을 지역의 형태로 삽입하여 main함수에 한하여 효력을 미친다.

이정도면 이름 공간에 대한 전역과 지역에 관한 특성들이 이해가 되었으리라 믿는다.

 

이름 공간의 별칭 지정

이름 공간이 중첩되어가면서까지 과도하게 사용되는 경우는 극히 드물다.

그러나 상황이라는 것은 가정해 볼 수 있지 않은가?

혹여 그러한 피치 못 할 상황이 다음과 같이 닥쳤을 때

namespace AAA
{
    namespace BBB
    {
        namespace CCC
        {
            int num1 = 1;
            int num2 = 2;
        }
    }
}

 

다음과 같은 방법으로는 두 변수에 접근하는 것은 좀 귀찮다.

AAA::BBB::CCC::num1 = 10;
AAA::BBB::CCC::num2 = 20;

 

여기서 또  어김없이 선대 분들의 킹혜(지혜)가 나온다.

namespace ABC = AAA::BBB::CCC;

 

위와 같은 방식으로 별칭을 부여할 수 있다.

그럼 또 이를 활용하여 두 변수에 다시 접근하여 보면

ABC::num1 = 10;
ABC::num2 = 20;

 

훨씬 게으르고 편하고 간단하게 접근할 수 있는 형태이지 않은가?

그렇다면 이 상황을 전체 코드의 상황에서 바라보는 경험도 해보도록 하자.

#include <iostream>

using namespace std;

namespace AAA
{
    namespace BBB
    {
        namespace CCC
        {
            int num1 = 1;
            int num2 = 2;
        }
    }
}

int main()
{
    AAA::BBB::CCC::num1 = 10;
    AAA::BBB::CCC::num2 = 20;

    namespace ABC = AAA::BBB::CCC;

    cout << ABC::num1 << endl;
    cout << ABC::num2 << endl;

    return 0;
}

 

코드 해석 능력을 위해 여러가지 요소들을 섞어서 코드를 구성해 보았다.

 

범위 지정 연산자의 또 다른 기능

전역 변수와 지역 변수의 이름이 같을 경우에

전역 변수가 지역 변수에 의해 가려진다는 특징을 위에서 말한 바 있다.

그럼 아무튼 다음 코드를 보자

#include <iostream>

int val = 100;       // 전역 변수

int main()
{
    int val = 20;    // 지역 변수
    val += 2;        // 지역 변수 val의 값 3 증가

    return 0;
}

자 위 코드에서 전역 변수 val의 값을 수정하려면 어떻게 해야 할까.

당연히 표지어에서 나와있다시피 범위 지정 연산자를 사용하면 된다.

#include <iostream>

int val = 100;

int main()
{
    int val = 20;
    val += 2;
    ::val += 10;      // 전역 변수 val의 값 10 증가

    return 0;
}

변수나 함수 명 앞에 붙인 범위 지정 연산자는 무슨 의미인가.

처음엔 필자도 좀 헷갈렸다.

하지만 그 의미는 '범위 지정 연산자를 변수의 이름 앞에 붙이면 해당 변수는 전역으로 사용하라'는 의미이다.

그럼 이정도로 이름 공간에 대해서 마치도록 하겠다.

 

마치며

음, 뭐랄까 되게 열심히 공부를 하고자 하는데

역시 집에서 하면 뭔가 퍼지게 되는 건 어쩔 수 없다.

나 역시 공간의 분할이 얼마나 중요한 지 알면서도

어찌할 방도가 없으니, 빨리 대학교로 상경하고 싶은 마음이 크다.

아무튼 천천히 가더라도 포기하지만 말자라는 마인드로

오늘도 공부한다.


참고 및 출처

  • 윤성우 열혈 C++ 프로그래밍