cos
, sin
, tan
등등의 함수는 각도로 인자를 받도록 되어 있다. 그런데 함수 이름 cos
는 이미 사용 중이고 호도를 인자로 받는다.
이런 문제는 또다른 이름을 정의하면 해결된다. 예를 들어 함수 이름을 cosDegrees
로 정의하면 된다. 그러나 C++는 이름공간을 통하여 또다른 해결책을 제시한다. 이름공간은 식별자가 정의되어 있는 코드 영역으로 간주할 수 있다. 이름공간에 정의된 식별자들은 이미 다른 곳에 (이름공간 밖에) 정의된 이름과 충돌하지 않는다. 그래서 (각도를 자신의 인자로 기대하는) cos
함수를 Degrees
이름공간에 정의할 수 있다. cos
를 Degrees
안에서 호출하면 각도를 기대하는 cos
함수를 호출할 수 있다. 호도를 기대하는 표준 cos
함수가 호출되지 않는다.
ANSI/ISO 표준은 최신 컴파일러에서 상당한 수준까지 구현되었으므로 이제 이름공간이 더 엄격하게 사용된다. 이 때문에 class
헤더를 설정하는 효과가 있다. 지금은 자세하게 다루지 못하지만 7.11.1항에 이름공간으로부터 객체들을 사용하는 헤더 파일을 구성하는 법을 다룬다.
이름공간은 클래스에 없는 부가 기능이 있다.
using namespace
를 사용해 이름공간을 가져올 수 있다.namespace p = FS;
별칭을 이용할 수 있다.namespace identifier { // 선언된 또는 정의된 개체 // (선언 구역) }이름공간을 정의할 때 사용된 식별자는 표준 C++ 식별자이다.
위의 예제 코드에 보여주듯이 선언 구역 안에 함수나 변수 또 구조체나 클래스 그리고 심지어 (내포) 이름공간도 정의하거나 선언할 수 있다. 이름공간은 함수 몸체 안에 정의할 수 없다. 그렇지만 여러 namespace 선언을 사용하여 이름공간을 정의하는 것은 가능하다. 이름공간은 `열려 있다'. 그 의미는 CppAnnotations
이름공간을 파일 file1.cc
와 file2.cc
에 정의할 수 있다는 뜻이다. file1.cc
파일과 file2.cc
파일의 CppAnnotations
이름공간 안에 정의된 개체들은 하나의 CppAnnotations
이름공간 안에 통합된다. 예를 들어:
// in file1.cc namespace CppAnnotations { double cos(double argInDegrees) { ... } } // in file2.cc namespace CppAnnotations { double sin(double argInDegrees) { ... } }이제
sin
함수와 cos
함수 모두 같은 CppAnnotations
이름공간에 정의된다.
이름공간 개체는 이름공간 밖에 정의할 수 있다. 이 주제는 4.1.4.1목에 다룬다.
namespace CppAnnotations { double cos(double degrees); double sin(double degrees); }
익명 이름공간에 정의된 객체들은 비교하자면 C의 static
함수나 변수와 같다. C++에서도 여전히 static
키워드를 사용할 수 있지만 주 사용법은 class
정의에 있다 (7장). C에서 정적 변수나 함수가 사용되어야 하는 상황에 C++에서는 익명 이름공간을 사용해야 한다.
익명 이름공간은 닫힌 이름공간이다. 즉, 익명 이름공간에 개체를 추가하더라도 다른 소스 파일의 익명 공간에 보이지 않는다.
CppAnnotations
이름공간에 정의된 cos()
함수는 다음과 같이 사용할 수 있다.
// CppAnnotations 이름공간이 다음 헤더 파일에 // 정의되어 있다고 간주: #include <cppannotations> int main() { cout << "The cosine of 60 degrees is: " << CppAnnotations::cos(60) << '\n'; }
CppAnnotations
이름공간에 있는 cos()
함수를 참조하기에 이 방법은 약간 귀찮다. 특히 함수를 자주 사용해야 할 경우는 더 그렇다. 그러면 선언한 다음에 간략 형태를 사용하면 된다.
using CppAnnotations::cos; // 주의: 함수 원형 없음, // 그냥 개체 이름만 // 있으면 됨.
cos
를 호출하면 CppAnnotations
이름공간에 정의된 cos
함수가 호출된다. 이것은 호도를 받는 표준 cos
함수가 더 이상 자동으로 호출되지 않는다는 뜻이다. 표준 cos
함수를 호출하려면 평범하게 영역 지정 연산자를 사용해야 한다.
int main() { using CppAnnotations::cos; ... cout << cos(60) // CppAnnotations::cos()를 호출 << ::cos(1.5) // 표준 cos() 함수를 호출 << '\n'; }
using
선언은 영역을 제한한다. 블록 안에 사용할 수 있다. using
선언은 using
선언에 사용된 것과 이름이 같은 개체가 정의되는 것을 방지한다. 어떤 이름공간에 있는 value
변수에 대하여 using
선언을 지정하는 것은 불가능하다. using
선언이 포함된 블록 안에 이름이 동일한 객체를 정의하거나 선언하는 것은 불가능하다. 예를 들어:
int main() { using CppAnnotations::value; ... cout << value << '\n'; // CppAnnotations::value 사용 int value; // 에러: 이미 선언된 값. }
using
선언에 대한 일반적인 지시어는 using 지시어이다.
using namespace CppAnnotations;이 지시어 다음부터
CppAnnotations
이름공간 안에 정의된 모든 개체들은 마치 using
선언이 된 것처럼 사용된다.
(그 이름공간이 미리 선언되어 있거나 정의되어 있다는 가정하에) using
지시어가 한 이름공간 안에 있는 모든 이름을 반입하는 빠른 방법이기는 하지만 약간 지저분한 방법이기도 하다. 특정 코드 블록 안에 실제로 어떤 개체가 사용중인지 좀 명확하지 않기 때문이다.
예를 들어 만약 cos
함수가 CppAnnotations
이름공간에 정의되어 있다면 cos
함수가 호출될 때 CppAnnotations::cos
가 사용될 것이다. 그러나 cos
가 CppAnnotations
이름공간 안에 정의되어 있지 않다면 표준 cos
함수가 사용될 것이다. 어떤 개체가 실제로 사용될 지에 관하여 using
지시어는 using
선언만큼 명확하게 문서화가 되어 있지 않다. 그러므로 using
지시어를 사용할 때 조심하라.
`쾨닉 검색'이 가리키는 것은 다음과 같다. 이름공간을 지정하지 않고 함수를 호출하면 인자 유형의 이름공간을 따라 함수의 이름공간을 결정한다. 인자 유형이 정의된 이름공간에 그런 함수가 있다면 그 함수가 사용된다. 이런 절차를 `쾨닉 검색(Koenig lookup)'이라고 부른다.
예를 들어 다음 예제를 생각해 보자. FBB::fun(FBB::Value v)
함수가 FBB
이름공간에 정의되어 있다. 명시적으로 이름공간을 언급하지 않아도 호출할 수 있다.
#include <iostream> namespace FBB { enum Value // FBB::Value 정의 { FIRST }; void fun(Value x) { std::cout << "fun called for " << x << '\n'; } } int main() { fun(FBB::FIRST); // 쾨닉 검색: 이름공간이 // fun()에 지정되어 있지 않으므로 } /* 출력: fun called for 0 */
컴파일러는 이름공간을 똑똑하게 처리한다. namespace FBB
안의 Value
가 typedef int Value
로 정의되어 있다면 FBB::Value
는 int
로 인지될 것이고 그리하여 쾨닉 찾기는 실패한다.
또다른 예제로 다음 프로그램을 생각해 보자. 두 개의 이름공간이 관련되어 있다. 이름공간마다 자신만의 fun
함수가 정의되어 있다. 애매모호한 것은 전혀 없다. 인자가 이름공간을 정의하고 있기 때문에 FBB::fun
멤버 함수가 호출된다.
#include <iostream> namespace FBB { enum Value // FBB::Value를 정의한다. { FIRST }; void fun(Value x) { std::cout << "FBB::fun() called for " << x << '\n'; } } namespace ES { void fun(FBB::Value x) { std::cout << "ES::fun() called for " << x << '\n'; } } int main() { fun(FBB::FIRST); // 애매 모호함 없음: 인자가 // 이름공간을 결정한다. } /* 출력: FBB::fun() called for 0 */
다음은 모호성이 있는 예제이다. fun
함수는 인자가 두 개이다. 각자의 이름공간으로부터 온다. 이 모호성은 프로그래머가 해결해야 한다.
#include <iostream> namespace ES { enum Value // ES::Value를 정의 { FIRST }; } namespace FBB { enum Value // FBB::Value를 정의 { FIRST }; void fun(Value x, ES::Value y) { std::cout << "FBB::fun() called\n"; } } namespace ES { void fun(FBB::Value x, Value y) { std::cout << "ES::fun() called\n"; } } int main() { // fun(FBB::FIRST, ES::FIRST); 모호함: 명시적으로 // 이름공간을 지정해 // 해결해야 함 ES::fun(FBB::FIRST, ES::FIRST); } /* 출력: ES::fun() called */
이름공간의 흥미로운 점은 한 이름공간에 정의된 것은 다른 이름공간에 정의된 코드를 깰 수도 있다는 것이다. 이름공간은 서로 영향을 미칠 수 있으며 그 기묘함을 인지하지 못하면 서로의 등에 총을 겨눌 수 있다는 사실을 보여준다. 다음 예제를 살펴보자:
namespace FBB { struct Value {}; void fun(int x); void gun(Value x); } namespace ES { void fun(int x) { fun(x); } void gun(FBB::Value x) { gun(x); } }
무슨 일이 일어나든 프로그래머는 ES::fun
함수에서 아무 것도 하지 않는 편이 좋다. 무한 재귀를 초래하기 때문이다. 그렇지만 그것이 요점은 아니다. 진짜 문제는 프로그래머에게 ES::fun
함수를 호출할 기회조차 없다는 것이다. 컴파일에 실패하기 때문이다.
gun
에는 컴파일이 실패하지만 fun
은 성공한다. 그러나 왜 그런가? 왜 ES::fun
은 문제없이 컴파일에 성공하는 반면에 ES::gun
은 실패하는가? ES::fun
에서 fun(x)
이 호출된다. x
의 유형이 이름공간 안에 정의되어 있지 않으므로 쾨닉 검색이 적용되지 않는다. 그래서 fun
은 자기 자신을 무한정 호출한다.
ES::gun
멤버는 인자가 FBB
이름공간에 정의되어 있다. 결과적으로 FBB::gun
함수는 호출 가능한 대상이다. 그러나 ES::gun
자체도 역시 호출 가능하다. ES::gun
의 원형이 정확하게 gun(x)
호출에 부합하기 때문이다.
이제 FBB::gun
이 아직 선언되어 있지 않은 상황을 생각해보자. 그러면 물론 모호함은 없다. ES
이름공간에 책임을 진 프로그래머는 흐뭇하게 바라본다. FBB
이름공간을 관리하는 프로그래머가 gun(Value x)
을 FBB
이름공간에 배치하면 좋겠다는 결정을 내리자마자 갑자기 ES
공간에 있는 코드가 깨지기 시작한다. 완전히 다른 이름공간(FBB
)에 뭔가 추가되었다는 이유만으로 말이다. 이름공간은 서로 완벽하게 독립적인 것은 아니라는 사실은 명확하다. 그러므로 위와 같은 경우를 세심하게 살펴야 한다. 나중에 이 문제에 관해 더 자세히 다루어 보겠다 (제 11장).
쾨닉 검색은 이름공간의 문맥에서만 사용된다. 함수가 이름공간 밖에 정의되어 있는데, 매개변수는 이름공간 안에 정의된 유형이고, 그 이름공간이 동일한 서명의 함수를 정의하고 있다면, 그 함수를 호출할 때 컴파일러는 모호하다고 보고한다. 다음은 예제이다. 위에 언급된 FBB
이름공간을 사용할 수 있다고 간주한다:
void gun(FBB::Value x); int main(int argc, char **argv) { gun(FBB::Value{}); // 모호함: FBB::gun 그리고 ::gun 모두 // 호출이 가능하다. }
std
이름공간은 C++에 예약되어 있다. 표준 이름공간에 실행시간 소프트웨어에 사용가능한 개체들이 많이 정의되어 있다 (예를 들어, cout, cin, cerr
); 표준 템플릿 라이브러리에 정의된 템플릿 (제 18장) 그리고 총칭 알고리즘 (제 19장) 등등이 std
이름공간에 정의되어 있다.
이전 절의 연구에 의하면 std
이름공간에 있는 개체들을 가리킬 때 using
선언을 사용할 수 있다. 예를 들어 std::cout
스트림을 사용하려면 코드는 이 객체를 다음과 같이 선언하면 된다.
#include <iostream> using std::cout;그렇지만 가끔
std
이름공간에 정의된 식별자를 별 생각없이 모조리 받아 들일 수도 있다. 프로그래머가 using
지시어를 사용하는 것을 자주 보는데 이름공간을 생략해도 그 이름공간에 정의된 모든 개체들을 가리킬 수 있기 때문이다. using
선언을 지정하는 대신에 다음 using
지시어도 다음과 같은 구조로 자주 볼 수 있다.
#include <iostream> using namespace std;
using
선언 대신에 using
지시어를 사용해야 할까? 지켜야 할 제일 규칙은 using
선언을 사용하기로 결정할 수 있다는 것이다. 리스트가 터무니없이 길 경우까지 그렇다. 이 시점을 넘어서면 using
지시어를 고려할 수 있다.
using
지시어와 선언에는 두 가지 제한이 적용된다.
using
선언과 지시어는 제 삼자가 작성한 코드에 부과하면 안된다. 이것은 using
지시어와 선언을 헤더 파일에 금지하고 소스 파일에만 사용해야 한다는 뜻이다 (7.11.1항).
std::placeholders
이름공간을 사용하기 전에 먼저 <functional>
헤더를 포함해야 한다.
이 책을 더 읽어 가다 보면 함수객체를 만날 것이다 (11.10절). 함수객체란 함수처럼 사용할 수 있는 `객체'이다. (펑크터(functors)라고도 부르는) 함수객체는 표준 템플릿 라이브러리에서 광범위하게 사용된다 (제 18장 STL 참고). STL이 제공하는 함수 중에는 함수 어댑터를 돌려주는 (bind
라는) 함수가 있다(18.1.4.1목). 그 안에서 함수가 호출되는데 이 함수는 인자를 이미 받았을 수도 안 받았을 수도 있다. 인자가 지정되어 있지 않다면 위치보유자를 사용해야 한다. bind
가 돌려준 해당 함수객체를 호출할 때 거기에다 실제 인자를 지정해야 한다.
그런 위치보유자는 이미 _1, _2, _3,
등등으로 이름으로 std::placeholders
이름공간에 정의되어 있다. 그의 다양한 사용법은 18.1.4.1목에서 볼 수 있다.
namespace CppAnnotations { int value; namespace Virtual { void *pointer; } }
value
변수는 CppAnnotations
이름공간 안에 정의된다. CppAnnotations
이름공간 안에 또다른 이름공간이 내포된다 (Virtual
). 두 번째 이름공간 안에 pointer
변수가 정의된다. 이 변수를 참조하려면 다음 방법을 사용할 수 있다.
int main() { CppAnnotations::value = 0; CppAnnotations::Virtual::pointer = 0; }
using namespace CppAnnotations
지시어를 줄 수 있다. 이제 이름공간 없이 value
에 접근할 수 있다. 그러나 pointer
는 앞에 Virtual::
이름공간을 붙여야 한다.
using namespace CppAnnotations; int main() { value = 0; Virtual::pointer = 0; }
using namespace
지시어를 사용할 수 있다. 이제 value
는 다시 또 CppAnnotations
이름공간이 필요하다. 그러나 pointer
는 더 이상 이름공간이 필요없다.
using namespace CppAnnotations::Virtual; int main() { CppAnnotations::value = 0; pointer = 0; }
using namespace
지시어 두 개를 사용하면 이름공간이 더 이상 필요없다.
using namespace CppAnnotations; using namespace Virtual; int main() { value = 0; pointer = 0; }
using
선언을 특정하면 변수에 이름공간이 필요없다.
using CppAnnotations::value; using CppAnnotations::Virtual::pointer; int main() { value = 0; pointer = 0; }
using namespace
지시어와 using
선언을 섞어 쓰는 것도 가능하다. 예를 들어 using namespace
지시어는 CppAnnotations::Virtual
이름공간에 사용할 수 있고 using
선언은 CppAnnotations::value
변수에 사용할 수 있다.
using namespace CppAnnotations::Virtual; using CppAnnotations::value; int main() { pointer = 0; }
using namespace
지시어 다음부터 모든 개체는 그 이름공간에 해당되므로 더 이상 이름공간 없이 사용할 수 있다. 내포된 이름공간을 가리키기 위해 using namespace
지시어를 하나만 사용하면 그 내포된 이름공간의 모든 개체들은 더 이상 이름공간이 없어도 사용이 가능하다. 그렇지만 그 위 이름공간에 정의된 개체들은 여전히 얕은 이름공간이 필요하다. 구체적으로 using namespace
지시어나 using
선언을 한 후에만 이름공간 자격 부여를 생략할 수 있다.
완전히 자격을 갖춘 이름이 더 좋겠지만 다음과 같이 이름이 너무 길면
CppAnnotations::Virtual::pointer이름공간 별칭을 사용할 수 있다.
namespace CV = CppAnnotations::Virtual;이렇게 하면
CV
를 전체 이름에 대한 별칭으로 사용한다. pointer
변수는 이제 다음과 같이 접근해도 된다.
CV::pointer = 0;이름공간 별칭은
using namespace
지시어나 using
선언에도 사용할 수 있다.
namespace CV = CppAnnotations::Virtual; using namespace CV;
C++17 표준부터 내포된 이름공간을 영역 결정 연산자를 사용하여 직접적으로 참조할 수 있다. 예를 들어,
namespace Outer::Middle::Inner { // 여기에 정의/선언된 개체는 Inner 이름공간에 정의되며 // Inner 이름공간은 Middle 이름공간 안에 정의된다. // 이어서 Middle 이름공간은 Outer 이름공간 안에 정의된다. }
이름공간 밖에 개체를 정의하려면 이름 앞에 이름공간을 붙여서 완전히 자격을 갖추어야 한다. 전역 수준에 정의를 하거나 아니면 이름공간에 내포될 경우에 그 사이 수준에 정의할 수 있다. 이렇게 하면 이름공간 A
안의 이름공간 A::B
에 속하는 개체를 정의할 수 있다.
int INT8[8]
유형이 CppAnnotations::Virtual
이름공간에 정의되어 있다고 가정해 보자. 또 squares
함수를 이름공간 CppAnnotations::Virtual
에 정의하고 싶다고 해보자. CppAnnotations::Virtual::INT8
를 포인터로 돌려준다.
필수조건은 CppAnnotations::
Virtual
이름공간 안에 정의했으므로 우리의 함수는 다음과 같이 정의할 수 있다 (메모리 배당 연산자 new[]
에 관한 연구는 제 9장을 참고하라):
namespace CppAnnotations { namespace Virtual { void *pointer; typedef int INT8[8]; INT8 *squares() { INT8 *ip = new INT8[1]; for (size_t idx = 0; idx != sizeof(INT8) / sizeof(int); ++idx) (*ip)[idx] = (idx + 1) * (idx + 1); return ip; } } }
squares
함수는 INT8
벡터의 배열을 정의하고 그 벡터를 8까지 자연수의 제곱으로 초기화한 후에 그 주소를 돌려준다.
이제 squares
함수는 CppAnnotations::
Virtual
이름공간 밖에 정의할 수 있다.
namespace CppAnnotations { namespace Virtual { void *pointer; typedef int INT8[8]; INT8 *squares(); } } CppAnnotations::Virtual::INT8 *CppAnnotations::Virtual::squares() { INT8 *ip = new INT8[1]; for (size_t idx = 0; idx != sizeof(INT8) / sizeof(int); ++idx) (*ip)[idx] = (idx + 1) * (idx + 1); return ip; }위의 코드에서 다음 사실을 눈여겨보자:
squares
함수는 CppAnnotations::Virtual
이름공간 안에 선언된다.
squares
함수 안이면 CppAnnotations::Virtual
이름공간 안에 있는 셈이고 그래서 함수 안에서 완전히 자격을 갖춘 이름은 (예를 들어, INT8
) 더 이상 필요하지 않다.
마지막으로, 함수를 CppAnnotations
이름공간 안에 정의해도 됨을 눈여겨보자. 그 경우 squares()
함수를 정의하고 반환 유형을 지정할 때 Virtual
이름공간이 필요할 것이다. 반면에 함수의 내부는 그대로이다.
namespace CppAnnotations { namespace Virtual { void *pointer; typedef int INT8[8]; INT8 *squares(); } Virtual::INT8 *Virtual::squares() { INT8 *ip = new INT8[1]; for (size_t idx = 0; idx != sizeof(INT8) / sizeof(int); ++idx) (*ip)[idx] = (idx + 1) * (idx + 1); return ip; } }