[C++ Basic] namespace(이름공간)란?


C++에서 std::cout 와 std::endl 를 이용하면 각각 출력 역할과 개행 역할을 하며 std::cout을 이용해 원하는 내용을 출력할 수 있습니다. cout, endl 을 보시면 앞에 std가 붙어 있는걸 알 수 있는데,

std는 C++ 표준 라이브러리의 모든 함수, 객체 등이 정의된 이름 공간(namespace) 이라고 합니다.

 

그러면 이름 공간이 뭔지 알아봐야겠죠. 오늘 알아볼 것은 namespace, 한국어 직역으로 이름공간이라고 하는 녀석입니다.

 

이름공간 (namespace)

개인이 간단하게 작성하는 짧은 코드의 경우엔 문제가 안될 수 있지만 보통 기업체나 프로그래머 여러명이서 협업하는 프로젝트의 경우 파일을 나눠서 작업하는 경우가 대부분이기 때문에 파일도 여러개 생기고

특히 함수나 구조체 등에서 이름이 겹치는 문제가 발생하게 됩니다.

 

프로그래밍을 할때는 인터넷에서 코드를 받아써서 만드는 경우가 많은데 이런것 때문에 문제가 발생하기도 하겠죠.

따라서 C++에서는 이러한 문제를 해결하기 위해 같은 이름이라도, 소속된 이름 공간(namespace)이 다르면 다른 것으로 취급하게 되었습니다.

 

namespace는 쉽게 이야기 해서 함수, 구조체, 변수, 클래스 등에 소속을 정해주는 것입니다.

소속을 정해주어서 그 소속에 속한 함수, 그 소속에 속한 구조체 등을 명확하게 지정하여 헷갈리지 않게 사용할 수 있습니다.

 

예를 들어, 대한민국에 철수라는 이름을 가진 분들은 아주 많습니다만

'서울'에 사는 철수인지 '부산'에 사는 철수인지는 엄연히 다르죠.

 

std::cout

위의 경우에도 std라는 이름 공간에 정의되어 있는 cout를 의미합니다.

std:: 없이 그냥 cout만 작성한다면 오류가 발생할것입니다.

컴파일러가 cout가 어디에 있는지 찾지 못하는 것이죠.

 

서울에 사는 철수인지, 부산에 사는 철수인지 알길이 없기 때문입니다.

 

#include <iostream>

int result = 30;

void print_result(int val) {
    std::cout << val << std::endl;
}
 
void print_result(int val) {
    std::cout << val + 30 << std::endl;
}
 
int main(void)
{
    //print_result 함수 호출
    print_result(result);
 
    return 0;
}

별로 좋은 예제는 아닙니다만 void print_result(int val); 라는 함수를 만들어서 전역변수 result 를 출력하는 예제입니다.

보시면 함수 이름과 매개 변수 형태는 같지만 한 함수에서는 그냥 들어온 매개변수를 출력하고 그 다음 함수에서는 매개변수값 + 30 을 출력하는 예시죠.

 

당연히 이 예제를 실행해보면 컴파일이 안될겁니다.

이름이랑 매개변수, 리턴값이 동일한 완전히 겹치는 함수가 하나 있기 때문이죠.

컴파일러는 형태가 완전히 동일한 함수에 있어서 무엇을 선택해야 할지 결정하지 못합니다.

 

물론 C++에서는 매개 변수만 다르다면 함수 오버로딩을 통해 이름이 같은 함수를 여러개 만들 수 있긴 합니다만 다음 예제에서는 완전히 겹치는 함수를 대상으로 합니다.

 

이런 문제를 해결하기 위해선 namespace를 적어줘서 소속을 정해주면 됩니다.

#include <iostream>

int result = 30;

namespace sec_a{
		void print_result(int val) {
	    std::cout << val << std::endl;
	}
}

namespace sec_b{
	void print_result(int val) {
	    std::cout << val + 30 << std::endl;
	}
}
 
int main(void)
{
    //print_result 함수 호출
    sec_a::print_result(result); //sec_a라는 이름 공간에 있는 print_result를 호출
    sec_b::print_result(result); //sec_b라는 이름 공간에 있는 print_result를 호출
    
    return 0;
}

이런식으로.. namespace를 통해 sec_a, sec_b라는 소속을 만들어서

함수를 감싸게 되면 이름 충돌을 피할 수 있게 됩니다.

 

보시면 각각 소속이 구분되어서 출력값을 보시면 30과 60이 잘 출력되게 됩니다.

 

namespace로 소속을 지정할때는 아래와 같이 합니다.

소속 이름을 지어주시고 중괄호로 묶어주시고 따로 세미콜론은 필요없습니다.

namespace [네임스페이스 명]
{
 내용(함수, 변수, 클래스, 구조체 등)
}

 

호출할때는 우선 가장 간단한 방법으로 네임스페이스명::함수명 으로 호출을 해주시면 됩니다.

 

#include <iostream>

namespace A{
	int foo() {return 0;};
	void bar() {return;};
}

namespace B{
	int foo() {return 0;};
	void bar() {return;};
}
 
int main(void)
{
    
    return 0;
}

추가적으로 이 호출을 하는 방법에 대해 조금더 알아보겠습니다.

 

보시면 A라는 이름 공간에 foo() 와 bar() 이라는 함수가 있고

B라는 이름 공간에도 똑같이 foo() 와 bar() 이라는 함수가 있습니다.

 

#include <iostream>

namespace A{
	int foo() {return 0;};
	void bar() {return;};
	
	int func(){
		std::cout << foo(); // 알아서 A::foo() 가 실행됨. 
	}
}

namespace B{
	int foo() {return 0;};
	void bar() {return;};
}
 
int main(void)
{
    A::func();
    return 0;
}

A라는 이름공간에 func() 라는 함수를 추가했고 foo() 를 호출하고 있습니다.

"어 그런데 이 소스코드에는 A라는 공간에도 foo() 가 있고 B라는 공간에도 foo() 가 있는데요" 라고 할 수 있지만

자기 자신이 포함되어 있는 이름 공간 안에서는 굳이 앞에 이름 공간을 명시하지 않아도 자유롭게 불러집니다.

 

예를 들어서 A라는 이름공간 안에서 foo() 를 호출하면 알아서 A::foo() 가 호출되게 됩니다.

A라는 공간에서 호출했기 때문에,

자기 공간에 foo() 라는것이 이미 있어서 찾고 호출하는 것이죠.

 

#include <iostream>

namespace B{
	int foo() {return 0;};
	void bar() {return;};
}
 
 
namespace A{
	int foo() {return 0;};
	void bar() {return;};
	
	int func(){
		std::cout << foo(); // 알아서 A::foo() 가 실행됨. 
		std::cout << B::foo();
	}
}


int main(void)
{
    A::func();
    return 0;
}

물론 B라는 공간에 있는 foo() 를 호출 못하는건 아닌데 그냥 A공간안에서 B::foo() 를 호출해주면 됩니다.

이런식으로요.

 

A와 B순서를 바꿔논건 A를 먼저 만들고 B에 있는걸 호출하면 namespace B가 정의되지 않아 오류가 발생해서 그렇습니다.

 

그런데 자주 호출을 하다보면 저렇게 앞에 네임스페이스명::을 적어주는게 상당히 번거로워 집니다.

제일 간단한 예시로 std::cout, std::endl 이렇게 적는것. 

상당히 귀찮죠.

 

출력이나 개행은 C++ 을 하면서 상당히 많이 사용하는 것인데 앞의 std::를 작성하는데 시간이 꽤 많이 소요됩니다.

(코드도 지저분해지구요.)

 

그래서 아래와 같이 '나는 앞으로 std라는 이름 공간에 들어있는 cout와 endl 만 쓸꺼다!' 라고 선언할 수 있습니다.

 

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

* using 네임스페이스이름::요소; 라고 적어주면 그 요소 하나에 있어서 이름공간 없이 사용하게 해줍니다!

당연하지만 using 선언을 하지 않은 다른 요소들의 경우에는 명시적으로 네임스페이스 이름을 적어줘야 합니다.

 

위의 경우엔 endl과 cout만 std::를 땔 수 있고 다른 std에 속한 것들은 std:: 라고 꼭 적어주고 사용해야 합니다.

#include <iostream>
using std::endl;
using std::cout;

int main(void)
{
    cout << "Test" << endl; //std에 있는 cout와 endl 사용
    return 0;
}

그러면 이렇게 앞의 네임스페이스를 생략할 수 있게 됩니다.

뿐만 아니라 std에 들어있는 모든 것들을 std:: 없이 사용하고 싶다면

 

#include <iostream>
using namespace std;

int main(void)
{
    cout << "Test" << endl;
    return 0;
}

다음과 같이 적어준다면 std:: 를 모두 생략하고 안에 있는 것들을 접근할 수 있습니다.

 

물론 using namespace std; 와 같이 어떤 이름 공간의 요소를 통째로 사용하겠다라고 선언하는 것은 별로 좋은 방법이 아닙니다.

저렇게 using namespace 를 마구 남용하여 추가하면 언젠간 함수 명이 겹치게 되는 문제가 발생하게 됩니다. C++ 표준 라이브러리는 매우 거대하므로 정말 수 많은 함수가 존재하고 있고 std에는 매번 수 많은 함수들이 새롭게 추가되고 있기 때문에 잠재적으로 충돌이 발생할 가능성이 있습니다.

따라서 using namespace std; 와 같은것은 코딩테스트 처럼 시간이 촉박한 상황에서 한정적인 함수들로 구현하는 상황이 아닌 이상 지양하고 std::를 직접 붙여서 사용하거나 using std::cout 와 같이 필요한 것만 사용한다고 명시해주는게 좋습니다. 

 

 

#include <iostream>


namespace {
	//내부 요소들은 이 파일 안에서만 사용할 수 있습니다.
	//static foo(), static bar() 과 동일. 
	int foo() {return 0;};
	void bar() {return;};
}

int main(void)
{
    return 0;
}

재미있게도 C++에서는 이름 공간에 이름을 설정하지 않을 수 있습니다.

이름을 붙여주지 않았는데 무슨 소용이냐구요?

 

이 경우에는 해당 이름 공간에 정의된 것들은 해당 파일안에서만 접근할 수 있게 됩니다.

static 키워드가 namespace 로 묶인 요소들에 적용된다고 보시면 됩니다.

 

헤더 파일을 통해 외부에서 위 내용들을 받아내도 저 익명의 namespace 안에 정의된 모든 것들은 사용할 수 없게 됩니다.

 

* 이러한 것을 익명 네임스페이스라고 합니다.

 

 

내용 참고

모두의 코드 씹어먹는 C++ 강좌

https://blockdmask.tistory.com/474

 

COMMENT WRITE