
enum class
일반 enum data를 비교할 때 서로 다른 타입으로 간주되지만 타입 안정성이 부족하여 의도하지 않은 동작을 할 수 있다
enum Animal
{
dog,
cat,
};
enum Color
{
red,
green,
};
Animal A{ dog };
Color C{ red };
if (A == C) //이런 문법이 통과됨
{
}
Animal과 Color는 명백히 다른 타입으로 구분된다, 하지만 컴파일러는 두 값을 비교 시 정수로 변환하고 비교하기 때문에 이러한 문법이 통과된다, 심지어 둘다 0이기 때문에 조건을 true로 넘어가게 됨
이러한 문제를 해결하기에 가장 좋은 방식이 enum대신 enum class를 사용하는것이다
enum class는 암시적 정수 변환이 허용되지 않고 enum data가 enum class 내부에만 존재한다, 외부에서 직접 접근이 불가능함 (클래스::로 접근해야 함)
enum class 이름
{
????,
????,
};
enum class Animal
{
dog,
cat,
};
enum class Color
{
red,
green,
};
Animal anim{ Animal::dog }; //dog로 단순히 접근이 불가능하고 반드시 클래스::로 접근해야 한다
Color color{ Color::red };
if(anim == color) //compile error
enum class는 enum과 다르게 암시적 정수 변환을 허용하지 않는다, 따라서 다른 타입간의 비교를 허용하지 않게 된다
따라서 다음과 같은 코드도 에러를 뱉게 된다
std::cout << Animal::dog << '\n'; //정수 변환 안됨
만약 정수형으로 변환이 필요하다면 다음과 같이 처리한다
//static_cast
static_cast<int>(color); //정수로 변환됨
//std::to_underlying() (C++23)
#include <utility>
std::to_underlying(color); //정수로 변환됨, 명시적 변환이 필요 없음
다른 scope 내부에서 클래스::로 내부 enum data에 접근하는게 아닌 직접적으로 바로 접근하고 싶다면 using enum을 사용하면 된다
enum class Color
{
black,
red,
blue,
};
int main()
{
using enum Color;
black; //ok
red; //ok
}
C++에서는 enum보다 enum class 사용을 더 권장한다 (타입 안전성 향상, 네임스페이스 오염 방지(스코프가 정해져 있기 때문))
struct
하나의 개념을 표현하기 위해 굉장히 여러개의 변수가 필요한 경우가 존재한다, 예를들면 CharacterStat을 만들때 Hp, Mp, exp 등등 필요한 것과 같은 개념이다
이러한 데이터들을 각각 변수로 선언하게 된다면 관리하기 굉장히 까다롭고 그 결과 유지보수성을 낮추는 큰 요인이 된다, 또한 이 변수들이 관련있는 변수라는게 명확하지 않다
이럴때 사용하기 좋은 키워드가 바로 구조체, 클래스이다
struct 구조체란 여러개의 데이터를 하나의 단위로 묶을 수 있는 user-defined type이다 (관련된 데이터를 하나의 단위로 관리한다), 이로서 유지보수성 향상 및 가독성 향상에 도움이 된다
struct, class, union 이 셋을 전부 class type에 속한다
구조체는 다음과 같이 정의가 가능하다
struct 구조체이름
{
변수;
변수;
변수;
};
struct Stat
{
int Hp{ 100 };
int Mp{ 50 };
float Exp{ 30.f };
};
반드시 struct정의 끝에 ;이 붙어야 하고 내부는 각각의 데이터(함수, 변수)들로 구성한다, 이때 각각의 데이터들을 멤버 데이터라고 한다
일반적으로 user-defined type의 이름은 대문자로 많이 시작한다
정의를 했으면 해당 구조체 이름을 type으로 사용한다
Stat s{}; //Stat타입 객체 s를 생성
이러면 s안에 Hp, Mp, Exp 변수가 포함된 것이다
.연산자를 이용하여 구조체 내부 데이터에 접근이 가능하다
s.Hp{ 100 };
s.Mp{ 300 };
기본적으로 구조체 멤버 변수들은 초기화 되지 않는다, 따라서 반드시 초기화를 해주는게 좋다 (초기화 하지 않은 값으로 의도치않은 동작이 발생할 수 있음)
aggregate data type, aggregate initialization
aggregate data type(집계 데이터 타입)은 여러개의 데이터 멤버를 포함할 수 있는 타입을 의미한다
예를들면 배열, 구조체와 같은 타입이 있다
C++에서 이러한 aggregate data type은 굉장히 엄격한 조건을 따르게 된다
이 조건들을 만족하면 구조체는 aggregate data로 간주되고 aggregate initialization을 사용할 수 있게 되는 것이다
쉽게 말하면 aggregate initialization은 집계 초기화로 각 멤버변수를 동시에 초기화 하는 방식이다
struct Stat
{
int Hp;
int Mp{ 200 };
float Exp;
};
int main()
{
Stat stat = {100, 50, 30.f}; //복사 리스트 초기화
Stat stat{ 100, 50, 30.f }; //리스트 초기화 (권장)
}
{ }를 이용하여 멤버 데이터를 한번에 초기화 할 수 있다
이때 초기화 하지 않는 멤버는 자동으로 0이 들어가게 된다 (선언과 동시에 초기화 한 값이 있다면 그 값으로 유지, 단 명시적으로 초기화를 한다면 선언과 동시에 초기화 된 값은 무시된다)
Stat stat{ 100 }; //Hp는 100, Mp는 200, Exp는 0.0이 들어가게 된다
C++20부터는 집계 초기화 시 각 멤버 데이터를 명시적으로 지정해서 초기화 할 수 있다 (Designated Initializer)
이러한 방법은 각 멤버 데이터에 어떤 값이 할당되는지 명확히 표현이 가능하여 가독성이 좋아진다
Stat stat{ .Hp = 100, .Mp = 200, .Exp = 10.f };
따라서 구조체 타입 객체는 선언 시 {}를 뒤에 붙히는게 좋다 (깜빡하고 초기화를 안해도 {}로 모든 멤버가 초기화가 되기 때문)
Stat stat{};
앞서 배운 연산자 오버로딩을 이용하여 내부 데이터를 쉽게 출력도 가능하다
struct Stat
{
int Hp{ 100 };
int Mp{ 200 };
float Exp{ 30.f };
};
// 출력 연산자 오버로딩
std::ostream& operator<<(std::ostream& out, const InStat& s)
{
out << "Hp: " << s.Hp << " Mp: " << s.Mp << " Exp: " << s.Exp;
return out;
}
int main()
{
Stat s{ 100 };
std::cout << s << '\n';
return 0;
}
struct 함수 인자로 사용
마찬가지로 각각의 별개의 변수를 하나하나 함수의 인자로 넘기기에는 불편하고 오류 발생 가능성도 높아진다, 따라서 함수의 인자로 struct를 넘기는것이 좋다
struct Stat
{
int Hp{};
int Mp{};
float Exp{};
};
void foo(const Stat& InStat)
{
std::cout << InStat.Hp << '\n';
}
모든 변수들을 개별로 전달할 필요가 없고 구조체 멤버에 데이터를 맘대로 추가 제거해도 함수의 시그니처에 변함이 없기 때문에 사용성이 증가되고 오류 발생 가능성이 줄어든다
또한 const&로 복사 비용 감소 및 수정 방지로 성능 최적화도 가능하다
임시 구조체
구조체도 한번만 사용할 경우 임시 구조체로 사용할 수 있다
void foo(const Stat& InStat)
{
}
int main()
{
foo(Stat{ 100, 200, 10.f }); // 따로 변수 생성하지 않고 Stat타입 임시객체를 전달
foo({ 100, 200, 300 }); // 컴파일러의 암시적 형변환으로 자동으로 Stat타입 임시객체 생성 후 전달
}
구조체 return
구조체를 인자로 전달할 수 있듯 반환도 가능하다 (여러개의 값을 한번에 return할 수 있다), const나 &는 상황에 맞게 추가해서 사용하자
Stat foo()
{
return Stat{};
}
Stat s{ foo() };