
이름공간의 등장배경
프로그램이 대형화되어 가면서 이름의 충돌문제가 등장했다.
회사 세 군데에서 은행관리 시스템을 개발하는데, A회사가 B회사의 코드를 많이 베껴 구현해 함수의 이름들이 거의 다 같은 것이 아닌가?
함수의 이름을 하나하나 바꾸기엔 너무나도 힘든 일이다.
그렇다면 무엇이 해결책이었을까?
프로젝트 진행 전 함수 및 변수의 이름을 모두 정해 이름충돌이 발생하지 않게 하는 것이 해결책인가?
이는 근본적인 해결책이 되지 못한다.
그래서 C++의 표준에서는 '이름공간(namespace)' 이라는 문법을 정의해 이에 대한 근본적인 해결책을 제시한다.
이름공간의 기본원리
NameSp1.cpp
#include <iostream>
namespace BestComImpl{
void SimpleFunc(void) {
std::cout<<"BestCom이 정의한 함수"<<std::endl;
}
}
namespace ProgComImpl{
void SimpleFunc(void) {
std::cout<<"ProgCom이 정의한 함수"<<std::endl;
}
}
int main(void) {
BestComImpl::SimpleFunc();
ProgComImpl::SimpleFunc();
return 0;
}
BestCom이 정의한 함수
ProgCom이 정의한 함수
위의 예제를 보면 아래와 같이 이름공간을 마련한 것을 볼 수있다.
namespace BestComImpl{
void SimpleFunc(void) {
std::cout<<"BestCom이 정의한 함수"<<std::endl;
}
}
위를 보면 BestComImpl이라는 이름공간을 마련했고, 이 안에 함수 SimpleFunc를 정의했다. 따라서 이 함수는 'BestComImpl::SimpleFunc()'이라고 지칭한다.
namespace ProgComImpl{
void SimpleFunc(void) {
std::cout<<"ProgCom이 정의한 함수"<<std::endl;
}
}
위를 보면 ProgComImpl이라는 이름공간을 마련했고, 이 안에 함수 SimpleFunc를 정의했다. 따라서 이 함수는 'ProgComImpl::SimpleFunc()'이라고 지칭한다.
위 예제에서 사용된 연산자 :: 을 가리켜 '범위지정 연산자(scpoe resolution operator)'라 한다.
이름공간 기반의 함수 선언과 정의의 구분
함수는 선언과 정의를 분리하는 것이 일반적이다.
'함수의 선언'은 헤더파일에,
'함수의 정의'는 소스파일에 저장하는 것이 일반적이다.
NameSp2.cpp
#include <iostream>
namespace BestComImpl{
void SimpleFunc(void);
}
namespace ProgComImpl{
void SimpleFunc(void);
}
int main(void) {
BestComImpl::SimpleFunc();
ProgComImpl::SimpleFunc();
return 0;
}
void BestComImpl::SimpleFunc(void) {
std::cout<<"BestCom이 정의한 함수"<<std::endl;
}
void ProgComImpl::SimpleFunc(void) {
std::cout<<"ProgCom이 정의한 함수"<<std::endl;
}
BestCom이 정의한 함수
ProgCom이 정의한 함수
함수의 선언 부분은,
namespace BestComImpl{
void SimpleFunc(void);
}
namespace ProgComImpl{
void SimpleFunc(void);
}
함수의 정의 부분은,
void BestComImpl::SimpleFunc(void) {
std::cout<<"BestCom이 정의한 함수"<<std::endl;
}
void ProgComImpl::SimpleFunc(void) {
std::cout<<"ProgCom이 정의한 함수"<<std::endl;
}
참고로, 동일한 이름공간에 정의된 함수를 호출할 때에는 이름공간을 명시할 필요가 없다. 그리고 둘 이상의 이름공간에 나누어서 함수를 추가할 수 있다.
다음 예제를 통해 이해를 돕겠다.
NameSp3.cpp
#include <iostream>
namespace BestComImpl{
void SimpleFunc(void);
}
namespace BestComImpl{
void PrettyFunc(void);
}
namespace ProgComImpl{
void SimpleFunc(void);
}
int main(void) {
BestComImpl::SimpleFunc();
return 0;
}
void BestComImpl::SimpleFunc(void) {
std::cout<<"BestCom이 정의한 함수"<<std::endl;
PrettyFunc(); //동일 이름공간
ProgComImpl::SimpleFunc(); //다른 이름공간
}
void BestComImpl::PrettyFunc(void) {
std::cout<<"So Pretty!!"<<std::endl;
}
void ProgComImpl::SimpleFunc(void) {
std::cout<<"ProgCom이 정의한 함수"<<std::endl;
}
BestCom이 정의한 함수
So Pretty!!
ProgCom이 정의한 함수
위의 예제 코드를 확인하면 알수 있듯,
namespace BestComImpl{
void SimpleFunc(void);
}
namespace BestComImpl{
void PrettyFunc(void);
}
이렇게 둘 이상의 영역에 나누어서 함수를 추가할 수 있다. SimpleFunc, PrettyFunc는 BestComImpl이라는 동일한 이름공간에 존재한다.
이름공간의 중첩
이름공간은 다른 이름공간 안에 삽입될 수 있다. 다음과 같은 형태로 말이다.
namespace Parent {
int num=2;
namespace SubOne {
int num=3;
}
namespace SubTwo {
int num=4;
}
}
위의 코드에는 num이 3개가 존재한다. 하지만 각각이 선언된 이름공간이 다르기에 이름충돌이 일어나지 않았다. 다음의 문장을 실행하면,
std::cout << Parent::num <<std::endl;
std::cout << Parent::SubOne::num <<std::endl;
std::cout << Parent::SubTwo::num <<std::endl;
를 실행하면 순서대로 2, 3, 4가 출력된다.
std::cout, std::cin, std::endl
지금까지 콘솔 입출력을 진행할 때 std::cout, std::cin을 사용해 왔다. 그런데 이제 ::연산자의 의미를 이해했기 때문에 다음 세가지가 뜻하는 바를 설명할 수 있다.
std::cout
std::cin
std::endl
순서대로,
"이름공간 std에 선언된 cout"
"이름공간 std에 선언된 cin"
"이름공간 std에 선언된 endl"
따라서 다음과 같은 이름공간의 구성을 생각할 수 있다.
namespace std {
cout . . . .
cin . . . .
endl . . . .
}
헤더파일 < iostream > 에 선언되어 있는 cout, cin 그리고 endl은 이름공간 std안에 선언되어 있다는 결론을 내릴 수 있다. 이렇듯 이름충돌을 막기 위해, C++ 표준에서 제공하는 다양한 요소들은 이름공간 std안에 선언되어 있다.
using을 이용한 이름공간의 명시
UsingDcl1.cpp
#include <iostream>
namespace Hybrid {
void HybFunc(void) {
std::cout<<"So Simple function!"<<std::endl;
std::cout<<"In namespace Hybrid!"<<std::endl;
}
}
int main(void) {
using Hybrid::HybFunc;
HybFunc();
return 0;
}
So Simple function!
In namespace Hybrid!
이 예제에서는 Hybrid라는 이름공간 안에 선언된 함수를 범위지정 없이 그냥 호출할 수 있음을 보이고 있다.
using Hybrid::HybFunc;
키워드 using을 이용해 '이름공간 Hybrid에 정의된 HybFunc를 호출할 때에는, 이름공간을 지정하지 않고 호출하겠다!'는 것을 명시(선언)하고 있다.
HybFunc();
이렇게 using선언을 통해 이름공간의 지정 없이 HybFunc() 함수를 호출하고 있다.
이때, 위 예제에서는 using 선언이 main 함수 내에 존재하는데, 이러한 경우 지역변수의 선언과 마찬가지로 선언된 이후부터 효력을 발휘하며ㅡ, 선언된 지역을 벗어나면, 선언의 효력은 잃게 된다.
따라서 프로그램 전체영역에 효력을 미치게 하려면 전역변수와 마찬가지로 함수 밖에 선언을 해야한다. 다음 예제를 살펴보자.
UsingDcl2.cpp
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main(void)
{
int num=20;
cout<<"Hello World!"<<endl;
cout<<"Hello "<<"World!"<<endl;
cout<<num<<' '<<'A';
cout<<' '<<3.14<<endl;
return 0;
}
Hello World!
Hello World!
20 A 3.14
예제를 보면 using 선언을 함수 밖에 전역의 형태로 삽입했다. 따라서 이제부터는 cin, cout, endl의 사용에 있어 이름공간의 지정이 불필요하고, 이를 통해 코드의 구성이 한결 간단해졌다.
만약에 위 예제에서 보이는 것처럼 일일이 using 선언을 하기가 귀찮다면,
'이름공간 std에 선언된 모든 것에 대해 이름공간 지정의 생략'을 명령할 수 있다.
using namespace std;
일일이 std 이름공간에 있다는 것을 지정하는 게 너무 귀찮고 코드가 지저분해 보였는데 드디어 한결 편안한 코딩을 할 수 있게 되었다!!
UsingDcl3.cpp
#include <iostream>
using namespace std;
int main(void)
{
int num=20;
cout<<"Hello World!"<<endl;
cout<<"Hello "<<"World!"<<endl;
cout<<num<<' '<<'A';
cout<<' '<<3.14<<endl;
return 0;
}
하지만 우리가 볼 때 위 예제의 using namespace 선언이 매우 매력적으로 보일거다.
하지만 이렇게 너무 빈번한 using namespace의 선언은 이름의 충돌을 막기위한 이름공간의 선언을 의미 없게 만든다.
따라서 제한적으로 사용할 필요가 있다.
이름공간의 별칭 지정
이름공간이 중첩되면서까지 과도하게 사용되는 경우는 드물지만, 과도하게 사용되었을 때의 예를 들어보자.
NameAlias.cpp
#include <iostream>
using namespace std;
namespace AAA {
namespace BBB {
namespace CCC {
int num1;
int num2;
}
}
}
int main(void) {
AAA::BBB::CCC::num1=20;
AAA::BBB::CCC::num2=30;
namespace ABC = AAA::BBB::CCC;
cout<<ABC::num1<<endl;
cout<<ABC::num2<<endl;
return 0;
}
20
30
num1, num2에 접근할 때 다음과 같이 접근하게 되면 폼도 안나고, 불편하다.
AAA::BBB::CCC::num1=20;
AAA::BBB::CCC::num2=30;
그래서 이러한 경우에는 AAA::BBB::CCC에 별칭을 줄 수 있다.
namespace ABC = AAA::BBB::CCC;
이렇게 되면 num1, num2에 다음과 같이 접근이 가능하다.
ABC::num1=10;
ABC::num2=20;
이 얼마나 편리한가?
범위지정 연산자(Scope Resolution Operator)의 또 다른 기능
지역변수의 이름이 전역변수의 이름과 같은 경우,
전역변수는 지역변수에 의해 가려진다.
int val=100;
int SimpleFunc(void) {
int val=20; // 지역변수
val+=3; // 지역변수 val의 값 3 증가
}
위의 코드에서 보이듯이 SimpleFunc내에서 전역변수와 이름이 동일한 지역변수 val이 선언되었으므로 val+=3;은 전역변수 val이 아닌, 지역변수 val의 값을 3 증가시킨다.
그렇다면, 전역변수 val에 접근하려면 어떻게 해야할까? 이 때에도 '범위지정 연산자'를 사용하면 된다.
int val=100;
int SimpleFunc(void) {
int val=20; // 지역변수
val+=3; // 지역변수 val의 값 3 증가
::val+=7; // 전역변수 val의 값 7 증가
}