• 1.9.1 네임스페이스(namespace)
  • 1.9.2 using namespace

1.9.1 네임스페이스(namespace)

 

우리말로는 명칭 공간이라고도 하는 개념이 C++에 있다. 프로그램을 만들다 보면 다른 동작을 하면서 같은 이름의 함수를 많이 쓰게 되는데, 이때 컴파일러가 어떤 함수인지 명확히 선택할 수 있도록 구분해줘야 한다. 이를 위해 사용하는 것이 바로 네임스페이스이다.

 

#include <iostream>

namespace MySpace1
{
	// 네임스페이스 안에 네임스페이스 가능
	namespace InnerSpace
	{
		int myFunction()
		{
			return 0;
		}
	}
	int doSomething(int a, int b)
	{
		return a + b;
	}
}

namespace MySpace2
{

	int doSomething(int a, int b)
	{
		return a * b;
	}
}


using namespace std;

int main()
{
	
	// 3*4가 실행된다.
	cout << MySpace2::doSomething(3, 4) << endl;

	//앞으로 MySpace1이라는 namespace를 사용할 것이다.
	using namespace MySpace1;

	// 3+4가 실행된다.
	cout << doSomething(3, 4) << endl;
	InnerSpace::myFunction();

	return 0;
}

 

코드를 보면 같은 이름의 함수가 존재하지만 빌드가 잘되는 것을 볼 수가 있다. 바로 네임스페이스 개념을 사용하였기 때문이다. 네임스페이스 안에 함수를 정의하면 해당 함수를 사용할 때는 네임스페이스를 앞에 작성해야 하고 이를 통해 컴파일러가 어떤 함수인지 혼동하지 않고 바로 찾을 수가 있다.

 

이러한 방식이 왜 필요한지 독학을 하는 경우엔 이해가 되지 않을 수 있는데, 실무에 가면 협업하는 사람들이나 선배님들이 작성한 코드에서 함수의 이름을 잘 나타내기 위해 최대한 보편적인 언어로 작성을 한다. 그러한 방식을 사용하다 보니 서로가 같은 이름의 함수를 작성하는 경우가 생기기도 한다. 실제로 현 직장에서 다루는 프로그램의 코드를 보면 configure 함수가 여러 개 존재하는 것을 볼 수가 있다.


1.9.2 using namespace

 

아마 많은 C++ 개발자들이 기초 교육을 받을 때 많이 써본적이 있을 것이다. using namespace std;라는 명령문은 정말 친숙하게 사용했을 것이라 생각한다. using namespace를 통해 해당 영역을 벗어나지 않는다면 계속 언급한 네임스페이스를 사용할테니 네임스페이스를 제외하고 함수를 호출해도 알아서 찾아달라는 의미이다.

 

앞의 예시에서는 std를 그런식으로 쓰고 MySpace1도 사용하였다. 이처럼 네임스페이스를 구현하고 사용하는 방법이 다채로운데, 처음 접한다면 시간을 투자해서 이것저것 실험을 해보는 것이 좋겠다.

  • 1.8.1 링킹 에러
  • 1.8.2 중복 정의
  • 1.8.3 헤더 가드(Header Guards)

1.8.1 링킹 에러

 

Linking Error

프로그램을 만들다 보면 LNK2019, LNK1120이라는 링킹 에러를 만나는 경우가 종종 생긴다. 위와 같은 에러가 생기는 이유는 특정 함수에 대하여 선언은 존재하지만 정의가 존재하지 않는 경우이다. 

 

add function header

위처럼 add 함수에 대한 선언을 헤더 파일로 작성하고서 include를 통해 main 함수가 작성된 코드에서 add 함수를 사용했다면 전방 선언이 된 효과를 가지게 된다. 선언이 되어 있으므로 컴파일러는 통과를 시키고 빌드가 이루어지는데, 결국 해당 함수의 정의가 담긴 cpp 파일이 없다면 함수 호출 뒤에 어떤 연산을 해야 할지 모르는 상황이 발생하면서 링킹 에러가 생긴다. 따라서 링킹 에러가 생긴다면 혹시나 정의를 빼먹은 함수가 없는지 확인해보자!


1.8.2 중복 정의

 

위와는 다르게 과도한 정의로 인한 에러가 생기는 경우가 있다.

function already has a body

이처럼 C2084 에러가 잡히면 Description 의미 그대로 이미 중복 선언이 된 경우이다. 일반적으로 많은 함수를 선언과 정의를 해서 헤더 파일로 가지고 있다면 여기저기서 include를 통해 함수를 재사용하고 있을 것이다. 다음의 예시를 보자.

 

add header
dosomething header

add 함수가 있는 헤더 파일을 만들고 add 함수를 끌어다 쓰는 doSomething 함수를 만들어 다른 헤더 파일에 만들었다.

 

main 함수

그리고 main 함수가 담긴 cpp 파일이 위처럼 작성되어 있다. 여기서 #include 전처리기가 작동하는 방식을 이해하면 왜 중복 정의인지 알 수가 있는데, #include 전처리기는 해당 파일을 복사해서 가지고 오는 것처럼 작동한다. 따라서 위의 코드는 아래와 같은 상황이 벌어진다.

 

add 중복


1.8.3 헤더 가드(Header Guards)

 

아마 헤더 파일을 만드는 순간 #pragma once라는 전처리기를 보았을 것이다. 해당 전처리기는 이미 한번 정의된 함수라면 다시 정의하지 않도록 해주는 전처리기로 프로그램이 커질수록 해당 전처리기의 도움을 많이 받는다. 같은 방식으로 동작하는 것이 아래의 코드가 되겠다.

 

#ifndef  MY_ADD
#define  MY_ADD

int add(int a, int b)
{
	return a + b;
}

#endif // ! MY_ADD

 

다음과 같이 코드가 작성 되어 있다면 가장 앞에 #pragma once 전처리기를 둔 것과 같은 효과를 지닌다. #ifndef MY_ADD를 통해 만약 MY_ADD가 정의되어 있지 않다면 #endif 바로 위까지 코드를 읽어드리는 것이다. 이런 경우 #define MY_ADD가 최상단에서 정의가 되고 add 함수를 정의하기 때문에 추후 해당 헤더 파일을 다시 include하더라도 MY_ADD가 정의되어 있기 때문에 다시 add 함수를 정의하는 불상사는 발생하지 않는다.

  • 1.7.1 선언(Declaration)과 정의(Definition)
  • 1.7.2 헤더(Header) 파일 만들기
  • 1.7.3 Peek Definition
  • 1.7.4 Close All But This

1.7.1 선언(Declaration)과 정의(Definition)

 

만드는 프로그램이 커질수록 중요해지는 개념이 바로 선언과 정의이다. 컴파일러는 주어진 코드를 순차적으로 읽기 때문에 사용하고자 하는 함수에 대해 미리 알고 있어야 한다. 다음의 예시를 보자.

 

identifier not found

분명 눈으로 보기에는 add 함수와 subtract 함수가 main 함수 아래에 존재하지만 컴파일러는 identifier not found라는 에러를 출력하며 해당 함수들을 찾지 못하고 있다. 앞에서 이야기했듯이 컴파일러는 순차적으로 코드를 읽기 때문에 main 함수에 진입하였을 때, add 함수와 subtract 함수가 무엇인지 알 수 없었고 에러를 출력한 것이다.

 

많은 사람들이 처음에는 이러한 컴파일 에러를 넘어가기 위해서 main 함수 위에 add 함수와 subtract 함수를 옮겨서 해결하는데, 프로그램을 계속 만들다 보면 사용하는 함수들이 커져가기 마련이다. 함수를 옮기지 않고 해결하는 방법이 바로 선언을 사용하는 것이다.

 

Forward Declaration

위처럼 함수의 최소한의 정보(입력과 출력에 대한 자료형)를 가지고 작성해주는 것이 선언이며, main 함수 뒤에 정의를 두고서 main 함수 앞에 선언을 하는 경우 전방 선언이라고도 한다. 정의는 말그대로 함수가 어떤 작동을 하는지 코드로 작성한 것을 말한다.


1.7.2 헤더(Header) 파일 만들기

 

앞에서는 전방 선언을 통해서 main 함수가 사용할 함수가 무엇인지 컴파일러에게 미리 알려주는 방식을 택하였다. 하지만 이러한 방법도 다루는 함수가 양이 많아질 경우 위아래로 스크롤을 많이 움직이는 비효율적인 작업을 하게 된다. 이를 해결하기 위해 함수의 정의와 선언에 대해 개별적으로 파일을 만들고 main 함수가 담긴 파일이 헤더 파일을 include하는 방식을 사용한다.

Header files and source files

add라는 함수를 예시로 사용하기 위해 위처럼 파일을 만든다. Header Files 폴더에 함수의 선언을 담을 헤더 파일을 생성하고 Source Files 폴더에는 함수의 정의를 담을 cpp 파일을 만들었다.

 

add.h

헤더 파일을 생성하는 경우 자동으로 #pragma once라는 헤더 가드 전처리기가 생성된다. 파일에는 add 함수의 선언만 작성하였다.

 

add.cpp

cpp 파일에는 add 함수의 정의를 작성하였다.

 

main 함수가 담긴 파일

main 함수가 담긴 파일은 #include 전처리기를 통하여 add.h를 추가해준다.  이를 통해 전방 선언과 똑같은 효과를 가져올 수가 있다. 만약 Header Files 폴더가 아닌 새 폴더에 헤더 파일을 만들어서 관리하는 경우 해당 폴더의 절대 경로를 "" 사이에 넣어줘야한다.


1.7.3 Peek Definition

 

위처럼 선언과 정의가 파일 자체로 분리가 되고 많은 함수를 만들어서 다루다 보면 해당 함수가 사용되는 부분에서 이게 무슨 함수인지를 보고 싶은 경우가 생길 것이다. 정의 파일로 직접 가서 볼 수도 있지만 사용되는 곳에서 바로 볼 수도 있다.

 

Peek Definition

Peek Definition 기능을 사용하면 아래와 같이 사용하는 곳에서 함수의 정의를 빠르게 볼 수 있다.

 

Definitions

예시의 경우 add 함수가 총 세 곳에 정의되어 있었다. add.cpp를 제외한 모든 파일은 빌드에서 제외된 상태였으므로 위처럼 존재해도 실행하는 것에는 지장이 없었다.


1.7.4 Close All But This

 

Peek Definition만큼 유용한 기능이 Close All But This이다.

 

Close All But This

현재 캡처에는 총 4개의 파일이 열려 있는데, 다루는 파일이 많아질수록 지저분하게 많은 파일을 열게 된다. 이를 한 번에 닫아주고 내가 보고 있는 파일만 남기는 기능이 바로 Close All But This이다.

 

After Close All But This

 

 

  • 1.6.1 리터럴(Literal)
  • 1.6.2 연산자(Operator)와 피연산자(Operand)

1.6.1 리터럴(Literal)

 

리터럴은 C++에서 언급되는 상수 중의 하나로 직접적으로 입력하는 데이터이다.

#include <iostream>

using namespace std;

int main()
{
	// Assignment operator =
	int x = 2; // x is a variable, 2 is a literal.
	
	cout << 1 + 2 << endl; // 1 and 2 are literal.

	// Unary operator -
	cout << -x << endl;

	// Binary operator +
	cout << 1 + 2 << endl;

	// Ternary operator ? :
	int y = (x > 0) ? 1 : 2;
	cout << y << endl;

	return 0;
}

 

위의 코드에서 x를 2로 초기화하는데, 여기서 입력하게 되는 2를 리터럴이라고 한다. 저 2라는 값에 3이라는 값을 넣을 방법이 없는 것처럼 바꿀 수 없는 것을 리터럴이라고 한다.


1.6.2 연산자(Operator)와 피연산자(Operand)

 

연산자는 우리가 평소에 다루던 +, -, *를 의미하고 피연산자는 연산자가 행하는 연산 수행에 사용되는 것들을 의미한다. 연산자는 단항 연산자(unary operator), 이항 연산자(binary operator), 삼항 연산자(ternary operator)가 있다.

 

-의 경우 숫자 앞에 쓰이므로 하나의 피연산자로 행해지는 연산이다. 이처럼 피연산자가 하나만 사용될 경우 단항 연산자라 한다. +와 *는 연산자의 양쪽에 피연산자가 필요하므로 이항 연산자가 된다.

 

C++에서 정의된 삼항 연산자는 조건문을 비교하여 그 결과에 따라 값을 선택하는 연산을 수행한다. 위의 코드에서는 x가 0보다 큰지에 대한 판단을 하고 참이면 1을 거짓이면 2를 반환하도록 구현되어 있다.

+ Recent posts