[C++] namespace
출처 - http://cafe.naver.com/cppmaster/1175
6.1 Namespaces 원리와 역사적 배경
6.1.1 Namespaces 이면에 존재하는 원리
Namespaces는 1995년에 C++ 표준으로 도입되었다.
우선 왜 namespaces가 언어에 추가되었는지를 이해하기 위해서, 여기에 하나의 비유가 있다.
당신의 컴퓨터에 있는 파일시스템이 디렉토리들과 서브디렉토리들을 전혀 가지지 않는다고 상상하자.
모든 파일들은 항상 모든 사용자와 애플리케이션에게 보여지는 단순한 저장소에 저장될 것이다.
결론적으로, 극단적인 어려움들이 발생할 것이다.
파일이름들이 충돌 할 것이고(몇몇 시스템들은 파일 이름을 8개의 문자로 제한하고 + 확장자들을 3개의 문자들로 제한하고서도 이것은 빈번히 발생할 것이다), 파일들의 목록화, 복사 또는 탐색은 훨씬 더 어려워질 것이다.
게다가, 보안 및 권한 제한들은 심각하게 손상될 것이다.
C++에서 namespace는 디렉토리들과 동일하다.
그것들은 쉽게 중첩(nest)될 수 있으며, 그들은 당신의 코드를 name conflicts로부터 보호하고, 당신이 선언들을 감출 수 있도록 하며, 어떤 실행시간 또는 메모리 오버헤드를 초래하지 않는다.
C++ Standard Library의 구성성분들 대부분은 namespace std하에 그룹화된다.
Namespace std는 STL의 오버로드된 연산자들의 정의들을 포함하고 있는 std::rel_ops와 같은 추가적인 namespace들로 분할된다.
6.1.2 간략한 역사적 배경
1990년대 초기에, C++이 범용 프로그래밍 언어로서 대중성을 얻었을 때 많은 벤더들이 다양한 컴포넌트 클래스들의 고유한 구현들을 선적하였다.
string 조작들, 수학적 함수들 그리고 데이터 컨테이너들을 위한 클래스 라이브러리들은 MFC, STL, OWL 및 다른 것들과 같이 구조의 통합된 부분들이었다.
재사용 가능한 컴포넌트들의 확산은 name 충돌문제의 원인이 되었다.
예를 들어 vector라 명명된 클래스는 둘 다 동시에 사용되었던 수학적 라이브러리와 또 다른 컨테이너 라이브러리에서 나타날 수 있다.
또는 string이라 명명된 클래스는 거의 모든 프레임워크 및 클래스 라이브러리에서 발견될 수 있다.
컴파일러가 동일한 이름들을 갖는 다른 클래스들을 구분하는 것은 불가능한 것이었다.
이와 유사하게, 링커들은 구분이 불가능한 이름들을 갖는 클래스들의 멤버 함수들의 동일한 이름들에 대처할 수 없었다.
예를 들어,
멤버함수는 두 개의 다른 클래스들 속에서 정의될 수 있다 - 첫 번째 클래스는 수학 라이브러리의 클래스일 수 있고 다른 클래스는 어떤 컨테이너 라이브러리에 속할 수 있다.
그리고 대규모 프로젝트들은 name 충돌들에 더 취약하다, name 충돌들은 서드파티 소프트웨어 라이브러리들로 한정되지 않는다.
큰 규모의 소프트웨어 프로젝트들에서, 동일한 이름이 다른 개발자들에 의해 다른 엔티티를 가리키기 위해서 한번 이상 사용될 수도 있다.
pre-namespace 시대에, 유일한 문제해결 방법은 한정자의 이름들에 다양한 접두사들을 사용하는 것이었다.
그러나 이러한 행위는 지루하고 에러를 발생시키는 경향이 있었다.
6.2 Namespace의 특성들
Namespaces는 name 컨테이너들 그 이상이다.
그것들은 어떤 오버헤드를 가하지 않고서 오래된 코드의 빠르고 간결한 이동을 허용하기 위해 설계되었다.
Namespaces는 그들의 사용들을 용이하게 하는 몇 가지 특징들을 가진다.
다음 절은 이들 특성들을 논의한다.
6.2.1 완전하게 한정된 이름들(Fully qualified names)
하나의 namespace는 선언들과 정의들이 함께 그룹화되는 범위이다.
다른 범위로부터 이들 중 어떤 것을 참조하기 위해서는 완전하게 한정된 이름이 요구된다.
한정자의 완전히 한정된 이름은 그것의 namespaces, 그것에 이어지는 범위 분해 연산자(scope resolution operator)(::), 그것의 클래스 이름과 마지막으로 <st1:personname w:st="on"><st2:sn w:st="on">한</st2:sn><st2:givenname w:st="on">정자</st2:givenname></st1:personname>(identifier) 그 자신으로 이루어진다.
namespaces 및 클래스들 모두 중첩될 수 있기 때문에, 결과하는 이름은 다소 길어질 수 있지만 그것은 고유한 식별(identification)을 보장한다:
그러나, 완전히 한정된 이름을 반복하는 것은 지루한 일이며 가독성이 낮다.
대신에, 당신은 using 선언(declaration) 또는 using 지시어(directive)를 사용할 수 있다.
6.2.2 using 선언 및 using 지시어(directive)
using 선언은 키워드 using, 그에 이어지는 namespace::member로 이루어진다.
그것은 마치 완전히 한정된 이름이 공급된 것처럼, 컴파일러가 특정한 namespace에서의 어떤 <st1:personname w:st="on">한정자</st1:personname>(타입, 연산자, 함수, 상수 및 등등)의 모든 발생의 위치를 정하도록 명령한다.
예를 들면,
다른 한편 using 지시어는 지시어의 범위에서 접근 가능한 지정된 namespace의 모든 이름들을 묘사한다.
그것은 다음의 순서로 구성된다.
using namespace와 그것에 이어서 namespace 이름.
예를 들면,
6.2.3 Namespaces는 확장될 수 있다
C++ 표준화 위원회는 관련있는 선언들이 몇몇 번역단위들(translation units)에 걸쳐서 확대될 수 있다는 사실을 잘 알고 있었다.
그 결과, 하나의 namespace가 부분적으로 정의될 수 있다.
예를 들어,
별개의 파일에서, 동일한 namespace가 추가적인 선언들을 가지고 확대될 수 있다.
완전한 namespace MyProj는 다음처럼 두 개의 파일들 모두로부터 추출될 수 있다.
6.2.4 Namespace 별칭들(aliases)
당신이 관찰할 때, namespace를 위하여 짧은 이름을 선택하는 것은 언젠가는 name 충돌로 이끌 것이다.
그러나, 매우 긴 이름들은 사용하기 쉽지 않다.
이러한 목적으로, namespace 별칭들이 사용될 수 있다.
다음의 예는 다루기 힘든 Excel_Software_Company namespace를 위하여 별칭 ESC를 정의한다.
Namespace 별칭들은 당신이 곧 보게 되듯이 다른 유용한 목적들을 가진다.
6.2.5 쾨니흐 검색(Koenig Lookup)
C++의 창시자들 중 한사람인 Andrew Koenig는 namespace 멤버들의 검색(lookup)을 해결하기 위한 알고리즘을 고안하였다.
argument dependent lookup이라 불리기도 하는 이 알고리즘은 다음과 같은 경우들을 다루기 위하여 표준 호환의 모든 컴파일러들에서 사용된다.
using 선언도 using 지시자도 프로그램에 존재하지 않는다.
그럼에도 컴파일러는 올바르게 작동한다 - Koenig 검색을 적용함으로써 그것은 한정되지 않은 name func를 namespace MINE에서 선언된 함수로서 정확하게 식별한다.
Koenig 검색은 컴파일러가 로컬 영역과 같은 보통의 장소들에서 뿐만 아니라 argument의 타입을 포함하는 namespace에서도 찾도록 명령한다.
그 결과, 다음 소스 라인에서, 컴파일러는 함수 func()의 argument인 객체 c가 namespace MINE에 속한다는 것을 알아낸다.
결론적으로 컴파일러는 프로그래머의 의도를 "추측하여" func()의 선언의 위치를 찾기 위하여 namespace MINE을 검색한다.
Koenig 검색 없는 namespaces는 완전하게 한정된 이름들을 반복적으로 지정하거나 using 선언들을 무수하게 사용한 프로그래머에게 받아들일 수 없는 지루함을 부과한다.
Koenig 검색을 그 이상으로 사용하여 argument를 push하기 위해 다음 예제를 고려하자.
using 선언은 main()의 범위속으로 std::cout을 삽입하고, 그것에 의해서 프로그래머가 비한정된 이름 cout을 사용할 수 있도록 한다.
그러나 당신이 기억할 수 있듯이 오버로드된 << 연산자는 std::cout의 멤버가 아니다.
그것은 namespace std에서 정의된 friend 함수이며, 그래서 그것은 그것의 argument로서 std::ostream 객체를 취한다.
Koenig 검색없이 프로그래머는 다음과 비슷한 무엇인가를 작성해야만 한다.
대신에, 프로그래머는 using namespace std; 지시자를 제공할 수 있다.
그러나 이들 옵션들 중 그 어떤 것도 바람직하지 않은데, 그 이유는 그것들이 코드를 혼란스럽게 하고 혼동과 에러들의 원인이 될 수 있다.
(using 지시자들은 현재의 범위에서 볼 수 있는 이름들을 나타내기 위한 가장 적게 유리한 형태인데 그 이유는 그들이 namespace의 모든 멤버들을 무차별적으로 보여지도록 만들기 때문이다.)
다행하게도, Koenig 검색은 “올바르게 동작하며” 우아한 방법으로 이러한 지루함으로부터 당신을 구원한다.
Koenig 검색은 자동적으로 적용된다.
어떤 특수한 지시자들 또는 설정 스위치들이 그것을 활성화하기 위해 요구되지 않으며 그것을 끌 수 있는 어떤 방법도 존재하지 않는다.
이 사실을 염두에 두어야만 하는데 그 이유는 어떤 상황들에서 놀라운 결과들을 가져올 수 있기 때문이다.
예를 들면,
표준과 호환되는 컴파일러는 NS1::f(NS1::B) 및 f(NS1::B)사이의 모호함에 대해 에러를 제기해야 한다.
그러나 비호환 컴파일러들은 모호한 호출에 대해 불평하지 않는다; 그것들은 단순히 f()의 버전들 중 하나를 선택한다.
그러나 이것은 프로그래머가 의도하는 버전이 되지 않을 것이다.
게다가, 문제는 f()의 추가적인 버전들이 프로젝트에 더해질 때인 개발의 더 나중 단계에서만 발생할 것이다 - 그것은 컴파일러의 검색 알고리즘을 방해할 수 있다.
그것은 또한 두 가지의 namespaces가 서로 관련될 때 발생할 것이다 - 예를 들어, 만일 namespace가 다른 namespace에서 선언되는 클래스 멤버 함수의 파라미터들로서 사용되는 클래스들을 선언할 때.
6.2.6 실제에서의 Namespaces
이전의 예제들로부터 이끌어낼 수 있는 결론은 다른 언어 특징들과 마찬가지로 namespaces는 현명하게 사용되어야만 한다는 것이다.
약간의 클래스들과 약간의 소스 파일들만을 포함하는 작은 프로그램들을 위하여, namespace는 불필요하다.
대부분의 경우들에서, 그러한 프로그램들은 한 명의 프로그래머에 의해서 코딩되고 유지되며, 그 결과 그들은 제한된 수의 컴포넌트들을 사용한다.
이러한 경우에 name 충돌들의 가능성은 다소 작다.
만일 name 충돌들이 여전히 발생한다면, 존재한 클래스들 및 함수들의 이름을 바꾸거나 간단하게 나중에 namespace를 추가하는 것이 항상 가능하다.
다른 한편으로는, 전에 언급하였듯이 큰 규모의 프로젝트들은 name 충돌들에 더 민감하다.
그래서, 그것들은 체계적으로 namespaces를 사용할 필요가 있다.
수 백명의 프로그래머들 또는 그러한 개발 팀들이 함께 작업하는 프로젝트를 발견하는 것은 특이한 일이 아니다.
예를 들어 Microsoft Visual C++ 6.0의 개발은 18개월이 걸렸고 1000명 이상의 개발자들이 개발과정에 참여하였었다.
그러한 거대한 프로젝트를 관리하는 것은 잘 문서화된 코딩 정책들을 요구하며 그래서 namespace는 병기고에 있는 도구들 중의 하나이다.
6.3 대규모 프로젝트들에서의 namespace 활용정책
namespaces가 환경설정 관리(configuration management)에서 어떻게 사용될 수 있는지를 보기 위하여, 가상의 국제적인 신용카드 회사인 Unicard의 온라인 거래처리 시스템을 상상하라.
프로젝트는 몇 개의 개발 팀들을 포함한다.
그 팀들 중 하나인 데이터베이스 관리팀은 데이터베이스 테이블들, 인덱스들 그리고 액세스 권한들의 생성과 유지를 담당한다.
또한 데이터베이스 팀은 데이터베이스에 있는 데이터를 추출하고 다루는 액세스 루틴들 및 데이터 객체들을 제공해야만 한다.
두 번째 팀은 그래픽 사용자 인터페이스를 담당한다.
세 번째 팀은 여행자들이 그들의 국제적인 Unicard로 지불하는 극장들, 식당들, 가게들 등에 의해 시작되는 국제적인 온라인 요청들을 다룬다.
영화티켓의 모든 구매, 보석들, 또는 예술관련 서적은 카드 소유자가 지불하기 전에 Unicard에 의해 확인되어야만 한다.
확인과정은 카드의 정당한 사용여부, 카드의 사용만료일, 그리고 카드 소유자의 한도를 조사하는 것을 포함한다.
유사한 확인절차가 국내에서의 구입들을 위하여 요구된다.
그러나 국제적인 확인요청들은 인공위성을 통해 전송되는 반면에 국내에서의 확인들은 보통 전화선을 통해 이루어진다.
소프트웨어 프로젝트에서, 코드 재사용은 탁월하다.
동일한 사업논리가 국내 및 국제 확인들 모두를 위하여 사용되기 때문에, 동일한 데이터베이스 객체들이 관련정보를 추출하고 필요한 계산들을 수행하기 위하여 사용될 필요가 있다.
여전히, 국제적인 확인은 인공위성을 통해서 전송되는 요청들을 수신하고, 그것을 해독하고 그리고 송신자에게 암호화된 응답을 보내는 복잡한 통신스택을 포함한다.
인공위성-기반의 확인 애플리케이션의 전형적인 구현은 프로토콜들, 통신계층들, 권한관리, 메시지 큐잉, 암호화 및 해독을 캡슐화하는 필요한 통신 객체들과 데이터베이스 액세스 객체들을 조합하는 수단에 의해 성취될 수 있다.
통신 컴포넌트들과 데이터베이스 액세스 객체들의 동시적인 사용으로부터 발생하는 name 충돌들을 상상하는 것은 어렵지 않다.
예를 들어, 두 개의 객체들(하나는 데이터베이스 연결을 캡슐화하고 다른 하나는 위성접속에 관련된)은 동일한 이름을 가질 수 있다. (Connection)
그러나 만일 통신 소프트웨어 컴포넌트들과 데이터베이스 액세스 객체들이 두 개의 별개의 namespaces에서 선언된다면, name 충돌들의 가능성은 최소화된다.
그래서 com::Connection 및 dba::Connection은 동일한 애플리케이션에서 동시에 사용될 수 있다.
체계적인 접근방법은 모든 컴포넌트들이 선언되는 프로젝트에서 모든 팀을 위하여 다른 namespace를 할당하는 것에 기초될 수 있다.
그러한 정책은 다른 팀들 사이에서 그리고 프로젝트에서 사용되는 서드파티 코드에서의 name 충돌들을 피할 수 있도록 도움을 줄 수 있다.
6.4 Namespaces 와 버전제어
성공적인 소프트웨어 프로젝트들은 제품의 출시로 끝나지 않는다.
대부분의 프로젝트들에서, 이전 버전들에 기반을 두는 새로운 버전들이 정기적으로 릴리즈된다.
게다가, 이전의 버전들은 새로운 운영체제들, 지역들 그리고 하드웨어들과 동작하도록 지원되고 패치되고 조정되어야만 한다.
웹 브라우저들, 상업용 데이터베이스들, 워드 프로세서들 및 멀티미디어 툴들은 그러한 제품들의 예들이다.
종종 동일한 개발팀이 동일한 소프트웨어 제품의 몇 가지 버전들을 지원해야하는 경우가 존재한다.
소프트웨어의 상당한 양이 동일한 제품의 다른 버전들 사이에서 공유될 수 있지만, 그러나 각 버전은 또한 그 버전에 특정한 컴포넌트들을 가진다.
Namespace 별칭들은 하나의 버전으로부터 다른 버전으로 즉각적으로 전환하기 위하여 이러한 경우들에서 사용될 수 있다.
일반적으로 연속적인 프로젝트들은 여기저기에서 사용되는 하부구조 소프트웨어 컴포넌트들의 풀(pool)을 가진다.
게다가, 모든 버전은 특수화된 컴포넌트들의 고유한(private) 풀을 가진다.
Namespace 별칭들은 동적 namespaces를 제공할 수 있다.
즉, 하나의 namespace 별칭은 어떤 시간에 버전 X의 namespacd를 가리킬 수 있고, 또 다른 시간에는 다른 namespace를 의미할 수 있다.
예를 들면,
이 예에서, 별칭 current는 ver_3_11이나 ver_95를 참조할 수 있는 심볼이다.
다른 버전으로 전환하기 위해 프로그래머는 다른 namespace를 그것에 할당하기만 하면 된다.
6.4.1 Namespaces는 추가적인 오버헤드를 초래하지 않는다
Koenig lookup을 포함하는 namespace resolution은 정적으로 resolve된다.
namespaces의 기반이 되는 구현은, 컴파일러가 그것을 위한 고유한 이름을 생성하기 위해 함수명과 그것의 arguments 리스트, 그것의 클래스 이름, 그것의 namespace를 결합하는 name mangling을 사용하여 발생한다(name mangling에 대한 자세한 설명은 13장, "C 언어 호환성 문제들"을 보라).
그래서 namespace는 어떤 런타임 또는 메모리 오버헤드를 초래하지 않는다.
6.5 Namespaces와 다른 언어 특징들의 상호작용
Namespace는 언어의 다른 특징들과 상호작용하고 프로그래밍 기법들에 영향을 미친다.
Namespace는 C++에 있는 몇몇 특징들을 과잉으로 또는 바람직하지 않은 것으로 만든다.
6.5.1 범위분해 연산자(Scope Resolution Operator)는 전역적 이름들을 지명하기 위해 사용되어서는 안된다
몇몇 프레임워크들(예를 들어 MFC)에서, 그것을 클래스 멤버가 아닌 함수로서 명시적으로 표시하기 위하여 전역함수의 이름 앞에 범위분해 연산자 ::를 덧붙이는 것은 관습적이다(다음의 예에서처럼):
이러한 습관은 권고되지 않는다.
한때 전역이었던 표준함수들 중 많은 것이 이제는 namespaces안에서 그룹화된다.
예를 들어, strcpy는 이제 Standard Library 함수들 대부분이 그러는 것처럼 namespace std에 속한다.
범위분해 연산자를 갖는 이들 함수들에 선행하는 것은 컴파일러의 검색 알고리즘을 혼란스럽게 할 수 있다.
게다가 그렇게 하는 것은 전역 namespace를 분할한다는 바로 그 아이디어를 훼손한다.
그래서 범위분해 연산자와 함수의 이름을 분리하는 것이 권고된다.
6.5.2 외부 함수를 File-Local 함수로 전환하기
표준 C에서, static으로 선언되는 비지역 <st1:personname w:st="on">한정자</st1:personname>(nonlocal identifier)는 내부적인 연결을 가지는데, 그것은 그것이 선언된 번역단위(translation unit)(소스파일)내부로부터만 액세스 가능하다는 것을 의미한다(2장, "Standard Briefing: The Latest Addenda to ANSI/ISO C++"을 보라).
이 기법은 정보은닉을 지원하기 위해서 사용된다.(다음의 예에서처럼):
비록 그것이 여전히 C++에서도 지원되기는 하지만 이러한 관습은 반대되는 특징으로 간주된다.
당신의 컴파일러의 미래의 릴리즈들은 그것들이 클래스의 멤버가 아닌 static <st1:personname w:st="on">한정자</st1:personname>를 발견할 때 경고 메시지를 보여줄 것이다.
하나의 함수를 그것의 번역단위 내부로부터만 액세스할 수 있도록 만들기 위하여, 대신에 이름이 붙지 않은 namespace를 사용하라.
다음의 예는 그러한 처리를 보여준다.
비록 이름이 붙지 않은 namespace에 있는 이름들이 외부적인 연결을 가지고 있을지라도, 그것들은 어떤 다른 번역단위로부터 결코 보여 질 수 없다.
이것의 순수한 효과는 이름이 붙지 않은 namespace의 이름들이 static 연결을 가지는 것으로 나타난다는 것이다.
만일 당신이 다른 파일의 이름이 붙지 않은 namespace에서 동일한 이름을 갖는 다른 함수를 선언한다면, 두 함수들은 서로에 대해 숨겨지며, 그 결과 그들의 이름들은 충돌하지 않는다.
6.5.3 Standard Headers Names
모든 표준 C++ 헤더파일들은 이제 다음과 같이 포함되어야만 한다:
즉 .h 확장자가 생략된다.
표준 C 헤더파일들 또한 그들의 이름에 문자 c를 추가함으로써 이러한 규약에 복종한다.
그래서 전에는 <xxx.h>로 명명되었던 C 표준 헤더는 이제 <cxxx>가 된다.
예를 들면,
C 헤더들 <xxx.h>의 더 오래된 규약은 여전히 지원된다.
그러나 그것은 이제 반대된다고 간주되고 그래서 새로운 C++ 코드에서 사용되지 않는다.
이것을 위한 근거는 C <xxx.h> 헤더들이 그것들의 선언들을 전역 namespace속으로 주입한다는 것이다.
그러나 C++에서 대부분의 표준선언들은 <cxxx> 표준 C 헤더들처럼 namespace std 아래에서 그룹화된다.
어떤 추론도 헤더파일의 실제적인 위치에서 사용되는 실제 이름 규약 또는 그것이 기반하는 이름으로부터 얻어지지 않는다.
사실, 대부분의 구현들은 <xxx.h>를 위한 단일의 실제 파일 및 그것의 대응하는 <cxxx> 표기법을 공유한다.
이것은 약간의 숨겨진 전 처리기 트릭들 때문에 가능하다.
새로운 스타일의 표준 헤더들에 있는 선언들을 액세스하기 위해서는 using 선언, using 지시자 또는 완전히 한정된 이름을 가질 필요가 있다는 것을 기억하라.
예를 들어,
6.6 Namespace에 대한 제약들
C++ 표준은 namespaces의 사용에 대한 몇 가지 제한들을 정의한다.
이들 제한들은 언어에서 대혼란을 생성할 수 있는 변칙들 또는 모호함들을 피하기 위하여 의도되었다.
6.6.1 Namespace std는 수정될 수 없다
일반적으로 namespaces는 개방적이며 그래서 몇몇 파일들에 걸쳐 사용되는 선언들 및 정의들을 가지고 존재하는 namespaces를 확장하는 것은 완전히 합법적이다.
이 규칙에 대한 유일한 예외는 namespace std이다.
표준에 따르면, 추가적인 선언들로 namespace std를 수정하는 결과는 정의되지 않은 행위를 산출하며 그래서 그것은 피해야 한다.
이 제한은 자의적인 것처럼 보일 수 있지만 그것은 상식일 뿐이다 - namespace std를 함부로 변경하려는 어떤 시도도 namespace는 표준 선언들에 대해 배타적으로 전용된다는 바로 그 개념을 손상시킨다.
6.6.2 사용자 정의된 new 및 delete는 namespace에서 선언될 수 없다
표준은 namespace에서 new 및 delete 연산자들의 선언을 금지한다.
왜 그런지를 보기 위해 다음 예제를 고려하자:
어떤 프로그래머들은 연산자 A::delete가 선택될 것이라고 기대할 수 있는데 그 이유는 그것이 저장장치에 할당하기 위해 사용되었던 연산자 new와 어울리기 때문이다.
다른 사람들은 A::delete가 함수 f()에서 보이지 않기 때문에 표준 연산자인 delete가 호출될 것이라고 예측할 수 있다.
namespace에서 new와 delete 모두를 선언하는 것을 금지함으로써, C++은 어떤 그러한 모호함들을 회피한다.
6.7 결론들
Namespace는 C++ 표준에 가장 최근에 추가되었다.
그래서 어떤 컴파일러들은 아직 이 특징을 지원하지 않는다.
그러나 모든 컴파일러 벤더들은 조만간 namespace 지원을 포함시킬 것이다.
namespaces의 중요성은 과장될 수 없다.
당신이 보았듯이, 어떤 자명하지 않은 C++ 프로그램은 Standard Template Library, iostream 라이브러리 및 다른 표준 헤더 파일들의 컴포넌트들을 이용하는데 그것들 전부는 이제 namespace 멤버들이다.
대규모 소프트웨어 프로젝트들은 이미 보았듯이 일반적인 위험들을 피하고 버전제어를 용이하게 하기 위하여 namespaces를 영리하게 사용할 수 있다.
C++은 namespace 구성요소를 현재의 범위로 주입하기 위하여 세 가지 방법들을 제공한다.
첫 번째는 using 지시자를 사용하는 것인데 그것은 현재의 범위에서 볼 수 있는 namespace의 모든 멤버들을 나타낸다.
두 번째는 using 선언인데, 그것은 더 선택적이며 namespace로부터 단일 컴포넌트의 주입을 가능하게 만든다.
마지막으로 완전하게 한정된 이름은 namespace 멤버를 유일하게 식별한다.
게다가, argument-dependent lookup 또는 Koenig 검색은 프로그래머가 namespace에 대한 지루한 참조들을 강제하지 않고 프로그래머의 의도를 포착한다.
6.1 Namespaces 원리와 역사적 배경
6.1.1 Namespaces 이면에 존재하는 원리
Namespaces는 1995년에 C++ 표준으로 도입되었다.
우선 왜 namespaces가 언어에 추가되었는지를 이해하기 위해서, 여기에 하나의 비유가 있다.
당신의 컴퓨터에 있는 파일시스템이 디렉토리들과 서브디렉토리들을 전혀 가지지 않는다고 상상하자.
모든 파일들은 항상 모든 사용자와 애플리케이션에게 보여지는 단순한 저장소에 저장될 것이다.
결론적으로, 극단적인 어려움들이 발생할 것이다.
파일이름들이 충돌 할 것이고(몇몇 시스템들은 파일 이름을 8개의 문자로 제한하고 + 확장자들을 3개의 문자들로 제한하고서도 이것은 빈번히 발생할 것이다), 파일들의 목록화, 복사 또는 탐색은 훨씬 더 어려워질 것이다.
게다가, 보안 및 권한 제한들은 심각하게 손상될 것이다.
C++에서 namespace는 디렉토리들과 동일하다.
그것들은 쉽게 중첩(nest)될 수 있으며, 그들은 당신의 코드를 name conflicts로부터 보호하고, 당신이 선언들을 감출 수 있도록 하며, 어떤 실행시간 또는 메모리 오버헤드를 초래하지 않는다.
C++ Standard Library의 구성성분들 대부분은 namespace std하에 그룹화된다.
Namespace std는 STL의 오버로드된 연산자들의 정의들을 포함하고 있는 std::rel_ops와 같은 추가적인 namespace들로 분할된다.
6.1.2 간략한 역사적 배경
1990년대 초기에, C++이 범용 프로그래밍 언어로서 대중성을 얻었을 때 많은 벤더들이 다양한 컴포넌트 클래스들의 고유한 구현들을 선적하였다.
string 조작들, 수학적 함수들 그리고 데이터 컨테이너들을 위한 클래스 라이브러리들은 MFC, STL, OWL 및 다른 것들과 같이 구조의 통합된 부분들이었다.
재사용 가능한 컴포넌트들의 확산은 name 충돌문제의 원인이 되었다.
예를 들어 vector라 명명된 클래스는 둘 다 동시에 사용되었던 수학적 라이브러리와 또 다른 컨테이너 라이브러리에서 나타날 수 있다.
또는 string이라 명명된 클래스는 거의 모든 프레임워크 및 클래스 라이브러리에서 발견될 수 있다.
컴파일러가 동일한 이름들을 갖는 다른 클래스들을 구분하는 것은 불가능한 것이었다.
이와 유사하게, 링커들은 구분이 불가능한 이름들을 갖는 클래스들의 멤버 함수들의 동일한 이름들에 대처할 수 없었다.
예를 들어,
vector::operator==(const vector&);
멤버함수는 두 개의 다른 클래스들 속에서 정의될 수 있다 - 첫 번째 클래스는 수학 라이브러리의 클래스일 수 있고 다른 클래스는 어떤 컨테이너 라이브러리에 속할 수 있다.
그리고 대규모 프로젝트들은 name 충돌들에 더 취약하다, name 충돌들은 서드파티 소프트웨어 라이브러리들로 한정되지 않는다.
큰 규모의 소프트웨어 프로젝트들에서, 동일한 이름이 다른 개발자들에 의해 다른 엔티티를 가리키기 위해서 한번 이상 사용될 수도 있다.
pre-namespace 시대에, 유일한 문제해결 방법은 한정자의 이름들에 다양한 접두사들을 사용하는 것이었다.
그러나 이러한 행위는 지루하고 에러를 발생시키는 경향이 있었다.
6.2 Namespace의 특성들
Namespaces는 name 컨테이너들 그 이상이다.
그것들은 어떤 오버헤드를 가하지 않고서 오래된 코드의 빠르고 간결한 이동을 허용하기 위해 설계되었다.
Namespaces는 그들의 사용들을 용이하게 하는 몇 가지 특징들을 가진다.
다음 절은 이들 특성들을 논의한다.
6.2.1 완전하게 한정된 이름들(Fully qualified names)
하나의 namespace는 선언들과 정의들이 함께 그룹화되는 범위이다.
다른 범위로부터 이들 중 어떤 것을 참조하기 위해서는 완전하게 한정된 이름이 요구된다.
한정자의 완전히 한정된 이름은 그것의 namespaces, 그것에 이어지는 범위 분해 연산자(scope resolution operator)(::), 그것의 클래스 이름과 마지막으로 <st1:personname w:st="on"><st2:sn w:st="on">한</st2:sn><st2:givenname w:st="on">정자</st2:givenname></st1:personname>(identifier) 그 자신으로 이루어진다.
namespaces 및 클래스들 모두 중첩될 수 있기 때문에, 결과하는 이름은 다소 길어질 수 있지만 그것은 고유한 식별(identification)을 보장한다:
unsigned int maxPossibleLength = std::string::npos;
// 완전히 한정된 이름. npos는 string의 멤버이다.
// string은 namespace st에 속한다.
int *p = ::new int; // 오버로드된 new와 전역적 new를 구분함
그러나, 완전히 한정된 이름을 반복하는 것은 지루한 일이며 가독성이 낮다.
대신에, 당신은 using 선언(declaration) 또는 using 지시어(directive)를 사용할 수 있다.
6.2.2 using 선언 및 using 지시어(directive)
using 선언은 키워드 using, 그에 이어지는 namespace::member로 이루어진다.
그것은 마치 완전히 한정된 이름이 공급된 것처럼, 컴파일러가 특정한 namespace에서의 어떤 <st1:personname w:st="on">한정자</st1:personname>(타입, 연산자, 함수, 상수 및 등등)의 모든 발생의 위치를 정하도록 명령한다.
예를 들면,
#include <vector> // STL vector; namespace std에서 정의됨
int main()
{
using std::vector; //using 선언;vector의 모든 출현은 std에서 찾아진다.
vector <int> vi;
return 0;
}
다른 한편 using 지시어는 지시어의 범위에서 접근 가능한 지정된 namespace의 모든 이름들을 묘사한다.
그것은 다음의 순서로 구성된다.
using namespace와 그것에 이어서 namespace 이름.
예를 들면,
#include <vector> // namespace std에 속한다
#include <iostream> // iostream 클래스들 및 연산자들 또한 namespace std에 속한다
int main()
{
using namespace std; // a using 지시어, 모든 <iostream> 및 <vector>선언들이 이제 접근 가능하다.
vector <int> vi;
vi.push_back(10);
cout << vi[0];
return 0;
}
6.2.3 Namespaces는 확장될 수 있다
C++ 표준화 위원회는 관련있는 선언들이 몇몇 번역단위들(translation units)에 걸쳐서 확대될 수 있다는 사실을 잘 알고 있었다.
그 결과, 하나의 namespace가 부분적으로 정의될 수 있다.
예를 들어,
// file proj_const.h
namespace MyProj
{
enum NetProtocols
{
TCP_IP,
HTTP,
UDP
}; // enum NetProtocols
}
// file proj_classes.h
namespace MyProj // MyProj namespace를 확장
{
class RealTimeEncoder{ public: NetProtocols detect(); };
class NetworkLink {}; // global
class UserInterface {};
}
별개의 파일에서, 동일한 namespace가 추가적인 선언들을 가지고 확대될 수 있다.
완전한 namespace MyProj는 다음처럼 두 개의 파일들 모두로부터 추출될 수 있다.
// file app.cpp
#include "proj_const.h"
#include "proj_classes.h"
int main()
{
using namespace MyProj;
RealTimeEncoder encoder;
NetProtocols protocol = encoder.detect();
return 0;
}
6.2.4 Namespace 별칭들(aliases)
당신이 관찰할 때, namespace를 위하여 짧은 이름을 선택하는 것은 언젠가는 name 충돌로 이끌 것이다.
그러나, 매우 긴 이름들은 사용하기 쉽지 않다.
이러한 목적으로, namespace 별칭들이 사용될 수 있다.
다음의 예는 다루기 힘든 Excel_Software_Company namespace를 위하여 별칭 ESC를 정의한다.
Namespace 별칭들은 당신이 곧 보게 되듯이 다른 유용한 목적들을 가진다.
// file decl.h
namespace Excel_Software_Company
{
class Date {/*..*/};
class Time {/*..*/};
}
// file calendar.cpp
#include "decl.h"
int main()
{
//ESC는 Excel_Software_Company를 위한 별칭이다.
namespace ESC = Excel_Software_Company;
ESC::Date date;
ESC::Time time;
return 0;
}
6.2.5 쾨니흐 검색(Koenig Lookup)
C++의 창시자들 중 한사람인 Andrew Koenig는 namespace 멤버들의 검색(lookup)을 해결하기 위한 알고리즘을 고안하였다.
argument dependent lookup이라 불리기도 하는 이 알고리즘은 다음과 같은 경우들을 다루기 위하여 표준 호환의 모든 컴파일러들에서 사용된다.
namespace MINE
{
class C {};
void func;
}
MINE::C c; // 타입 MINE::C의 전역객체
int main()
{
func( c ); // OK, MINE::f이 호출된다.
return 0;
}
using 선언도 using 지시자도 프로그램에 존재하지 않는다.
그럼에도 컴파일러는 올바르게 작동한다 - Koenig 검색을 적용함으로써 그것은 한정되지 않은 name func를 namespace MINE에서 선언된 함수로서 정확하게 식별한다.
Koenig 검색은 컴파일러가 로컬 영역과 같은 보통의 장소들에서 뿐만 아니라 argument의 타입을 포함하는 namespace에서도 찾도록 명령한다.
그 결과, 다음 소스 라인에서, 컴파일러는 함수 func()의 argument인 객체 c가 namespace MINE에 속한다는 것을 알아낸다.
결론적으로 컴파일러는 프로그래머의 의도를 "추측하여" func()의 선언의 위치를 찾기 위하여 namespace MINE을 검색한다.
func( c ); // OK, MINE::f called
Koenig 검색 없는 namespaces는 완전하게 한정된 이름들을 반복적으로 지정하거나 using 선언들을 무수하게 사용한 프로그래머에게 받아들일 수 없는 지루함을 부과한다.
Koenig 검색을 그 이상으로 사용하여 argument를 push하기 위해 다음 예제를 고려하자.
#include<iostream>
using std::cout;
int main()
{
cout<<"hello"; // OK, 연산자 << 는 Koenig 검색에 의해 범위안으로 가져와진다
return 0;
}
using 선언은 main()의 범위속으로 std::cout을 삽입하고, 그것에 의해서 프로그래머가 비한정된 이름 cout을 사용할 수 있도록 한다.
그러나 당신이 기억할 수 있듯이 오버로드된 << 연산자는 std::cout의 멤버가 아니다.
그것은 namespace std에서 정의된 friend 함수이며, 그래서 그것은 그것의 argument로서 std::ostream 객체를 취한다.
Koenig 검색없이 프로그래머는 다음과 비슷한 무엇인가를 작성해야만 한다.
std::operator<<(cout, "hello");
대신에, 프로그래머는 using namespace std; 지시자를 제공할 수 있다.
그러나 이들 옵션들 중 그 어떤 것도 바람직하지 않은데, 그 이유는 그것들이 코드를 혼란스럽게 하고 혼동과 에러들의 원인이 될 수 있다.
(using 지시자들은 현재의 범위에서 볼 수 있는 이름들을 나타내기 위한 가장 적게 유리한 형태인데 그 이유는 그들이 namespace의 모든 멤버들을 무차별적으로 보여지도록 만들기 때문이다.)
다행하게도, Koenig 검색은 “올바르게 동작하며” 우아한 방법으로 이러한 지루함으로부터 당신을 구원한다.
Koenig 검색은 자동적으로 적용된다.
어떤 특수한 지시자들 또는 설정 스위치들이 그것을 활성화하기 위해 요구되지 않으며 그것을 끌 수 있는 어떤 방법도 존재하지 않는다.
이 사실을 염두에 두어야만 하는데 그 이유는 어떤 상황들에서 놀라운 결과들을 가져올 수 있기 때문이다.
예를 들면,
namespace NS1
{
class B{};
void f;
};
void f(NS1::B);
int main()
{
NS1::B b;
f; // 모호함 - NS1::f() 아니면 f(NS1::B) ?
return 0;
}
표준과 호환되는 컴파일러는 NS1::f(NS1::B) 및 f(NS1::B)사이의 모호함에 대해 에러를 제기해야 한다.
그러나 비호환 컴파일러들은 모호한 호출에 대해 불평하지 않는다; 그것들은 단순히 f()의 버전들 중 하나를 선택한다.
그러나 이것은 프로그래머가 의도하는 버전이 되지 않을 것이다.
게다가, 문제는 f()의 추가적인 버전들이 프로젝트에 더해질 때인 개발의 더 나중 단계에서만 발생할 것이다 - 그것은 컴파일러의 검색 알고리즘을 방해할 수 있다.
그것은 또한 두 가지의 namespaces가 서로 관련될 때 발생할 것이다 - 예를 들어, 만일 namespace가 다른 namespace에서 선언되는 클래스 멤버 함수의 파라미터들로서 사용되는 클래스들을 선언할 때.
6.2.6 실제에서의 Namespaces
이전의 예제들로부터 이끌어낼 수 있는 결론은 다른 언어 특징들과 마찬가지로 namespaces는 현명하게 사용되어야만 한다는 것이다.
약간의 클래스들과 약간의 소스 파일들만을 포함하는 작은 프로그램들을 위하여, namespace는 불필요하다.
대부분의 경우들에서, 그러한 프로그램들은 한 명의 프로그래머에 의해서 코딩되고 유지되며, 그 결과 그들은 제한된 수의 컴포넌트들을 사용한다.
이러한 경우에 name 충돌들의 가능성은 다소 작다.
만일 name 충돌들이 여전히 발생한다면, 존재한 클래스들 및 함수들의 이름을 바꾸거나 간단하게 나중에 namespace를 추가하는 것이 항상 가능하다.
다른 한편으로는, 전에 언급하였듯이 큰 규모의 프로젝트들은 name 충돌들에 더 민감하다.
그래서, 그것들은 체계적으로 namespaces를 사용할 필요가 있다.
수 백명의 프로그래머들 또는 그러한 개발 팀들이 함께 작업하는 프로젝트를 발견하는 것은 특이한 일이 아니다.
예를 들어 Microsoft Visual C++ 6.0의 개발은 18개월이 걸렸고 1000명 이상의 개발자들이 개발과정에 참여하였었다.
그러한 거대한 프로젝트를 관리하는 것은 잘 문서화된 코딩 정책들을 요구하며 그래서 namespace는 병기고에 있는 도구들 중의 하나이다.
6.3 대규모 프로젝트들에서의 namespace 활용정책
namespaces가 환경설정 관리(configuration management)에서 어떻게 사용될 수 있는지를 보기 위하여, 가상의 국제적인 신용카드 회사인 Unicard의 온라인 거래처리 시스템을 상상하라.
프로젝트는 몇 개의 개발 팀들을 포함한다.
그 팀들 중 하나인 데이터베이스 관리팀은 데이터베이스 테이블들, 인덱스들 그리고 액세스 권한들의 생성과 유지를 담당한다.
또한 데이터베이스 팀은 데이터베이스에 있는 데이터를 추출하고 다루는 액세스 루틴들 및 데이터 객체들을 제공해야만 한다.
두 번째 팀은 그래픽 사용자 인터페이스를 담당한다.
세 번째 팀은 여행자들이 그들의 국제적인 Unicard로 지불하는 극장들, 식당들, 가게들 등에 의해 시작되는 국제적인 온라인 요청들을 다룬다.
영화티켓의 모든 구매, 보석들, 또는 예술관련 서적은 카드 소유자가 지불하기 전에 Unicard에 의해 확인되어야만 한다.
확인과정은 카드의 정당한 사용여부, 카드의 사용만료일, 그리고 카드 소유자의 한도를 조사하는 것을 포함한다.
유사한 확인절차가 국내에서의 구입들을 위하여 요구된다.
그러나 국제적인 확인요청들은 인공위성을 통해 전송되는 반면에 국내에서의 확인들은 보통 전화선을 통해 이루어진다.
소프트웨어 프로젝트에서, 코드 재사용은 탁월하다.
동일한 사업논리가 국내 및 국제 확인들 모두를 위하여 사용되기 때문에, 동일한 데이터베이스 객체들이 관련정보를 추출하고 필요한 계산들을 수행하기 위하여 사용될 필요가 있다.
여전히, 국제적인 확인은 인공위성을 통해서 전송되는 요청들을 수신하고, 그것을 해독하고 그리고 송신자에게 암호화된 응답을 보내는 복잡한 통신스택을 포함한다.
인공위성-기반의 확인 애플리케이션의 전형적인 구현은 프로토콜들, 통신계층들, 권한관리, 메시지 큐잉, 암호화 및 해독을 캡슐화하는 필요한 통신 객체들과 데이터베이스 액세스 객체들을 조합하는 수단에 의해 성취될 수 있다.
통신 컴포넌트들과 데이터베이스 액세스 객체들의 동시적인 사용으로부터 발생하는 name 충돌들을 상상하는 것은 어렵지 않다.
예를 들어, 두 개의 객체들(하나는 데이터베이스 연결을 캡슐화하고 다른 하나는 위성접속에 관련된)은 동일한 이름을 가질 수 있다. (Connection)
그러나 만일 통신 소프트웨어 컴포넌트들과 데이터베이스 액세스 객체들이 두 개의 별개의 namespaces에서 선언된다면, name 충돌들의 가능성은 최소화된다.
그래서 com::Connection 및 dba::Connection은 동일한 애플리케이션에서 동시에 사용될 수 있다.
체계적인 접근방법은 모든 컴포넌트들이 선언되는 프로젝트에서 모든 팀을 위하여 다른 namespace를 할당하는 것에 기초될 수 있다.
그러한 정책은 다른 팀들 사이에서 그리고 프로젝트에서 사용되는 서드파티 코드에서의 name 충돌들을 피할 수 있도록 도움을 줄 수 있다.
성공적인 소프트웨어 프로젝트들은 제품의 출시로 끝나지 않는다.
대부분의 프로젝트들에서, 이전 버전들에 기반을 두는 새로운 버전들이 정기적으로 릴리즈된다.
게다가, 이전의 버전들은 새로운 운영체제들, 지역들 그리고 하드웨어들과 동작하도록 지원되고 패치되고 조정되어야만 한다.
웹 브라우저들, 상업용 데이터베이스들, 워드 프로세서들 및 멀티미디어 툴들은 그러한 제품들의 예들이다.
종종 동일한 개발팀이 동일한 소프트웨어 제품의 몇 가지 버전들을 지원해야하는 경우가 존재한다.
소프트웨어의 상당한 양이 동일한 제품의 다른 버전들 사이에서 공유될 수 있지만, 그러나 각 버전은 또한 그 버전에 특정한 컴포넌트들을 가진다.
Namespace 별칭들은 하나의 버전으로부터 다른 버전으로 즉각적으로 전환하기 위하여 이러한 경우들에서 사용될 수 있다.
일반적으로 연속적인 프로젝트들은 여기저기에서 사용되는 하부구조 소프트웨어 컴포넌트들의 풀(pool)을 가진다.
게다가, 모든 버전은 특수화된 컴포넌트들의 고유한(private) 풀을 가진다.
Namespace 별칭들은 동적 namespaces를 제공할 수 있다.
즉, 하나의 namespace 별칭은 어떤 시간에 버전 X의 namespacd를 가리킬 수 있고, 또 다른 시간에는 다른 namespace를 의미할 수 있다.
예를 들면,
namespace ver_3_11 // 16 bit
{
class Winsock {/*..*/};
class FileSystem {/*..*/};
}
namespace ver_95 // 32 bit
{
class Winsock {/*..*/};
class FileSystem {/*..*/};
}
int main() // implementing 16 bit release
{
namespace current = ver_3_11;// current는 ver_3_11의 별칭
using current::Winsock;
using current::FileSystem;
FileSystem fs; // ver_3_11::FileSystem
//...
return 0;
}
이 예에서, 별칭 current는 ver_3_11이나 ver_95를 참조할 수 있는 심볼이다.
다른 버전으로 전환하기 위해 프로그래머는 다른 namespace를 그것에 할당하기만 하면 된다.
6.4.1 Namespaces는 추가적인 오버헤드를 초래하지 않는다
Koenig lookup을 포함하는 namespace resolution은 정적으로 resolve된다.
namespaces의 기반이 되는 구현은, 컴파일러가 그것을 위한 고유한 이름을 생성하기 위해 함수명과 그것의 arguments 리스트, 그것의 클래스 이름, 그것의 namespace를 결합하는 name mangling을 사용하여 발생한다(name mangling에 대한 자세한 설명은 13장, "C 언어 호환성 문제들"을 보라).
그래서 namespace는 어떤 런타임 또는 메모리 오버헤드를 초래하지 않는다.
6.5 Namespaces와 다른 언어 특징들의 상호작용
Namespace는 언어의 다른 특징들과 상호작용하고 프로그래밍 기법들에 영향을 미친다.
Namespace는 C++에 있는 몇몇 특징들을 과잉으로 또는 바람직하지 않은 것으로 만든다.
6.5.1 범위분해 연산자(Scope Resolution Operator)는 전역적 이름들을 지명하기 위해 사용되어서는 안된다
몇몇 프레임워크들(예를 들어 MFC)에서, 그것을 클래스 멤버가 아닌 함수로서 명시적으로 표시하기 위하여 전역함수의 이름 앞에 범위분해 연산자 ::를 덧붙이는 것은 관습적이다(다음의 예에서처럼):
void String::operator = (const String& other)
{
::strcpy (this->buffer, other.getBuff());
}
이러한 습관은 권고되지 않는다.
한때 전역이었던 표준함수들 중 많은 것이 이제는 namespaces안에서 그룹화된다.
예를 들어, strcpy는 이제 Standard Library 함수들 대부분이 그러는 것처럼 namespace std에 속한다.
범위분해 연산자를 갖는 이들 함수들에 선행하는 것은 컴파일러의 검색 알고리즘을 혼란스럽게 할 수 있다.
게다가 그렇게 하는 것은 전역 namespace를 분할한다는 바로 그 아이디어를 훼손한다.
그래서 범위분해 연산자와 함수의 이름을 분리하는 것이 권고된다.
6.5.2 외부 함수를 File-Local 함수로 전환하기
표준 C에서, static으로 선언되는 비지역 <st1:personname w:st="on">한정자</st1:personname>(nonlocal identifier)는 내부적인 연결을 가지는데, 그것은 그것이 선언된 번역단위(translation unit)(소스파일)내부로부터만 액세스 가능하다는 것을 의미한다(2장, "Standard Briefing: The Latest Addenda to ANSI/ISO C++"을 보라).
이 기법은 정보은닉을 지원하기 위해서 사용된다.(다음의 예에서처럼):
// File hidden.c
static void decipher(FILE *f);
// accessible only from within this file
// now use this function in the current source file
decipher ("passwords.bin");
//end of file
비록 그것이 여전히 C++에서도 지원되기는 하지만 이러한 관습은 반대되는 특징으로 간주된다.
당신의 컴파일러의 미래의 릴리즈들은 그것들이 클래스의 멤버가 아닌 static <st1:personname w:st="on">한정자</st1:personname>를 발견할 때 경고 메시지를 보여줄 것이다.
하나의 함수를 그것의 번역단위 내부로부터만 액세스할 수 있도록 만들기 위하여, 대신에 이름이 붙지 않은 namespace를 사용하라.
다음의 예는 그러한 처리를 보여준다.
// File hidden.cpp
namespace // unnamed
{
void decipher(FILE *f); // 이 파일 내부로 부터만 액세스가능
}
// 이제 함수를 현재 소스파일로부터 사용.
// 어떤 using 선언들 또는 지시자들도 요구되지 않음
decipher ("passwords.bin");
비록 이름이 붙지 않은 namespace에 있는 이름들이 외부적인 연결을 가지고 있을지라도, 그것들은 어떤 다른 번역단위로부터 결코 보여 질 수 없다.
이것의 순수한 효과는 이름이 붙지 않은 namespace의 이름들이 static 연결을 가지는 것으로 나타난다는 것이다.
만일 당신이 다른 파일의 이름이 붙지 않은 namespace에서 동일한 이름을 갖는 다른 함수를 선언한다면, 두 함수들은 서로에 대해 숨겨지며, 그 결과 그들의 이름들은 충돌하지 않는다.
6.5.3 Standard Headers Names
모든 표준 C++ 헤더파일들은 이제 다음과 같이 포함되어야만 한다:
#include <iostream> //note: ".h" 확장자가 없다.
즉 .h 확장자가 생략된다.
표준 C 헤더파일들 또한 그들의 이름에 문자 c를 추가함으로써 이러한 규약에 복종한다.
그래서 전에는 <xxx.h>로 명명되었던 C 표준 헤더는 이제 <cxxx>가 된다.
예를 들면,
#include <cassert> // 전에는<assert.h>.접두사 'c'와 ".h"의 생략에 주목하라
C 헤더들 <xxx.h>의 더 오래된 규약은 여전히 지원된다.
그러나 그것은 이제 반대된다고 간주되고 그래서 새로운 C++ 코드에서 사용되지 않는다.
이것을 위한 근거는 C <xxx.h> 헤더들이 그것들의 선언들을 전역 namespace속으로 주입한다는 것이다.
그러나 C++에서 대부분의 표준선언들은 <cxxx> 표준 C 헤더들처럼 namespace std 아래에서 그룹화된다.
어떤 추론도 헤더파일의 실제적인 위치에서 사용되는 실제 이름 규약 또는 그것이 기반하는 이름으로부터 얻어지지 않는다.
사실, 대부분의 구현들은 <xxx.h>를 위한 단일의 실제 파일 및 그것의 대응하는 <cxxx> 표기법을 공유한다.
이것은 약간의 숨겨진 전 처리기 트릭들 때문에 가능하다.
새로운 스타일의 표준 헤더들에 있는 선언들을 액세스하기 위해서는 using 선언, using 지시자 또는 완전히 한정된 이름을 가질 필요가 있다는 것을 기억하라.
예를 들어,
#include <cstdio>
using namespace std;
void f()
{
printf ("Hello World\n");
}
6.6 Namespace에 대한 제약들
C++ 표준은 namespaces의 사용에 대한 몇 가지 제한들을 정의한다.
이들 제한들은 언어에서 대혼란을 생성할 수 있는 변칙들 또는 모호함들을 피하기 위하여 의도되었다.
6.6.1 Namespace std는 수정될 수 없다
일반적으로 namespaces는 개방적이며 그래서 몇몇 파일들에 걸쳐 사용되는 선언들 및 정의들을 가지고 존재하는 namespaces를 확장하는 것은 완전히 합법적이다.
이 규칙에 대한 유일한 예외는 namespace std이다.
표준에 따르면, 추가적인 선언들로 namespace std를 수정하는 결과는 정의되지 않은 행위를 산출하며 그래서 그것은 피해야 한다.
이 제한은 자의적인 것처럼 보일 수 있지만 그것은 상식일 뿐이다 - namespace std를 함부로 변경하려는 어떤 시도도 namespace는 표준 선언들에 대해 배타적으로 전용된다는 바로 그 개념을 손상시킨다.
6.6.2 사용자 정의된 new 및 delete는 namespace에서 선언될 수 없다
표준은 namespace에서 new 및 delete 연산자들의 선언을 금지한다.
왜 그런지를 보기 위해 다음 예제를 고려하자:
char *pc; // global
namespace A
{
void* operator new ( std::size_t );
void operator delete ( void * );
void func ()
{
pc = new char ('a'); // A::new를 사용
}
} //A
void f() { delete pc; } // A::delete 아니면 ::delete를 호출?
어떤 프로그래머들은 연산자 A::delete가 선택될 것이라고 기대할 수 있는데 그 이유는 그것이 저장장치에 할당하기 위해 사용되었던 연산자 new와 어울리기 때문이다.
다른 사람들은 A::delete가 함수 f()에서 보이지 않기 때문에 표준 연산자인 delete가 호출될 것이라고 예측할 수 있다.
namespace에서 new와 delete 모두를 선언하는 것을 금지함으로써, C++은 어떤 그러한 모호함들을 회피한다.
6.7 결론들
Namespace는 C++ 표준에 가장 최근에 추가되었다.
그래서 어떤 컴파일러들은 아직 이 특징을 지원하지 않는다.
그러나 모든 컴파일러 벤더들은 조만간 namespace 지원을 포함시킬 것이다.
namespaces의 중요성은 과장될 수 없다.
당신이 보았듯이, 어떤 자명하지 않은 C++ 프로그램은 Standard Template Library, iostream 라이브러리 및 다른 표준 헤더 파일들의 컴포넌트들을 이용하는데 그것들 전부는 이제 namespace 멤버들이다.
대규모 소프트웨어 프로젝트들은 이미 보았듯이 일반적인 위험들을 피하고 버전제어를 용이하게 하기 위하여 namespaces를 영리하게 사용할 수 있다.
C++은 namespace 구성요소를 현재의 범위로 주입하기 위하여 세 가지 방법들을 제공한다.
첫 번째는 using 지시자를 사용하는 것인데 그것은 현재의 범위에서 볼 수 있는 namespace의 모든 멤버들을 나타낸다.
두 번째는 using 선언인데, 그것은 더 선택적이며 namespace로부터 단일 컴포넌트의 주입을 가능하게 만든다.
마지막으로 완전하게 한정된 이름은 namespace 멤버를 유일하게 식별한다.
게다가, argument-dependent lookup 또는 Koenig 검색은 프로그래머가 namespace에 대한 지루한 참조들을 강제하지 않고 프로그래머의 의도를 포착한다.
댓글
댓글 쓰기