티스토리 뷰

※ 주의 사항 ※

  • 이 글의 목적은 '지식의 전달'이 아닌 '학습의 기록'입니다.
  • 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
  • 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.

 

등장 배경

먼저 다음의 이야기를 통해 namespace의 등장 배경에 대해 알아보겠습니다.

은행 관리 시스템을 개발하는 데에 있어서 세 개의 회사가 참여를 했습니다. 각 회사의 이름은 BestCom, ProgCom, HybridCom입니다.

이들은 프로젝트의 규모가 큰 관계로 일을 구분하여 독립적으로 진행하기로 했습니다. 그래서 이를 목적으로 구현해야 할 부분을 적절히 나누고 6개월 뒤에 모여서 하나의 프로젝트를 완성하기로 합니다. 드디어 6개월의 시간이 흘렀습니다. 각각의 회사가 구현한 모듈을 하나로 묶고, 부족한 부분을 완성할 때가 되었습니다. 그런데 문제가 발생했습니다.

BestCom에서 정의한 함수의 이름과 ProgCom에서 정의한 함수의 이름이 같은 것입니다. 이름 충돌이 나지 않도록 미리 약속이라도 할 것을 그랬다는 후회가 드는 순간입니다. 그래서 BestCom 팀장과 ProgCom 팀장이 서로 싸우기 시작합니다. 이름 하나 바꾸는 것이 쉽지 않은 일이기 때문입니다. 상황에 따라서는 프로그램의 상당 부분에 영향을 미치는 일입니다.

그런데 문제는 산 너머 산입니다. HybridCom에서는 맡은 부분을 구현하는 과정에서 과거에 구현해 놓은 함수를 일부 사용했는데, 이 함수들의 이름 중 상당수가 BestCom과 ProgCom에서 구현한 함수의 이름과 겹치는 것입니다.

결국 프로젝트는 실패로 돌아갔습니다.

 

그렇다면 해결책은 과연 무엇일까요? 프로젝트를 진행하기 전에 함수 및 변수의 이름을 모두 정해서 이름 충돌이 발생하지 않게 하는 것이 해결책일까요? 물론 이는 해결책이 될 수 있으나 프로젝트를 시작도 하지 않은 상황에서 어떤 변수와 함수들을 사용하게 될지 아무도 완벽히 알 수는 없기 때문에 한계가 분명합니다.

 

따라서 C++의 표준에서는 namespace라는 문법을 정의해서 이러한 문제에 대한 근본적인 해결책을 제시합니다.

 

개념

한 건물에 철수가 2명이 있다면 "철수야"하고 불렀을 때 누구를 부른 것인지 알 수 없습니다. 하지만 두 명의 철수가 각각 사는 곳이 다르다면 "204호 철수야", "601호 철수야"하고 부를 수 있습니다. 이것이 namespace의 개념입니다. 아래의 코드는 이름 충돌의 상황을 보여주고 있습니다.

#include <iostream>

void SimpleFunc(void)
{
	std::cout << "BestCom이 정의한 함수\n";
}

void SimpleFunc(void)
{
	std::cout << "ProgCom이 정의한 함수\n";
}

int main(void)
{
	SimpleFunc();

	return 0;
}

 

위의 코드는 별도의 설명이 없어도 함수의 이름과 매개 변수의 자료형, 매개 변수의 개수가 동일하기 때문에

문제가 됨을 쉽게 알 수 있습니다. 그런데 아래의 예제와 같이 namespce를 만들고, 그 안에서 함수를 정의하거나 변수를 선언한다면 이름 충돌을 피할 수 있습니다.

#include <iostream>

namespace BestCom
{
	void SimpleFunc(void)
	{
		std::cout << "BestCom이 정의한 함수\n";
	}
}

namespace ProgCom
{
	void SimpleFunc(void)
	{
		std::cout << "ProgCom이 정의한 함수\n";
	}
}

int main(void)
{
	BestCom::SimpleFunc();
	ProgCom::SimpleFunc();

	return 0;
}

/*
실행결과

BestCom이 정의한 함수
ProgCom이 정의한 함수

*/

 

위 예제에서 SimpleFunc 함수를 호출하기 위해 각 namespace뒤에 ::연산자를 붙여 호출함을 알 수 있습니다.

::연산자는 '범위 지정 연산자'라고 하며, 네임스페이스를 지정할 때 사용하는 연산자입니다. 

 

네임스페이스 기반 함수의 선언과 정의 분리

함수는 선언과 정의를 분리하여 함수의 선언은 헤더 파일에 저장하고, 함수의 정의는 소스파일에 저장하는 것이 일반적입니다(이런 습관을 들이시면 좋습니다).  따라서 예제를 통해 네임스페이스 기반에서 함수의 선언과 정의를 구분하는 방법을 설명하겠습니다.

//functions.h 헤더파일로 저장
namespace BestCom
{
	void SimpleFunc(void);
}

namespace ProgCom
{
	void SimpleFunc(void);
}
//main.cpp 소스파일로 저장
#include <iostream>
#include "functions.h"

int main(void)
{
	BestCom::SimpleFunc();
	ProgCom::SimpleFunc();
}

void BestCom::SimpleFunc(void)
{
	std::cout << "BestCom이 정의한 함수\n";
}

void ProgCom::SimpleFunc(void)
{
	std::cout << "ProgCom이 정의한 함수\n";
}

/*
실행결과

BestCom이 정의한 함수
ProgCom이 정의한 함수

*/

 

위와 같이 함수의 선언을 헤더 파일에, 함수의 정의를 소스파일에 저장해서 사용하는 경우도 어렵지 않게 적용할 수 있습니다. 동일한 공간에 선언된 변수나 함수를 사용할 때는 ::연산자를 사용해서 namespace를 지정해주지 않아도 됩니다.

 

using 선언문

지금까지 콘솔 입출력을 진행할 때 std::cout, std::cin, std::endl을 사용해 왔습니다. 눈치챘겠지만 여기에도 ::연산자가 사용되고 있습니다. 따라서 ::연산자 앞의 std는 namespace임을 알 수 있습니다. std는 standard의 약자입니다.

 

cout, cin, endl을 사용하기 위해 매번 std::를 붙여야 하는 것은 여간 귀찮은 일이 아닙니다. 이때 using 선언문을 사용하여 namespace 안의 함수를 범위 지정 없이 호출할 수도 있습니다. 예제를 통해 살펴보겠습니다.

#include <iostream>

using std::cout;
using std::endl;

int main()
{
	cout << "Hello world!" << endl;

	return 0;
}

/*
실행 결과

Hello world!

*/

 

위 예제에서 using 선언문이라고 불리는 부분은 바로 이 부분입니다.

using std::cout;
using std::endl;

 

위 코드는 각각 '앞으로 namespace가 지정되지 않은 채 cout이나 endl이 사용되면 std::cout, std::endl의 것을 사용해라'라는 의미를 담고 있습니다.

 

그리고 해당 using 선언문이 전역으로 선언되었으므로 이는 전역적으로 적용되게 됩니다. 만약 using 선언문이 main() 함수 안에 선언되었다면 이는 main() 함수 안에서만 효력을 가지게 됩니다.

 

using 선언문은 C++11 이후부터 위에서 언급한 방법 외에 '별칭'이란 이름으로 다음과 같이 사용할 수 있습니다.

#include <iostream>

using integer = int;    //typedef int integer와 동일한 의미

int main()
{
	integer number = 120;

	std::cout << number << std::endl;

	return 0;
}

/*
실행 결과

120

*/

 

위 예제의 아래와 같은 코드는

using integer = int;

 

C언어에서 사용한 다음과 같은 typedef 문법과 아주 동일한 의미를 가집니다.

typedef int integer;

 

using 선언문은 다음과 같이 템플릿과 연계되어 데이터 타입이 매우 긴 경우 사용하면 유용합니다.

using doubleTypeIt = std::vector<double>::const_iterator;

 

하지만 일반적으로 프로그램 내부에서 using이나 typedef, #define 별칭을 너무 많이 사용하면 가독성이 떨어지므로 한정된 영역 내 작은 블록 단위로 국한하여 사용하는 것이 좋습니다.

 

using 지시문

using 지시문은 다음과 같이 사용할 수 있습니다.

#include <iostream>

using namespace std;

int main()
{
	cout << "Hello world!" << endl;

	return 0;
}

/*
실행 결과

Hello world!

*/

 

위 예제에서 다음과 같은 문장은

using namespace std;

 

'만약 프로그램 실행 중에 namespace가 지정되지 않아 어디에 선언된 것인지 확인할 수 없는 식별자가 있다면 이를 std라는 namespace에서 찾으라'와 같은 의미를 가집니다. 이에 따라 main() 함수에서 cout과 endl의 namespace가 지정되어 있지 않음에도 이를 자동으로 std namespace에서 찾게 됩니다.

 

단, 주의할 사항이 있습니다. using 지시문은 서로 다른 네임스페이스에서 같은 이름으로 선언된 식별자가 존재한다면 에러를 발생하게 됩니다. 따라서 규모 있는 프로그램을 여러 그룹에서 공동으로 개발한다면, using 지시문을 사용하지 않는 것이 좋습니다.

 

네임스페이스 중첩, 별칭 사용

namespace는 다른 namespace 안에 삽입될 수 있습니다. 이를 namespace의 중첩이라고 부릅니다. 이름 공간이 중첩되면서까지 과도하게 사용되는 경우는 극히 드뭅니다. 하지만 다음과 같이 과도하게 사용이 되었을 때,

namespace AAA
{
	namespace BBB
	{
		namespace CCC
		{
			int num1;
			int num2;
		}
	}
}

 

다음과 같은 방법으로 변수 num1과 num2에 접근하는 것은 매우 비효율적입니다.

AAA::BBB::CCC::num1 = 20;
AAA::BBB::CCC::num2 = 30;

 

그래서 이런 경우에는 다음과 같이 AAA::BBB::CCC와 같은 이름에 별칭을 줄 수 있습니다.

namespace ABC = AAA::BBB::CCC;

 

이렇게 별칭이 주어지고 나면 다음과 같이 num1과 num2에 접근하는 것이 가능해집니다.

ABC::num1 = 20;
ABC::num2 = 30;

 

지역 변수에 가린 전역 변수 사용

지역변수와 전역 변수의 이름이 같은 경우 전역 변수는 지역변수에 의해 가려집니다. 다음 코드를 통해 살펴보겠습니다.

int val = 100;

int SimpleFunc(void)
{
	int val = 20;
	val += 3;
}

 

처음 val은 전역 변수로 선언되었고 100으로 초기화되었습니다. 이후 SimpleFunc 함수 내에서 같은 이름의 지역변수 val이 선언되었고 20으로 초기화되었습니다. 이후 val에 3을 더해주었습니다. 만약 이 val을 출력한다면 값은 23일 것입니다. 전역 변수 val이 지역변수 val에 가려졌기 때문입니다.

 

만약 위와 같은 상황에 전역 변수 val를 사용하고 싶다면 ::연산자를 사용하여 다음과 같이 할 수 있습니다.

int val = 100;

int SimpleFunc(void)
{
	int val = 20;
	val += 3;
	::val += 10;
}

 

'공부 일지 > CPP 공부 일지' 카테고리의 다른 글

C++ | 참조자(Reference)  (0) 2021.08.01
C++ | 새로운 자료형 bool  (0) 2021.07.31
C++ | 인라인(inline) 함수  (0) 2021.07.31
C++ | 매개변수의 디폴트 값  (0) 2021.07.31
C++ | 함수 오버로딩  (0) 2021.07.31
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함