Namespace
이름 공간
- C++에서 이름 사용 범위를 제어하는 기능이다.
- 대규모의 프로그래밍 프로젝트에서 변수나 함수와 같은 이름이 중복되어 생기는 문제*에 대한 해결책이다.
* Namespace Problem (이름 공간 문제) : 두 라이브러리에서 같은 이름의 변수, 함수가 정의되어 있어서 생기는 충돌 문제이다.
Declarative Region (선언 영역)
- 말 그대로, 선언할 수 있는 영역을 의미한다.
ex) 전역 변수의 선언 영역 : 모든 함수의 바깥 영역
ex) 함수 내의 지역 변수의 선언 영역 : 해당 함수 내부
- \(\texttt{namespace}\)는 이름을 선언하는 영역을 따로 제공하는 기능이다.
Potential Scope (잠재 사용 범위)
- 해당 변수를 선언한 지점부터 선언 영역의 끝까지를 지칭하는 용어이다.
- 잠재 사용 범위는 선언 영역보다 좁다.
- 전역 변수의 경우, 지역 변수와 이름이 중복되면 해당 영역에서의 잠재 사용 범위가 가려진다.
+ 실질적으로, 변수를 사용할 수 있는 영역을 Scope(사용 범위)라 한다.
Namespace Declaration (이름 공간 정의)
- 이름 공간은 전역 위치 또는 다른 이름 공간 내부에 놓일 수 있으며, \(\texttt{using}\) 지시자나 \(\texttt{using}\) 선언 또는 \(\texttt{::}\) 연산자를 통해 다른 이름 공간의 특정 이름을 선언시킬 수도 있다. (그러므로, 이름 공간 내에 선언된 이름은 기본적으로 외부 링크를 갖는다.)
// Ex. 이름 공간의 중첩 선언 예시
namespace elements {
namespace fire {
int flame;
...
}
float water;
}
// Ex. 이름 공간 내의 using 지시자와 using 선언 예시
namespace myth {
using Jill:fetch;
using namespace elements;
using std::cout;
...
}
std::cin >> myth::fetch; // 가능하다.
std::cout << Jill::fetch; // 가능하다.
using namespace myth;
cin>> fetch; // 가능하다.
- 사용자 정의 이름 공간과는 별개로, Global Namespace(전역 이름 공간)라는 파일 수준의 선언 영역도 존재한다.
- 이름 공간은 블록 내부에는 놓일 수 없다. (블록 내에만 정의되는 이름공간은 의미가 없다.)
- 이름 공간의 열린("Open")성격으로 인해, 기존의 이름 공간이 정의된 이후에 추가로 새로운 이름을 정의할 수 있다.
// Ex. 이름 공간의 열린 성질 예시
namespace Jill {
double bucket(double n {...} // 이름 공간내에 정의된 함수
double fetch;
int pal;
struct Hill {...} // 이름 공간내에 정의된 구조체
}
...
namespace Jill { // 위에 정의된 이름 공간과 동일한 이름 공간
char* goose(const char*); // 추가된 이름 ("Namespace are open")
}
// 이를 이용해서, 앞 부분의 이름 공간에는 함수 원형을 선언하고,
// 뒤 따르는 이름 공간에는 함수 정의를 기술하는 것이 가능하다.
Namespace Access (이름 공간 접근)
- 이름 공간에 정의된 이름을 사용하는 방법으로 \(\texttt{using}\) Directive (\(\texttt{using}\) 지시자) 과 \(\texttt{using}\) Declaration (\(\texttt{using}\) 선언), Scope Resolution Operator (사용 범위 결정 연산자, \(\texttt{::}\))가 있다.
- \(\texttt{using}\) 지시자와 \(\texttt{using}\) 선언이 놓이는 위치(전역 공간, 지역 공간 등)에 따라 Unqualified Name의 사용 범위가 결정된다.
- \(\texttt{using}\) 지시자와 \(\texttt{using}\) 선언은 코딩 편의성을 높이는 대신 이름 충돌 가능성도 높이게 되는 반면, \(\texttt{::}\) 연산자를 통한 접근은 편의성은 낮은 대신 이름 충돌 가능성 또한 낮추는 효과가 있다.
- 컴파일러는 Ambiguity를 피하기 위해 다른 이름 공간의 같은 이름에 대한 \(\texttt{using}\) 선언을 허용하지 않는다.
// Ex. 허용되지 않는 using 선언 예시
using Jack::pal;
using Jill::pal; // 허용되지 않는다.
1. \(\texttt{using}\) Directive (\(\texttt{using}\) 지시자)
- \(\texttt{::}\) 연산자 없이 이름 공간 전체에 접근 가능하게 하는 방법이다.
- \(\texttt{using namespace Name;}\) 형태를 따른다. (\(\texttt{Name}\)에는 이름공간의 이름이 온다.
// Ex.using 지시자 사용 예시
using namespace Jill; // 해당 이름 공간 내의 모든 이름을 ::연산자 없이 이용할 수 있다!
fetch = 2.54;
bucket(fetch);
pal++;
- \(\texttt{using}\) 지시자는 Transitive적 특성이 있어 큰 범주의 이름 공간이 \(\texttt{using}\) 지시자를 통해 접근되면, 해당 이름 공간 내의 모든 이름에 대해 Unqualified Name으로써 접근이 가능해진다.
* 이름 공간의 Open적 특성 때문에, 이름 공간의 정의가 여러 곳에 분산되어 존재하므로, 이름 간 충돌을 방지하기 위해서는 \(\texttt{using}\) 지시자는 가급적 사용을 지양해야 한다.
2. \(\texttt{using}\) Declaration (\(\texttt{using}\) 선언)
- 이름 공간 내, 하나의 이름을 \(\texttt{::}\) 연산자 없이 접근 가능하게 하는 방법이다.
// Ex.using 선언 사용 예시
using Jill::fetch;
fetch = 1.23; // Jill 이름 공간을 명시하지 않아도 된다!
- \(\texttt{using}\) 선언을 통해 이름 공간 내에 정의된 함수에 접근하는 경우, 함수의 Signature(매개변수 정보)는 따로 명시할 필요 없이, 함수의 이름만 명시하면 된다.
(그렇기 때문에, 같은 이름으로 다수의 함수가 오버로딩 되어있는 경우 그 함수들이 모두 접근 가능해진다.)
// Ex. 함수에 대한 using 선언 예시
namespace debts {
void getDebt (int& rd) {...}
void showDebt (const int& rd) {...}
double sumDebts (const int ar[], int n) {...}
}
using debts::showDebt; // showDebt 함수를 ::연산자 없이 이용할 수 있으며, 함수 시그니처는 명시하지 않았다.
3. Scope Resolution Operator (\(\texttt{::}\) 연산자)
- 어떤 이름을 미리 정의된 이름 공간내의 이름으로 지정(Qualify)하는 방법이다.
* Qualified Name(제한된 이름) : \(\texttt{::}\) 연산자를 통해 이름 공간이 지정된 이름을 지칭한다.
ex) \(\texttt{Jill::pal}\)
* Unqualified Name(제한되지 않은 이름) : 별다른 이름 공간 지정없이 단독으로 존재하는 이름을 지칭한다.
ex) \(\texttt{pal}\)
Override (이름들 간의 우선순위)
- 이름 공간의 이름과 지역 이름간의 Conflict(충돌)가 발생하면 컴파일러는 이에 대한 경고 메세지를 출력한다.
- \(\texttt{using}\) 지시자들 통해 선언된 중복된 이름의 지역 이름과 공존하게 되면, 지역 버전이 이름 공간 버전을 Override 하며(우선시 되며), 별도의 경고 메세지는 출력되지 않는다.
// Ex. 이름 공간 변수와 지역 변수간의 Override 관계 예시
namespace Jill {
double bucket(double n) {...}
double fetch;
struct Hill {...};
}
char fetch; // 전역변수를 정의한다.
int main() {
using namespace Jill; // using 지시자
Hill Thrill;
double water = bucket(2);
double fetch;
cin >> fetch; // main()의 지역변수 fetch
cin >> ::fetch; // 전역변수 fetch
cin >> Jill::fetch; // Jill 이름 공간의 fetch (using 지시자 선언이 있어도, 다시 사용할 수 있는 표현이다.)
...
}
Namespace Alias (이름 공간의 대용 이름)
- 이름 공간의 포함 관계가 복잡할 경우에 유용하게 쓰이는 수단이다.
Ex. 이름 공간의 Alias 예시
namespace my_very_favorite_things {...};
namespace mvft = my_very_favorite_things; // 단순히 이름만 바꾸는 예시
namespace MEF = myth::elements::fire; // 3개의 namespace 단계
using MEF::flame;
Unnamed Namespace (무명 이름 공간)
- 이름 공간의 이름을 따로 지정하지 않은 이름 공간이다.
- \(\texttt{using}\) 지시자 + 이름 공간 정의의 개념이다.
- 잠재 사용 범위는, 무명 이름 공간을 포함하고 있는 선언 영역의 끝까지이다.
// Ex. 무명 이름 공간 정의 및 사용 예시
namespace {
int ice;
int bandycoot;
}
ice = 0;
...
- 이름 공간의 이름이 따로 정의되어 있지 않기 때문에 일회성이며, 다른 파일에서 사용할 수 없다.
(이 때문에, 내부 링크를 가지는 정적 변수 대신 무명 이름 공간을 활용할 수도 있다.)
// Ex. 정적 변수를 대신하는 무명 이름 공간 예시
static int counts;
// 위 구문과 아래 구문은 기능적으로 일치한다.
namespace {
int counts;
}
Guidelines for using Namespace (이름 공간 사용 가이드라인)
- 외부 전역 변수 대신, 이름 공간에 정의된 변수를 사용하라.
- 정적 전역 변수 대신, 무명 이름 공간에 정의된 변수를 사용하라.
- 개발중인 라이브러리 함수 또는 라이브러리 클래스를 하나의 이름 공간 내에 정의하라.
- \(\texttt{using}\) 지시자는 구버전 코드에 이름 공간을 적용하여 변환시키는 임시 수단으로써만 사용하라.
(\(\texttt{iostream.h}\)와 같은 구버전의 헤더 파일에는 이름 공간을 사용하지 않는다.)
- \(\texttt{using}\) 지시자를 헤더 파일내에 정의하지 마라.
(어떤 이름을 사용하게 되는지를 알 수 없게 되며, 헤더 파일들이 포함되는 순서에 따라 수행되는 작업에 영향을 미칠 수 있게 된다.)
- \(\texttt{using}\) 지시자 혹은 \(\texttt{using}\) 선언은 모든 \(\texttt{#include}\) Preprocessor 지시문 뒤에 위치시켜라.
- \(\texttt{using}\) 지시자 대신, \(\texttt{using}\) 선언이나 \(\texttt{::}\) 연산자를 이용하여 Conflicts 발생 확률을 줄여라.
- \(\texttt{using}\) 선언을 전역 범위 대신 선택적으로 지역 범위에 사용하라.