프로그래밍 지식/C++

C++ 문법 / typedef, using, auto, decltype, constexpr,

게임이 더 좋아 2021. 12. 3. 16:30
반응형
728x170

C++에서 우리가 조금 더 편의성을 가질 수 있게 하는 키워드들을 모아왔다.

알아보자

 

1. typedef

 

typedef를 많이 썼다면 그것은 거의 구조체 만들 때 썼을 것이다.

typedef를 선언해서 별명을 만든 것을 기억하는가?

해당 구조체가 이름으로도, 별명으로도 선언이 되어서 우리가 조금 더 짧게 쓰거나 편하게 호출할 수 있었다.

**typedef는 새로운 자료형을 정의하는 것이 아니라 있던 자료형의 별명이다.

 

 

그러나 typedef는 비단 구조체에만 쓰이는 것이 아니었다.

열거체에도 쓰일 수 있다. 

뿐만 아니라 사용자 정의 타입에도 다 쓰인다.

 

다만 암묵적인 규칙이 있다.

typedef로 변환한 별명들에는 _t라는 접미사가 붙는다.

 

그래서 우리가 document를 볼 떄 _t가 있다면 유심히 보는 것이 좋다.

 

기본적인 모양은

typedef <원래 있는 자료형> <별명>

 

예를 들자면

기존에 있는 자료형의 이름에 별명을 붙여줄 수도 있다.

typedef char int8_t;
typedef int int16_t;
typedef long int32_t;

//숫자는 비트 수를 얘기한다.

 

열거체도 동일하다.

enum ObjectState{
    isOpen,
    isBroken,
    isDone,
    isGone
};

typedef ObjectState stobj_t

...

많이 있다.

 

 

**참고로 using 이용해서도 별명을 만들 수 있다.

using distance_t = double;
//using 은 namespace 할 때만 사용하는 것이 아니다.

 


 

2. using(지시자)

 

보통 네임스페이스 사용 선언 시에 많이 쓰고

위에 typedef 역할도 할 수 있다고 말했다.

 

??? 아니 그럼 using이 기능이 더 많은데.. 굳이 typedef 써야하나??

물론 using으로 다 해도 좋지만..?

내가 속하는 팀에 컨벤션에 맞추는 것이 좋고

둘 다 사용하지 않고 이제 사용할 예정이라면 using을 사용하는 것이 직관적이고 가독성이 더 높다.

 

예를 들면

using namespace std; // 네임스페이스 사용 선언
using Score = vector<int>; // 자료형 별명을 만듬

 

하지만 using을 쓴다고 모든 네임스페이스 문제가 해결이 되는 것은 아니다.

using을 써도 충돌이 생길 수 있는데

이는 현재 영역에 네임스페이스에서 가져온 명칭과 똑같은 명칭이 있어서 충돌이 발생하는 것이다.

 

예를 들어보자

namespace UTIL{
    int value;
    double score;
    void sub() {puts("sub routine"); }
}

int value;

int main()
{
    using UTIL::value;
    int value = 5;
    
    value = 1;
    ::value = 2;
    
}

value라는 이름의 변수가 네임스페이스에 하나, 전역에 하나, 지역에 하나 총 세 개 선언되어 있다.

소속이 달라 선언 자체는 가능하지만 using 선언의 의해 모호함이 발생한다.

using 선언에 의해 UTIL의 value가 main 영역으로 들어오는데 main 지역 변수 value와 이름이 같아진다.

 

즉, UTIL의 value인지 현재 main에 선언된 value인지 구분을 못하게 된다.

다만 ::value는 처음부터 전역에서 선언되었으므로 구분이 가능하다.

 

그리고 네임스페이스가 많아져서 중복이 된다면

별명을 붙여서 충돌을 회피할 수도 있다.

하지만 여기까진 그냥 알아만 두면 되겠다.

 

 


 

3. auto

 

키워드 뜻 그대로 자동으로 무엇인가 해주는데

이것은 컴파일러가 자동으로 타입을 추론해주는 것이다.

컴파일러가 추론하는 기준은 사용하면 초깃값의 형식에 맞추는 것이다.

**double을 의도하면서 auto i = 5라한다면 int로 형식이 맞춰졌다고 해서 화내면 안된다.

우리가 초깃값을 잘못줬기 때문이다.

 

하지만 유용해보이지만

남용하면 가독성을.. 떨어뜨린다.

auto를 쓰는 이유는

복잡한 타입임에도 명확하게 그것임을 알 수 있을 때 쓰는 것이다.(ex.너무 길어서 복잡한 경우)

아무곳이나 auto를 남발하면 디버깅하기 너무 힘들다.

너무 쉬워서 예는 생략하겠다.

 


 

4. decltype

이 친구 또한 auto랑 비슷한 아이다.

이 키워드는 다만 인자로 받은 자료형에 맞춰서 다른 변수의 자료형을 지정하는 형태다.

 

예를 들면

auto x1 = 1;
decltype(x1) x2 = 2;
auto x3 = 3.4;
decltype(x2 + x3) x4 = 5;

 

x1은 int 형으로 지정될 것이고

x2에서 decltype이 x1을 int로 생각하기 떄문에 x2도 int가 된다.

x3는 double 형으로 지정될 것이고

x4에서 decltype이 x2는 int로 x3는 double로 유추하고 더했을 때는 double형이 범위가 크므로 double로 지정된다.

 


 

5. constexpr (C++11 이상) - Constant Expression(상수식) 의 약자

 

앞에 const를 보니 상수가 생각나나?

맞다.

상수를 조금 더 편리하게 이용할 수 있는 키워드다.

 

일반적으로 상수를 선언할 때는 선언과 동시에 값을 정해주어야 한다.

그것이 장점이자 단점인데

constexpr을 쓰게되면 컴파일 시점에 값을 결정하기 때문에 const보단 유연하게 값을 결정할 수 있다.

다시 말하자면 해당 객체나 함수의 리턴값을 컴파일 타임에 값을 알게 된다. 라는 의미다.

** 함수나 객체에 이 키워드를 다 쓸 수 있다.

 

 

배열과 같이 선언과 함께 크기를 지정해야할 때 (고정적이지 않은 크기는 constexpr을 써서 해결할 수 있다.)

 

constexpr int func1()
{
    return  2;
}

constexpr int func2(int x, int y)
{
    return x + y;
}

...


int arr1[func1()];
int arr2[func2(2,3)];

cout << sizeof(arr1) << "  " << sizeof(arr2) << '\n';

...

 

결과는 예상했던 대로

8, 20이 나온다.

 

..? const로는 저게 안되나???

const와 constexpr의 큰 차이를 알아보자

 

일단 const는 나중에 x값이 정해지고 

y에 상수로 선언되어 값이 정해질 것이다.

 

즉, 컴파일할 때 굳이 값을 알아야 할 필요가 없다.

int x;

.....

const int y = x;

 

constexpr을 쓰면??

int x;

....

constexpr int y = x;

 

우리는 constexpr을 쓴다면 컴파일 시간에 알아야 한다고 말했다.

즉, 위 문장은 컴파일 시 값을 알 수가 없어서 틀린 문장이고 에러를 쓰로잉한다.

 

즉, const는 컴파일 시에 알아도 되고 몰라도 되는데

constexpr은 컴파일 시에 알아야 한다.

const가 조금 더 큰 범주라고 생각하면 되겠다.

**다시 말해서 우리가 컴파일 타임에 초기화를 꼭 하고 싶다?? constexpr을 쓰면 된다.

 

 

앞에서 변수가 아니어도 객체나 함수에 이 키워드가 붙는다고 했는데..?

그렇다면 constexpr이 붙은 함수는 뭘까?

 

앞서 말했듯이 해당 키워드면 컴파일 시간에 계산이 된다는 뜻인데

그렇다면 컴파일 시간에 해당 함수를 돌려서 값을 얻어낼 수도 있다고 생각해도 된다.

 

원래는.. 컴파일 시간에 값을 얻지 못했는데..?

얻음으로써 오류가 나지 않는 순간이 있나...?

 

예를 들면

#include <iostream>

int factorial(int N) {
  //N! 을 반환하는 함수
  ...
}

template <int N>
struct A {
  int operator()() { return N; }
};

int main() { A<factorial(5)> a; }

//여기서 컴파일할 때 오류가 발생한다. 해당 함수는 런타임 때 돌아가는데
//해당 객체는 컴파일 시에 만들고 싶다고 선언해놨기 때문이다.
//처음부터 A<120> 이라고 선언하지 않을 것이면
//우리는 constexpr 키워드가 필요하다.

 

하지만 여기서 함수에.. constexpr을 실행해주면

컴파일 시간에 계산이 가능해진다.

즉, 해당 객체를 값을 컴파일 시간에 결정하면서 선언할 수 있게 된다.

 

다만 몇 가지 제약이 있는데 살짝 보고만 가자.

아래 제약을 사용하는 함수는 constexpr 함수로 선언될 수 없다.

 

1. goto 문 사용

2. 예외 처리 (try 문; C++ 20 부터 가능하게 바뀌었습니다.)

3. 리터럴 타입이 아닌 변수의 정의

4. 초기화 되지 않는 변수의 정의

5. 실행 중간에 constexpr 이 아닌 함수를 호출함

 

그렇다면 constexpr을 붙인 함수는 일반함수처럼 쓸 수 없나???

아니다.

그냥 컴파일 할 때도 쓰이고, 일반 런타임에서도 쓰인다.

 

 


 

여기까지 몇가지 유용한 키워드들을 알아봤다.

프로그래밍 언어는 발전할수록 사용자 친화적으로 바뀌니까..

항상 어떻게 바뀌는지 공부해볼 필요는 있다고 생각한다.

 

728x90
반응형
그리드형