<개인공부>/[C++]

[C++] namespace 네임스페이스 정리 및 예제

BlockDMask 2021. 2. 1. 00:30
반응형

안녕하세요. BlockDMask입니다.
오늘은 C++의 namespace 이름 공간에 대해서 알아보려고 합니다.
C언어에서는 없던 새로운 개념인데요, 한번 천천히 자세하게 알아보겠습니다.

<목차>
1. namespace 란?
2. namespace의 요소에 접근하는 방법 5가지
3. C++ 표준 이름공간 std

 

 

1. C++ namespace 란?


하나의 프로그램을 만들다 보면 여러 파일이 생성되고, 여러 개발자가 붙어서 개발을 하다 보면 함수나 구조체 등에서 이름이 같은 경우가 생기게 됩니다.

이런 경우 함수의 이름이나 구조체의 이름 그리고 당연하게도 변수의 이름이 같아지게 되면 이름 충돌이 발생하여 오류가 발생하게 됩니다. 

이런 충돌이 발생하지 않기 위해 만들어진 것이 namespace라는 개념입니다.

쉽게 이야기해서 namespace는 함수나 구조체 혹은 변수 이름 등의 소속을 정해주는 것입니다.
소속을 정해주어서 그 소속에 속한 함수, 그 소속에 속한 구조체 등을 딱 지정해서 헷갈리지 않게 하는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
 
void printAll() {
    cout << "printAll 함수" << endl;
}
 
void printAll() {
    cout << "printAll 함수" << endl;
}
 
int main(void)
{
    //printAll 함수 호출
    printAll();
 
    return 0;
}
cs

위 코드와 같이 printAll() 이라는 함수를 어떤 프로그래머와 다른 프로그래머가 만들 수 있다고 하면 이 프로그램은 충돌이 발생하게 됩니다.

아래와 같이 각 printAll 함수를 namespace로 감싸게 되면 충돌 문제를 해결할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;
 
namespace A {
    void printAll() {
        cout << "A의 printAll 함수" << endl;
    }
}
 
namespace B {
    void printAll() {
        cout << "B의 printAll 함수" << endl;
    }
}
 
 
int main(void)
{
    //printAll 함수 호출
    A::printAll();
    B::printAll();
 
    return 0;
}
cs

이렇게 namespace A, namespace B라는 소속을 만들어서 각 함수를 감싸게 되면 이름 충돌을 방지할 수 있습니다.
간단하죠?

namespace [네임스페이스 이름]
{
  함수, 구조체, 변수, 클래스 등등등
}

이런 식으로 하면 [네임스페이스 이름] 소속으로 함수, 구조체, 변수, 클래스 등이 만들어지게 됩니다.
호출을 하는 방법은 아래 "2번 namespace 요소 접근 방법"에서 자세하게 다룰 예정입니다.

 

2. C++ namespace 요소 접근 방법


namespace 요소에 접근하는 방법은 크게는 3개로 나눌 수 있고 작게는 5개로 나눌 수 있습니다.
저는 조금 상세하게 5가지로 나누어 보겠습니다.

1. 한정된 이름(qualified name)을 사용한 접근

네임스페이스이름::요소

이와깉이 네임스페이스 이름을 입력하고 "::"을 통해서 네임스페이스 내부에 있는 요소에 접근하는 방법이 있습니다. 이런 방법을 한정된 이름을 사용한 접근이라고 하는데, 이 방법이 제일 명확합니다.

코드가 길어지고, 코드를 작성할 때 번거로운 단점이 있지만, 이름 충돌을 피할 수 있으며 어디에 속해있는 함수, 변수, 클래스 인지를 명확하게 알 수 있습니다.

바로 예제로 확인해봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;
 
namespace A {
    void printAll() {
        cout << "A의 printAll 함수" << endl;
    }
}
 
namespace B {
    void printAll() {
        cout << "B의 printAll 함수" << endl;
    }
}
 
 
int main(void)
{
    //printAll 함수 호출
    A::printAll();
    B::printAll();
 
    return 0;
}
cs

이렇게 namespace A와 namespace B가 있다고 할때 한정된 이름을 이용한 접근은
A::요소, B::요소 이렇게 접근할 수 있습니다.

A::printAll();
B::printAll();

와 같이 함수를 호출하기 위해서 이런 식으로 사용하면 됩니다.

 

 

2. 전역 using 선언(declaration) 사용한 접근

using 선언은 namespace 소속에 있는 변수 하나, 함수 하나 이렇게 하나지정해서 
이름공간::변수를 "이름공간" 없이 사용하게 해 줍니다.

using 네임스페이스이름::요소;

위처럼 특정 네임스페이스에서 네임스페이스 이름 없이 요소를 호출하고 싶을 때 위와 같이 사용하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;
 
namespace ABC {
    int MAX_CNT = 10;
    
    void init() {
        cout << "ABC의 init() 함수" << endl;
    }
 
    void print() {
        cout << "ABC의 print() 함수" << endl;
    }
}
 
using ABC::init;
 
int main(void)
{
    init();         // 가능
 
    ABC::init(); // 가능
    
 
    ABC::print();
    cout << ABC::MAX_CNT << endl;
    //print();                    호출 불가
    //cout << MAX_CNT << endl;  호출 불가    
 
    return 0;
}
cs

namespace 예제

예제를 보면  using ABC::init 을 이용해서 이름공간 ABC 안에 있는 init 함수를 전역 using 선언을 해주었습니다.
이렇게 되면 해당 파일 어디에서든 ABC::init() 함수를 ABC::을 떼고 init()라는 이름으로 호출할 수 있습니다.

init();으로 호출이 가능하며, 물론 ABC::init()으로 호출해도 상관은 없습니다.

using 선언을 해준 init과는 달리,
using 선언을 하지 않은 ABC 네임스페이스의 다른 변수나 함수 print(), MAX_CNT는 ABC::를 꼭 붙여서 사용해주어야 합니다.

 

3. 전역 using 지시어(directive) 사용한 접근

특정 요소만 접근을 풀어주었던 using 선언과 달리,
using 지시어는 해당 namespce 내부에 있는 모든 요소의 접근을 풀어주게 됩니다.

using namespace 네임스페이스이름;

이렇게 using namespace 네임스페이스 이름; 으로 선언하게 되면 네임스페이스 모든 요소에 "네임스페이스::"을 떼고 접근할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
using namespace std;
 
namespace ABC {
    int MAX_CNT = 10;
    
    void init() {
        cout << "ABC의 init() 함수" << endl;
    }
 
    void print() {
        cout << "ABC의 print() 함수" << endl;
    }
}
 
using namespace ABC; //ABC 이름공간 제약없이 접근 가능
 
int main(void)
{
    init();
    print();                    
    cout << MAX_CNT << endl;
 
    return 0;
}
cs

이름공간 예제

예제를 보면 using namespace ABC를 선언했기 때문에, main 함수 내부에서 처럼 ABC 이름 공간 안에 있는 모든 요소에 "이름공간::" 이 없이 접근이 가능한 것을 확인할 수 있습니다.

 

 

4. 함수 내부 using 선언(declaration) 사용한 접근

위에 2-2에서 배운 "전역 using 선언"과 기능은 다를 게 없지만, 함수 내부에서 using을 선언하였기 때문에,
적용되는 범위가 해당 함수 스코프 내부에서만 적용이 됩니다.

함수(...)
{
  //...

  using 네임스페이스이름::요소;
  // 이 아래부터 해당 요소는 네임스페이스 이름 생략 가능 (이 함수 내부 한정)
  
}

예를 들어 main 함수에서만 using을 적용하고 싶고 다른 함수에서는 using을 적용하고 싶지 않을 때
이런 상황에서 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;
 
namespace QWERTY {
    
    void go() {
        cout << "QWERTY 이름공간의 go() 함수" << endl;
    }
 
    void back() {
        cout << "QWERTY 이름공간의 back() 함수" << endl;
    }
}
 
int main(void)
{
    QWERTY::go();
    //go(); // 불가능
 
    using QWERTY::go; // 여기 아래부터는 main 함수 내부에서는 go() 그냥 사용 가능
    go(); //가능!
 
    return 0;
}
 
// 다른 함수
void example() {
    //go(); 불가능
    QWERTY::go(); //가능
}
cs
 

main 함수 내부를 보면
QWERTY 이름공간에 go 함수를 
using QWERTY::go 를 선언하기 전에는 QWERTY::go() 이렇게만 호출이 가능한데,
using QWERTY::go 를 선언하고 난 이후부터는 go() 이렇게도 호출이 가능해진 것을 볼 수 있습니다.

또한, 전역 using 선언과 달리 example() 함수 내부에서는 go() 이렇게 호출이 불가능한 것을 볼 수 있습니다.

 

 

5. 함수 내부 using 지시어(directive) 사용한 접근

위에 2-3에서 배운 "전역 using 지시어"와 기능은 같습니다.
하지만 그 적용 범위를 하나의 함수 내부로 범위를 좁히는 것입니다.

함수(...)
{
  //...

  using namespace 네임스페이스이름;
  // 이 아래부터 해당 네임스페이스의 모든 요소는 네임스페이스이름 생략 가능
  // 해당 함수 내부 한정

  
}

바로 예제로 확인하시죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;
 
namespace QWERTY {
    
    void go() {
        cout << "QWERTY 이름공간의 go() 함수" << endl;
    }
 
    void back() {
        cout << "QWERTY 이름공간의 back() 함수" << endl;
    }
}
 
int main(void)
{
    QWERTY::go();
    QWERTY::back();
 
    //go();   불가능
    //back(); 불가능
 
    using namespace QWERTY; 
    // 여기 아래부터는 QWERTY 이름공간 내부요소 그냥 접근가능
    
    go(); //가능!
    back(); //가능!
    
    return 0;
}
 
// 다른 함수
void example() {
    //go(); 불가능
    //back(); 불가능
    QWERTY::go(); //가능
    QWERTY::back(); //가능
}
cs

main 함수 내부를 보면
using namespace QWERTY; 를 선언하기 전에는 QWERTY네임스페이스 모든 요소에 "QWERTY::요소"이렇게만 호출이 가능한데,
using namespace QWERTY; 를 선언하고 난 이후부터는 "QWERTY::"를 떼고 go(), back() 이렇게도 호출이 가능해진 것을 볼 수 있습니다.

전역 using 지시어와 달리 example() 함수 내부에서는 "QWERTY::"를 뗄 수 없는 것을 알 수 있습니다.

 

6. 전역 네임스페이스 접근 방법 (global namespace)

네임스페이스에 속해있는 요소에는 한정된 이름 접근 방법에 따라서 "네임스페이스::요소"를 이용해서 딱 그 요소에 접근이 가능합니다.

그럼 전역에 있는 요소를 딱 가리키는 방법은 없을까요?

"::요소" 사용하면 무조건 글로벌 네임스페이스에 접근하게 됩니다.

글로벌 네임스페이스에 접근한다는 것은 전역 요소에 접근한다는 것과 같은 이야기입니다.
이걸 언제 사용하는가 하면 using 선언이나, using 지시어를 통해서 같은 요소가 존재할 수 도 있기 때문에 전역 요소를 명확하게 불러주려고 할 때 사용할 수 있습니다.

전역 이름 공간 예제1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;
 
namespace Car {
    void drive() {
        cout << "Car 이름공간 drive() 함수 호출 " << endl;
    }
}
 
void drive() { cout << "글로벌 이름공간 drive() 함수 호출" << endl; }
 
int main(void)
{
    drive();        // 글로벌 1
    ::drive();        // 글로벌 2
    Car::drive();   // Car 이름공간
 
    return 0;
}
cs

전역 이름공간 예제

 

전역 이름 공간 예제2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;
 
namespace Car {
    void drive() {
        cout << "Car 이름공간 drive() 함수 호출 " << endl;
    }
}
 
using namespace Car; //using 지시어
 
void drive() { cout << "글로벌 이름공간 drive() 함수 호출" << endl; }
 
int main(void)
{
    // drive();        // 함수 이름 충돌로 사용 불가능
 
    ::drive();        // 글로벌
    Car::drive();   // Car 이름공간
 
    return 0;
}
cs

글로벌 네임스페이스 예제

이렇게 using 지시어로 Car 이름공간의 요소를 제약 없이 접근이 가능하도록 만들게 되면
drive() 라는 함수 자체로 호출이 불가능해집니다.
전역 drive() 함수인지, Car::drive() 함수인지 명확하지 않기 때문입니다.

그렇기 때문에 글로벌 네임스페이스 drive를 호출하려면 ::drive() 를 이용하고,
Car 네임스페이스 drive를 호출하려면 using 지시어 선언을 하였다 해도 Car::drive()를 이용해야 합니다.

이렇게 요소의 이름이 겹치게 되면 using 지시어, using 선언 접근이 무의미해지고, 결국 "네임스페이스이름::요소"의 한정된 이름 접근 방법을 사용해야 합니다.

 

 

3. C++ 표준 std 이름공간


C++은 모든 표준 요소를 std 이름 공간에 만들어 둡니다.
그래서 우리가 보통 C++ 코드를 보면 파일 맨 상단에 헤더 파일 include 하고 그다음 
using namespace std; 를 볼 수 있는 것입니다.

C++의 표준 요소들은 다 std라는 이름 공간에 있기 때문에 
cin, cout 등의 함수들을 using 선언 없이 사용한다면, std::cin, std::cout으로 사용해야 하는 것입니다.

1
2
3
4
5
6
#include <iostream>
int main(void)
{
    std::cout << "출력" << std::endl;
    return 0;
}
cs

 

 

이렇게 C++ 이름 공간 namespce에 대해서 알아보았습니다.
using 선언, using 지시어를 사용하여 요소에 접근하는 게 편리하긴 하지만, 적절하게 사용하지 않으면 이름 충돌이 발생할 것입니다.
제일 정확한 방법은 "네임스페이스::요소"처럼 한정된 접근입니다.

반응형