• 8.1.1 객체지향 프로그래밍이 아닌 코드의 경우
  • 8.1.2 객체지향 프로그래밍
  • 8.1.3 인스턴스(instance)

8.1.1 객체지향 프로그래밍이 아닌 코드의 경우

 

객체지향 프로그래밍이 아닌 기존의 C/C++ 코드를 보고 객체지향 프로그래밍으로 넘어가 보자. 친구에 대한 다양한 정보(이름, 주소, 나이, 키, 몸무게)를 저장하고 출력하고자 한다. 이럴 경우 가장 가볍게 생각할 수 있는 것이 구조체이다. 단순히 친구 한 명이라면 main 함수에 각 정보를 담을 메모리를 할당하고 출력하면 되겠지만 친구가 여러 명이 생길 수 있을 것이라 생각하자.

#include <iostream>
#include <string>
#include <vector>
using namespace std;


struct Friend
{
	string	name;
	string	address;
	int		age;
	double	height;
	double	weight;

};

void print(const Friend& fr)
{
	cout << fr.name << " " << fr.address << " "
		<< fr.age << " " << fr.height << " " << fr.weight << endl;
}

int main()
{
	Friend jj{ "Jack Jack", "Uptown", 2, 30, 10 };

	print(jj);

	return 0;
}

이처럼 구조체를 하나 만들어서 초기화하고 출력 함수를 구현할 수 있겠다.

 

여기까지만 해도 나름 깔끔하게 잘 만들었지만 print 함수가 너무 지저분해 보인다. 이를 줄이고 싶어서 구조체 안에 print 함수를 넣어서 정리한다.

#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Friend
{
	string	name;
	string	address;
	int		age;
	double	height;
	double	weight;

	void print()
	{
		cout << name << " " << address << " " 
			<< age << " " << height << " " << weight << endl;
	}
};

int main()
{
	Friend jj{ "Jack Jack", "Uptown", 2, 30, 10 };

	jj.print();

	return 0;
}

이제 print 함수의 매개변수도 사라지고 출력 부분에서 구조체 내부의 데이터에 접근하기 위하여 불필요한 코드도 사라졌다. 이 정도면 나름 잘 정리한 것 같은데...?


8.1.2 객체지향 프로그래밍

 

이제 C++에서 말하는 객체지향 프로그래밍의 기본적인 구조를 한번 살펴보자. 데이터와 기능이 묶여 있는 것을 객체라고 말한다. 이런 객체(object)를 프로그래밍으로 구현한 것이 class이다.

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Friend
{
public:
	string	name;
	string	address;
	int		age;
	double	height;
	double	weight;

	void print()
	{
		cout << name << " " << address << " " 
			<< age << " " << height << " " << weight << endl;
	}
};

int main()
{
	Friend jj{ "Jack Jack", "Uptown", 2, 30, 10 };

	jj.print();

	return 0;
}

이전 코드 블럭과의 차이점은 두 가지다. struct 키워드 대신 class 키워드를 썼다는 점, class 시작 부분에 public:이라는 키워드가 붙었다는 점이다.

 

많은 사람들이 처음 클래스를 접하게 되면 구조체로도 비슷하게 구현이 가능한데 굳이 클래스로 구현해야 하는 것인가라는 의문점을 가지게 된다. 클래스의 경우 public이라는 access specifier라는 것 덕분에 많은 추가적인 이점을 가질 수가 있다. 구조체는 기본적으로 public이지만 클래스의 경우 private이나 protected로 설정이 가능하다. 이 부분은 다음 포스팅에서 다루도록 하겠다.

 

일반적으로 프로그래밍을 하면 구조체는 데이터만 담고 클래스에는 데이터와 기능을 함께 담는다.


8.1.3 인스턴스(instance)

 

객체지향 프로그래밍에 대해 다루다 보면 인스턴스와 클래스의 개념이 혼동될 수 있다. 객체는 데이터와 기능을 담아두어서 추상화시킨 구조일 뿐 물리적으로 메모리를 지니고 있는 존재는 아니다. 반면 인스턴스는 물리적 메모리 주소를 할당받은 것이고 구조체가 지닌 데이터나 기능을 사용할 수 있는 존재이다.

int main()
{
	Friend jj{ "Jack Jack", "Uptown", 2, 30, 10 };

	jj.print();

	return 0;
}

이 코드에서 jj가 인스턴스이다. 이처럼 메모리를 가지기 시작하는 것을 instanciation이라고 부른다.

열거형 타입 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 내부에 있는 값들로만 비교할 수 있게 되므로 휴먼 에러를 줄일 수 있는 방법이 되겠다.

  • 4.7.1 열거형 구현 방식 안 좋은 예
  • 4.7.2 Enumerated Types
  • 4.7.3 열거형 타입 사용 시 주의사항

 


4.7.1 열거형 구현 방식 안 좋은 예

 

어떤 특성에 대해 여러 종류로 분류하는 경우가 굉장히 많다. 실무에서도 입자의 성분을 분류하거나 계산 축을 설정하기 위해 정수형으로 나열해서 쓰곤 했다. 그냥 하드코딩으로 된 것도 있으며, 매크로를 사용하는 경우도 많았다. 아래의 예시를 보자.

int computeDamage(int weapon_id)
{
	if (weapon_id == 0) // sword
	{
		return 1;
	}

	if (weapon_id == 1) // hammer
	{
		return 2;
	}

	// ... 이런식으로 구현하면 힘들어진다.
}

무기별로 데미지를 다르게 해서 구현을 하려 했다. 0을 sword로 생각하고 구현했으며, 주석을 달아서 나름 친절하게 구현을 했다고 볼 수 있겠다. 하지만 무기 특성을 데미지 계산뿐만 아니라 다른 곳에서도 쓴다면 계속 위처럼 조건문을 작성해야 하는데 모든 곳에 주석을 달기엔 비효율적이다.


4.7.2 Enumerated Types

 

위와 같은 고민을 해결해줄 수 있는 것이 열거형 타입인 enum이 되겠다. 바로 적용된 버전의 코드를 보자.

enum Weapon
{
	WEAPON_SWORD,
	WEAPON_HAMMER,	// 마지막 쉼표 있어도 된다.
};

int computeDamage(int weapon_id)
{
	if (weapon_id == WEAPON_SWORD)
	{
		return 1;
	}

	if (weapon_id == WEAPON_HAMMER)
	{
		return 2;
	}

	// ...
}

아까보다 주석이 줄었으며, 가독성도 좋아졌다. enum의 경우 직접 수치를 입력하지 않을 경우 0부터 1씩 커지는 숫자가 enum 안에 들어간다. 여기서 WEAPON_SWORD가 0이고 다음이 1이 들어가 있다. enum은 헤더에 넣어두고 include하는 방식으로 사용하며, enum 마지막엔 ;으로 끝맺음을 해줘야 한다.

 

열거형 타입을 반드시 0부터 시작되게 사용하지 않아도 된다. 프로그래머가 값을 입력하면 그 값을 입력한 다음 항목에 대해서는 1씩 증가하게 구현이 된다.

#include <iostream>
#include <typeinfo>

enum Color
{
	COLOR_BLACK = -3,
	COLOR_RED,
	COLOR_BLUE = 5,
	COLOR_GREEN,
	COLOR_SKYBLUE,
};

int main()
{
	using namespace std;

	// 초기화 방식은 다른 타입들과 같다.
	Color paint = COLOR_BLACK;
	Color house(COLOR_BLUE);
	Color apple{ COLOR_RED };

	Color my_color = COLOR_BLACK;

	cout << my_color << " " << COLOR_BLACK << endl;

	return 0;

위의 예시는 순서대로 -3, -2, 5, 6, 7이 할당된다. 이처럼 사용자가 할당할 수는 있지만 권장하지 않는 방식이다.


4.7.3 열거형 타입 사용 시 주의사항

 

하나의 enum에 같은 수를 배정할 수 있지만 의도와는 다르게 작동할 수 있다. 아래의 예시를 수행해보면 조건문이 참으로 되고 두 개가 다른 항목이지만 같다고 판단하는 꼴이 된다.

#include <iostream>

enum Color
{
	COLOR_BLACK,
	COLOR_RED,
	COLOR_BLUE = 5,
	COLOR_GREEN = 5,
	COLOR_SKYBLUE,
};

int main()
{
	using namespace std;

	if (COLOR_BLUE == COLOR_GREEN)
		cout << "equal" << endl;

	return 0;
}

 

다른 enum일지라도 같은 이름을 가지면 안된다. 아래 예시처럼 int 자료형에도 저장이 가능하기 때문에 같은 이름인 BLUE를 저장할 경우 C2365 에러가 생긴다. 만약 같은 이름을 다른 열거형에서 사용하고싶다면 C++11부터 생긴 enum class를 활용하자.

#include <iostream>

enum Color
{
	COLOR_BLACK,
	COLOR_RED,
	COLOR_BLUE,
	COLOR_GREEN,
	COLOR_SKYBLUE,
	BLUE
};
enum Feeling
{
	HAPPY,
	JOY,
	TIRED,
	BLUE,
};

int main()
{
	using namespace std;

	int color_id = BLUE;

	cout << color_id << endl;

	return 0;
}

BLUE가 중복되었다.

 

위에서처럼 int 자료형에 enum을 넣는 것은 가능하지만 반대는 안된다. 아래 주석 처리된 부분은 바로 에러가 뜬다. static_cast를 사용하면 넣을 수 있겠지만 애초에 숫자로 넣는 모호한 방식을 사용하지 않기 위해 사용된 것이 열거형이므로 이런 식으로 사용하는 경우는 없을 것으로 생각된다.

enum Color
{
	COLOR_BLACK,
	COLOR_RED,
	COLOR_BLUE,
	COLOR_GREEN,
	COLOR_SKYBLUE,
};

int main()
{
	using namespace std;

	int color_id = COLOR_BLUE;
	cout << color_id << endl;

	// 그냥 넣는건 안된다. 애초에 안그러기 위해 만들어진게 enum
	//Color test = color_id;
	//Color test = 3;

	Color my_color = static_cast<Color>(3);

	return 0;
}

 

cin으로 바로 enum 자료형에 값을 넣는 것은 안된다. 입력 받으려면 우회해야 한다. 입력을 정수로 받고 해당 정수와 enum의 숫자를 일치하는지 조건을 체크하여 구현하는 방법이 최선일 것이다.

#include <iostream>
#include <typeinfo>

enum Color // user-defined data types
{
	COLOR_BLACK,
	COLOR_RED,
	COLOR_BLUE,
	COLOR_GREEN,
	COLOR_SKYBLUE,
};

int main()
{
	using namespace std;

	Color my_color = static_cast<Color>(3);

	//cin >> my_color;

	// 우회하는 방법
	int in_number;
	cin >> in_number;

	if (in_number == 0) my_color = COLOR_BLACK;
	// ...

	return 0;
}

+ Recent posts