열거형 타입 enum에 대해 이전에 포스팅하였는데, 편리하지만 생각보다 단점이 많이 존재한다. 이를 어느 정도 해소시켜줄 수 있는 것이 바로 열거형 클래스 enum class이다. 영역 제한 열거형이라고도 부르며 enum struct도 존재하지만 주로 enum class만 사용한다.


enum이 발생시키는 상황

 

enum은 마치 #define 매크로처럼 제한이 없이 넓은 영역에 영향을 미친다고 볼 수 있다. 서로 다른 enum일지라도 이름을 같게 해서는 안되며, 서로 다른 이름에 다른 enum일지라도 같은 값을 가지면 조건문에서 쓸 때 같다고 판단해버린다.

#include <iostream>

int main()
{
	using namespace std;

	enum Color
	{
		RED,
		BLUE
	};

	enum Fruit
	{
		BANANA,
		APPLE
	};

	Color color = RED;
	Fruit fruit = BANANA;

	if(color==fruit)
		cout << "Color is fruit ? " << endl;

	return 0;

}

위의 코드를 실행하면 if문에서 참으로 판단하고 출력을 수행한다.


enum class

 

enum class를 사용하면 애초에 위와 같은 조건문을 사용할 수 없도록 막아버린다. 아래는 enum class로 변경한 코드이다.

#include <iostream>

int main()
{
	using namespace std;

	enum class Color
	{
		RED,
		BLUE
	};

	enum class Fruit
	{
		BANANA,
		APPLE
	};

	Color color = Color::RED;
	Fruit fruit = Fruit::BANANA;
	
	if(color==fruit)
		cout << "Color is fruit ? " << endl;

	return 0;
}

위의 코드는 실행되지 않는다. 실행할 경우 다음과 같은 에러 메시지가 뜬다.


Error C2676 binary '==': 'main::Color' does not define this operator or a conversion to a type acceptable to the predefined operator

서로 다른 클래스의 멤버이기 때문에 막혀버린다.

if (static_cast<int>(color) == static_cast<int>(fruit))

물론 조건문을 위처럼 static_cast를 사용하여 int로 변경한 뒤 사용할 수 있겠지만 좋은 생각은 아니다.

 

enum class를 통해 같은 enum class 내부에 있는 값들로만 비교할 수 있게 되므로 휴먼 에러를 줄일 수 있는 방법이 되겠다.

C++11에서 auto 키워드가 생겼다. 우리가 기존에 명시해주던 자료형에 대하여 저장하는 데이터 형태를 보고 자료형을 추론해내어 알아서 저장한다. 프로그래밍하면서 굉장히 유용하게 쓸 수 있으니 숙지해두면 좋을 개념이다.


일반적인 변수 초기화

 

일반적으로 하는 변수 초기화를 생각해보자. 변수 이름 앞에 자료형을 기입해주고 초기화해주는 r-value를 해당 자료형에 맞게 입력해준다. 이 경우 해당 변수는 양 옆으로 똑같이 자료형에 대한 정보를 주는 상황이 된다. 즉, 같은 정보가 중복되어서 입력된 것이다. 물론 double로 선언하고 3을 넣는 사람도 있지만 3.0을 넣는게 올바른 프로그래밍이겠다.

#include <iostream>

int main()
{
	using namespace std;

	int a = 123;

	return 0;
}

int 자료형과 r-value 123(int) 정보가 중복되어 들어오는 예시이다.


auto 키워드

 

앞에서 언급한 것처럼 정보가 중복되어 들어오는 것을 비효율적이라 생각하여 auto 키워드가 생겼다. 정보 입력은 하나만 있어도 충분하기 때문이다. auto 키워드를 사용하여 선언을 하는 경우 반드시 초기화도 함께 해주어야 한다. 저장되는 데이터를 컴파일러가 추론해서 어느정도의 메모리를 사용하여 어떤 기본 자료형으로 저장할지 선택해야 하는데, 애초에 저장할 데이터를 안 주면 추론 자체가 불가능하니 당연하겠다.

#include <iostream>

int main()
{
	using namespace std;

	int a = 123;

	auto b = 123123123123123123;
	auto d = 123.0;
	auto c = 1 + 2.0;

	return 0;
}

이렇게 auto로 초기화한 변수에 마우스 커서를 올리면 어떤 자료형으로 추론이 되었는지 알 수 있다.

long long으로 추론되었다.

이렇게 한 번 추론이 되면 추론된 자료형으로 취급하여 다루면 되겠다.


함수에서의 auto 키워드

 

auto 키워드는 함수를 다룰 때도 사용할 수 있다.

int add(int x, int y)
{
	return x + y;
}

int main()
{
	using namespace std;

	auto result = add(1, 2);

	return 0;
}

이처럼 함수에서 return 값을 auto로 받아낼 수 있겠다. 위의 코드에서 add 함수를 아래 두가지로 변경해서 넣어보자.

auto add(int x, int y)
{
	return x + y;
}

auto add(int x, int y)
{
	return x + (double)y;
}

정의부터 auto를 써서 사용한다. 애초에 return에 놓인 결과값도 int이기 때문에 추론이 가능한 형태이다. 두 번째 add 함수는 return하기 전에 double로 형 변환을 수행하였다. 이럴 경우 return 값이 double로 변하고 해당 함수의 return 값을 받아야 하는 변수도 double이 되어야 한다. 전부 auto로 연결되어서 문제없이 작동하는 것을 확인할 수 있다.

 

매개 변수 위치에는 auto 키워드를 쓸 수 없다. 매개 변수에 auto 키워드를 쓰는 목적을 생각해보면 함수 오버로딩을 안 하고 한 번에 정의하기 위함일 듯하다. 그러한 목적을 충족시켜주는 template 개념이 이미 존재하기 때문에 template을 활용하여 사용하면 되겠다.

 

auto 키워드가 생기면서 function trailing return type이라는 개념으로 프로그래밍이 가능해졌다. 결과가 되는 자료형을 아래처럼 함수 뒤에 적는 것이다. 굳이 적을 필요가 있냐는 생각이 들 수 있지만 추후 다루게 될 template과 사용할 때 효과를 발휘한다. 또한 함수의 결과가 무엇인지 좌에서 우로 순차적인 형태가 되기 때문에 C++이 익숙하지 않은 프로그래머에게 가독성을 높이는 코딩이라 할 수 있겠다.

auto add(int x, int y) -> double;

int main()
{
	using namespace std;

	auto result = add(1, 2);

	return 0;
}

auto add(int x, int y) -> double
{
	return x + (double)y;
}

Reference

최근에 긴 프로젝트를 마치고 결과 보고서를 쓰면서 시간 측정을 많이 했다. visual studio로 시간 측정을 하면 C 기반의 시간 측정 함수를 만들어서 쓰곤 했는데, C++11부터 생긴 chrono 시간 라이브러리를 알게 되었고 이를 사용하여 시간 측정을 하였다. 입무 시간에는 빠르게 만들어서 결과만 뽑으려고 코드 정리를 안 했는데, 이번 기회에 시간 측정 클래스로 구현해서 자주 끌어다 쓸 예정이다!


chrono 라이브러리

 

크로노라고 부르는 이 시간 라이브러리는 크로노스라는 시간의 신에서 유래되었다. C++11이라 나름 세련된? 이름을 가진 라이브러리다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>

using namespace std;

// 시간 측정 클래스 시작
class Timer
{
	using clock_t = std::chrono::high_resolution_clock;
	using second_t = std::chrono::duration<double, std::ratio<1>>;

	std::chrono::time_point<clock_t> start_time = clock_t::now();

public:
	void elapsed()
	{
		std::chrono::time_point<clock_t> end_time = clock_t::now();

		cout << std::chrono::duration_cast<second_t>(end_time - start_time).count() << endl;
	}
}; // 시간 측정 클래스 끝

int main()
{
	random_device rnd_device;
	mt19937 mersenne_engine{ rnd_device() };

	vector<int> vec(10);
	for (unsigned int i = 0; i < vec.size(); ++i)
		vec[i] = i;

	std::shuffle(begin(vec), end(vec), mersenne_engine);

	for (auto& e : vec) cout << e << " ";
	cout << endl;

	Timer timer; // 시간 측정 시작

	std::sort(begin(vec), end(vec));

	timer.elapsed(); // 시간 측정 끝

	for (auto& e : vec) cout << e << " ";
	cout << endl;

	return 0;
}

간단한 정렬 예제를 가지고 왔다. 일반적인 작동 방식은 시간을 측정하고 싶은 곳에 인스턴스를 생성하여 시작 시간을 저장하고 끝나는 부분에서 elapsed 멤버 함수를 불러서 끝 시간을 저장하여 총 걸린 시간을 출력한다.

 

using clock_t = std::chrono::high_resolution_clock;

클래스 가장 첫줄에 위와 같은 명령문을 볼 수 있는데, 최대한 정밀도를 높여서 시간을 측정하기 위함이다. using 사용에 대해 모르겠다면 이전 포스팅 링크를 참고하자!

 

또한 시간 측정은 Release와 Debug에서 차이가 나는데, 프로그램을 개발하면 Release 모드로 배포하니 Release 모드로 측정해서 내 프로그램의 시간을 측정하자! Debug 모드가 시간이 더 오래 걸린다!

현재 Debug 모드이다.


Reference

 

 

<chrono> - C++ Reference

 

www.cplusplus.com

 

 

Chrono in C++ - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

C++를 처음 접하면 using이라는 키워드는 namespace를 코드에서 작성하기 귀찮아서 빼기 위해 사용하는 경우가 많다.

using namespace std;

하지만 typedef와 비교가 되는 사용법이 있어서 이번 포스팅으로 정리하도록 한다.


typedef를 활용한 변수 별칭 만들기

 

typedef를 사용하는 것은 자료형에 대해 별칭을 만들기 위함이다. 예를 들어 double에 대해서 거리라는 의미를 가지도록 하기 위해 사용한다. 이렇게 사용하는 것이 추후 거리를 의미하는 모든 변수가 float 자료형으로 변경되어야 할 때 한 줄만 바꾸면 되기 때문에 유지보수를 위해서도 사용하는 편이다.

typedef double distance_t;

using을 활용한 변수 별칭 만들기

 

using을 사용하는 것도 typedef를 사용하여 별칭 만드는 것과 목적은 같다. 사용하는 방법은 아래와 같다.

using distance_t = double;

typedef와 using의 차이점

 

C/C++를 사용하지 않는 프로그래머에게 코드를 보여주었을 때 using 키워드의 경우 직관적이라 가독성이 좋다. 그리고 가장 큰 차이점은 using 키워드는 template을 지원한다는 점이다.

template<typename T>
using distance_t = T; // 템플릿 사용 가능

template<typename T>
typedef T distance_t; // 템플릿 사용 불가능

C++ 11 이상이고 template을 사용하게 되는 경우 using 키워드를 사용하는 것이 더 좋을 것이다.


Reference

+ Recent posts