겸손한 개발을 위한 자양분


namespace 에 대해 오해하고 있는 소수의 분들을 위해...

Static Library 를 제작하고, 또 사용하다보면 Name Conflict 에러가 발생하는 경우가 있습니다.
이 에러는 프로젝트에 포함된 이종 라이브러리가 같은 이름의 함수명/변수명을 갖게 될 경우 나타납니다.

가령, 프로젝트에서 a.lib , b.lib 두개의 라이브러리를 link 할때에
a.lib 에서 fnA() 라는 함수가 선언되어있고,
b.lib 에서 fnA() 라는 함수가 선언되어있는 경우
두 함수의 이름이 같기 때문에 발생하는 에러입니다.

이 때 conflict 를 피하기 위하여 생긴것이 namespace 입니다.
namespace 는 내부에 정의된 이름들에 대해 마치 class의 캡슐화와 같은 효과를 줍니다.

.net 프로그래밍을 하셨다면,
using namespace std;
라는 구문을 보셨을텐데요,

이 명령은
소스코드 내에서 cout 등의 함수들을 호출할때
std::cout 을 호출하는것으로 인식하게 해 줍니다.

즉, 표준 함수의 namespace 가 std 인 셈입니다.

개발을 하다보면,
동일 소스를 사용해서 이종 Static Library 를 생산해야할 때가 있습니다.
이럴때에는 소스가 동일하기 때문에 똑같은 이름의 함수/변수를 갖는 Static Library 가 생기게 됩니다.
( 물론 소스를 복사해서 쓸수도 있지만, 동일 소스가 여러개로 보관되면 관리상의 치명적인 문제점이 생기게 되겠죠 )

이럴때, 프로젝트를 기준으로 하여
소스가 동일하더라도, 프로젝트별 namespace 를 달리하게 되면
하나의 코드로 여러개의 library 를 만들더라도 conflict 문제 없이 빌드할 수 있습니다.

일반적으로 namespace 를 사용하는 소스의 예는 다음과 같습니다.

using namespace myprojectns;
 
namespace myprojectns{

int g_nCount;

void fnA(){
Sleep(0);
}

}


마치 Class 와 비슷한 모양을 갖게 되는데요
위의 예에서는 myprojectns 로 설정된 네임스페이스 내에
직접 코딩을 하게 되면 외부의 소스에서는 접근할때에 ( extern 되어있다고 가정 )
myprojectns::g_nCount
myprojectns::fnA()
로 접근 할 수 있습니다.

다음은 이를 간단히 적용하기 위한 매크로 입니다.

// MyNamespace.h

#pragma once
#if defined(_LIB_SDK_PROJECT_Y)
#define MYNAMESPACE MYNAMESPACE_LIB_PROJECT_Y
#elif defined(_LIB_SDK_PROJECT_Z)
#define MYNAMESPACE MYNAMESPACE_LIB_PROJECT_Z
#else
#error MYNAMESPACE Required. Check MyNamespace.h
#endif
#define MN MYNAMESPACE 

#define _START_OF_INTERNAL_SRC  namespace MYNAMESPACE {using namespace MYNAMESPACE ;
#define _END_OF_INTERNAL_SRC  }
#define _SO_INTERNAL_SRC   _START_OF_INTERNAL_SRC  
#define _EO_INTERNAL_SRC   _END_OF_INTERNAL_SRC 

_LIB_SDK_PROJECT_Y 는 Project의 Properties 에서 설정한 Preprocessor definitions 입니다.
Project별로 다른 정의를 주어야하는 부분입니다.

위의 매크로는 Preprocessor definitions 에서 프로젝트 특성을 읽어
서로 다른 namespace 를 적용할 수 있도록 해줍니다.

예에서는 Preprocessor definitions 가
_LIB_SDK_PROJECT_Y 인경우 MYNAMESPACE_LIB_PROJECT_Y
_LIB_SDK_PROJECT_Z 인경우 MYNAMESPACE_LIB_PROJECT_Z
의 namespace 가 정의됩니다.

cpp 소스에서의 사용은 다음과 같습니다.

// _LIB_SDK_PROJECT_Y 의 소스일 때

#include <stdio.h>
#include "MyNamespace.h"

_SO_INTERNAL_SRC


VOID fnA()
{
...
}

BOOL fnB()
{
...
}

_EO_INTERNAL_SRC 

헤더파일을 포함하고, 소스의 위 아래를 _SO_ , _EO_  매크로로 감싸주면
헤더에 정의된 namespace 가 project 별로 적용됩니다.

외부에서 위 소스의 함수를 호출하고자 할때에는 다음과 같이 호출할 수 있습니다.
//_LIB_SDK_PROJECT_Y 라이브러리를 링크했을 때

#include <stdio.h>

void initialEntry()
{

MYNAMESPACE_LIB_PROJECT_Y::fnA();
MYNAMESPACE_LIB_PROJECT_Y::fnB();

}

혹은,
//_LIB_SDK_PROJECT_Y 라이브러리를 링크했을 때

#include <stdio.h>

using namespace MYNAMESPACE_LIB_PROJECT_Y;

void initialEntry()
{

fnA();
fnB();

}


외부 library 제작이 필요없는 실행파일 기반 프로젝트이거나
소형 프로젝트일 경우에는 namespace를 사용하지 않아도 될것 같습니다.

하지만, 여러명이 작업하는 프로젝트이거나 ( 소스별 extern function name conflict 문제 )
static library 를 만드는 프로젝트일 경우에는
사용할 필요성이 높아 보이는 부분입니다.

-------------------------------------------------------------------------------------------

덧붙혀서,
매크로를 자세히 보신 분은 MYNAMESPACE 가 MN 으로 정의된걸 보셨을텐데요
이는 내부에서의 사용편의성을 위한 정의입니다.

가령, 배포용 라이브러리를 제작할때에
내부소스에서 사용되는 함수/변수들은 namespace를 주고,
extern 되는 함수에 대해서는
(사용편의성을 위해) namespace없이 unique한 이름으로 정의하는 경우
내부에서 사용되는 소스의 경우 _SO_, _EO_ 매크로를 사용하고,
외부에 extern 되는 소스는 _SO_, _EO_ 매크로의 사용없이
MN::fnA() 등과 같이 바로 접근할 수 있도록 정의한 부분입니다.

가령,
// extern source

#include <stdio.h>
#include "MyNamespace.h"

extern "C" void fnProjectInit()
{
MN::fnA();
}

( extern의 위치는 이해의 편의를 위해... )

lib 제작시 외부에 제공되는 함수는 위의 모습처럼 _SO_, _EO_ 없이 사용하고,
내부에서 다른소스의 함수를 가져다 쓰는 형태를 만들어주면,
외부에서는 namespace 없이 fnProjectInit()을 바로 호출할 수 있고,
소스의 내부에 정의된 함수나 변수이름등은 중복확률이 낮아지게 되는 효과를 갖게 됩니다.