뚜둔뚜둔✩

판다의 개발일지

C · C++

[C++] namespace 사용 및 특징

2021. 12. 18. 02:06

    Namespace 사용(접근)

    일반적으로, namespace 안 identifer 접근에는 범위 확인 연산자가 사용된다.

     

    [C++] 범위 확인 연산자 (::)

    Scope Resolution Operator 범위 확인 연산자 :: 은 특정 namespace에 포함되는 identifer 접근에 사용된다. namespace foo { void init(void) { } // ① } namespace goo { void init(void) { } // ② } void ini..

    pandas-are-bears.tistory.com

     

    예를 들어, foo라는 namespace 안 check라는 이름의 함수를 호출하려면 다음과 같은 코드 작성이 필요하다.

    int main(void)
    {
    	foo::check();
    }

    check 함수가 매우 여러 번 호출되는 프로그램이라면, 매번 namespace 이름부터 foo::check();를 전부 작성하는 것이 비효율적일 수도 있고, 또 코드 가독성이 떨어질 수도 있다.

     

    이렇게 여러 번 호출되는 identifier에 대한 접근을 간소화시켜주는 것이 바로 C++의 using이다.

     

    using - 간소화된 접근

    using의 사용에는 2가지가 있다.

    <1> using 지시자(Directive)

    [ 사용 방법 ] using namespace namespace_identifier;

     

    using directive를 사용하게 되면 특정 namespace 안 모든 identifier를 가져오는 효과가 있다.

    namespace foo
    {
    	void init(void)		{ }
    	void check(void)	{ }
    }
    
    int main(void)
    {
    	using namespace foo;	// using directive
    
    	init();					// foo::init();
    	check();				// foo::check();
    }

    가장 흔히 쓰이는 using namespace std;가 using directive 사용에 해당된다.

    <2> using  선언(Declaration)

    [ 사용 방법 ] using namespace namespace_identifier::identifier;

     

    using direction은 특정 하나의 이름(identifer)을 범위 확인 연산자 없이 사용할 수 있게 한다.

    namespace foo
    {
    	void init(void)		{ }
    	void check(void)	{ }
    }
    
    int main(void)
    {
    	using namespace foo::check;	// using declaration
    
    	check();					// foo::check();
    }

     

    using 사용 시 주의할 점

    using을 사용하면 작성해야 하는 코드의 양은 줄일 수 있다.

    하지만 using을 사용함으로써 namespace가 해결하였던 이름 충돌 문제가 다시 문제가 될 수 있다.

    namespace foo
    {
    	void check(void) { }
    }
    
    namespace goo
    {
    	void check(void) { }
    }
    
    void check(void) { }
    
    int main(void)
    {
    	using namespace foo;
    	using namespace goo;
        
    	check();				// ....? ERROR "ambiguous symbol"
    }

    foo namespace 안 모든 identifier가 using directive로 import 되었기 때문에

    foo namespace의 check 함수는 foo::check()가 아닌 check()로도 호출이 가능하다.

     

    문제는.. global namespace와 goo에도 동일 이름의 함수가 존재하며, 심지어 goo namespace 역시 using directive로 모든 identifer가 import 된 상태이기 때문에 단순한 check() 호출만으론 이것이 foo, goo, global 중 정확히 어떤 namespace의 함수를 호출하는 것인지 알 수 없다.

     

    이를 해결하기 위해선 결국 1) 완전한 이름을 전부 호출하거나,

    2) using directive 대신 using declaration으로 어떤 check 함수를 사용하는 것인지 명시해주어야 한다.

     

    하나 더 참고해야 할 점은, using을 사용해서 이름 충돌이 현재 발생하지 않은 상태라 하여도 프로그램 규모가 커지거나 다른 library를 새로 include 하면서 이런 이름 충돌은 언제나 발생할 수 있다.

    따라서, 결국 최선의 선택은 항상 완전한 이름을 호출하는 것일 수 있다.

     

    Namespace 특징

    Nested Namespaces

    C++에서는 namespace를 또 다른 namespace 안에서 정의할 수 있다. 즉, namespace의 중첩이 가능하다.

    int init = 0;
    
    namespace outer_namespace
    {
    	int init = 10;
    	namespace first_inner_namespace
    	{
    		namespace second_inner_namespace
    		{
    			int value = init;		// = 10
                						// 'init' refers to outer_namespace::init
    		}
    	}
    }

    Namespace Aliases

    [ 사용 방법 ] namespace alias_name = ns_name;

     

    개발자는 namespace alias 사용으로 어떤 namespace를 다른 이름으로 정의하여 사용할 수 있다. 이는 namespace의 이름이 매우 길거나 사용하고자 하는 namespace가 매우 깊게 중첩되어 있을 때 유용하게 쓰인다.

     

    앞선 예시에서 value 값에 액세스하기 위해선 다음과 같이 매우 긴- 코드 작성이 필요하다.

    ▷ outer_namespace::first_inner_namespace::second_inner_namespace::value

     

    이럴 때 namespace alias를 사용하면 더 짧은 코드 작성으로 동일 요소에 액세스가 가능해진다.

    #include <iostream>
    
    namespace ns_alias = outer_namespace::first_inner_namespace::second_inner_namespace;
    
    int main(void)
    {
    	std::cout << ns_alias::value << std::endl;
    }

    Discontiguous Namespace

    C++에서는 namespace를 여러 코드 블록, 심지어 여러 파일에 나누어 선언할 수 있다.

    // component1.h
    namespace project
    {
    	namespace component1
    	{
    		// ...
    	}
    }
    
    // component2.h
    namespace project
    {
    	namespace component2
    	{
    		// ...
    	}
    }

    하나의 'package'(위의 예시에서는 namespace project) 안 개별 요소들이 클 때, 이와 같이 별개의 파일로 나누어 전체 package를 구성할 수 있다.

     


    using의 등장 배경

    C++의 std namespace는 표준(standard) library를 포함하는 namespace이다.

    즉, 표준 library에 포함되는 함수 및 identifer를 사용할 때에는 std::가 prefix로 붙게 된다.

     

    하지만, C++에 namespace 문법이 지원되기 전에는 표준 library의 요소들은 모두 global namespace에 포함되었고, 해당 시절에 작성된 코드는 그에 따라 작성되었다.

     

    이제 namespace 문법이 지원됨에 따라 표준 library는 모두 std namespace에 포함되었기 때문에 std::를 사용하지 않은, global namespace를 기준으로 작성된 이전의 코드는 동작할 수 없게 되었다.

    하지만 이전의 코드에 단순히 std::를 추가하는 것만으로는 코드가 온전히 동작한다는 것이 보장되지 않기에 using이 등장하였다.

     

    stdio.h library는 C의 표준 라이브러리로, C 언어의 방식으로 구현되었기에 C++에서는 모두 global namespace에 포함된다.

    하지만 C++에서는 모든 표준 library 요소를 std namespace 안에 넣고 싶었기에 cstdio를 다음과 같이 구성하였다.

    // cstdio file
    #include <stdio.h>
    
    _STD_BEGIN
    using _CSTD printf;		// same as using ::printf;
    						// (global namespace)
    _STD_END
    #define _STD_BEGIN	namespace std {
    #define _STD_END	}
    #define _STD		::std::
    
    #ifdef __cplusplus
    	#define _CSTD	::
    #endif

    cstdio 파일을 살펴보면 기존의 stdio.h 헤더 파일을 include하면서 stdio.h 파일에서 global namespace에 선언된 함수를 std namespace 안에서 using declaration 하고 있다.

    따라서 stdio.h 대신 cstdio를 include하면 printf와 같은 C 표준 library 함수를 1) 원래대로 printf(); 사용이 가능하고, 2) std::printf(); 사용도 가능하다.


     

    References

    https://www.learncpp.com/cpp-tutorial/using-declarations-and-using-directives/

     

    'C · C++' 카테고리의 다른 글

    [C(++)] Header Guard (헤더 가드) Coding  (0) 2022.02.05
    [C] printf 함수  (0) 2021.12.21
    [C] const type qualifier  (0) 2021.12.21
    [C++] 범위 확인 연산자 (::)  (0) 2021.12.15
    [C++] namespace  (0) 2021.12.15