1편에서 이어집니다.

문법 정리

가변 길이 템플릿 (Variadic template)

template<typename T>  
int add(T arg) {  
   return arg;  
}  
  
template<typename T, typename... TArgs>  
int add(T arg, TArgs... args) {  
   return arg + add(args...);  
}  
  
int main() {  
   cout << add(1, 2, 3, 4); // 10  
   return 0;  
}

이렇게 재귀적으로 함수를 만들어야 하는 것은 조금 이전 방법이다.

조금 더 발전된 방법은 c++17에서 등장한 fold expression을 사용할 수 있다.

image.png https://modoocode.com/290

template<typename... T>  
int add(T... args) {  
   return (args + ...);  
}

함수 가변인자

템플릿 문법을 쓰지 않고 일반 함수에서 가변인자 문법을 쓰는 방법은 cstdarg 헤더의 va_list, va_start, va_arg, va_end 를 쓰는 것이다.

이 때는 함수가 몇개의 인자를 받았는지 caller가 알려주어야 하고 그걸 보통 첫 인자로 주는것이 관례이다.

int add(int count, ...) {  
   va_list list;  
   va_start(list, count);  
  
   int ret = 0;  
   for (int i = 0; i < count; i++)  
      ret += va_arg(list, int);  
  
   va_end(list);  
   return ret;  
}  
  
int main() {  
   cout << add(3, 1, 2, 3, 4); // 10  
   return 0;  
}

템플릿 메타 프로그래밍 TMP

TMP는 Template Meta Programming의 준말로써 C++의 템플릿이 타입 뿐 아닌 리터럴 자체도 typename으로써 존재한다는 것과 그것이 컴파일 타임에 접근될 수 있다는 점, 리터럴 자체가 다르면 다른 타입으로 취급된다는 점, 구조 내에 static const 변수는 컴파일 타임에 템플릿 리터럴에 접근할 수 있다는 점 등등이 합쳐져서 나온 괴랄한 테크닉이다.

boost같은 곳에서는 TMP가 굉장히 많이 쓰여있다.

TMP의 장점은 컴파일 타임에 모든 것이 계산되어버려 컴파일 시간은 증가할 지 몰라도 극도의 성능 향상이 되기 때문이다.

template<int N>  
struct Factorial {  
   static const ll result = N * Factorial<N - 1>::result;  
};  
  
template<>  
struct Factorial<1ll> {  
   static const ll result = 1;  
};  
  
void solve() {  
   cout << Factorial<14ll>::result;  
}

이것은 알고리즘에선 잘 쓰이지 못한다. 생각해보면 알고리즘 문제에선 input이 동적인데, 그것이 상당히 크다면(1000만 이상) 사실 TMP로 문제를 풀 수 없다는 것을 알 수 있다. 또한 이런 것들을 막기 위한(뚫으려면 어떻게든 되지만) 메모리 제한이나 모듈러 연산, 큰수 연산등이 들어가면 사실 TMP를 써서 문제를 푸는것이 훨씬 어렵다는 것을 알 수 있다.

TMP는 생각보다 문법이 까다롭고 디버깅도 어렵기 때문에 굳이 써야하나 싶을수도 있지만 그 활용성이 무궁무진하고 constexpr을 사용하는 것보다 특정 경우 항상 컴파일 타임 연산을 강제하는 이점이 있다.

또한, c++11에 등장한 type_traits, c++17에 등장한 constexpr조건문, c++20에 등장한 concept까지 모두 사용한다면 굉장한 시너지 효과를 내어 타입을 제한하고 템플릿을 무궁무진하게 쓸 수 있다.

friend 키워드

friend 키워드는 단방향적인 관계로 특정 구조체, 클래스 안에서 다른 구조체, 클래스나 멤버 함수를 friend로 선언하면 그 안에선 private 멤버에 접근할 수 있게 해준다.

즉, friend를 쓰는 쪽의 private를 ~에겐 접근할 수 있는 권한을 부여한다 라고 생각할 수 있다.

class A {
 private:
  void private_func() {}
  int private_num;

  // B 는 A 의 친구!
  friend class B;

  // func 은 A 의 친구!
  friend void func();
};

class B {
 public:
  void b() {
    A a;

    // 비록 private 함수의 필드들이지만 친구이기 때문에 접근 가능하다.
    a.private_func();
    a.private_num = 2;
  }
};

using 키워드

c++11부터 typedef대신 using을 쓸 수 있다.

typedef int myint;
using myint = int;

두 타입이 같은지 검사

템플릿이라면 is_same 을 쓸 수 있다.

객체라면 typeid를 쓸 수 있다.

#include <typeinfo>
#include <type_traits>

if (typeid(a) == typeid(b)) {
    std::cout << "a and b are the same type.";
}

if (std::is_same<decltype(a), decltype(b)>::value) {
    std::cout << "a and b are the same type.";
}

예외 처리 throw, try, catch

아무것이나 던질 수 있지만 다음과 같은 에러를 명시적으로도 던질 수 있다.

이 예외 객체들은 std::exception을 상속받아야한다.

  • std::out_of_range
  • std::overflow_error
  • std::length_error
  • std::runtime_error

등의 에러가 사전 정의되어있다.

throw 가 실행될 때 스택에서의 자원은 초기화되지만 만약 그곳이 생성자라면 따로 그렇지 않으므로 소멸자에서 적절히 자원 해제를 해주어야 한다.

모든 에러를 받고 싶다면

try {
  fn();
} catch (...) {

}

처럼 해준다.

복사 생략

컴파일러는 복사생성자문법이 쓰여도 경우에 따라 복사를 생략할 수 있다.

nullptr

c++11에 추가된 키워드로써 기존의 NULL을 대체한다.

NULL은 단순히 0을 의미하기 때문에 포인터 주소값이 가리키는 곳이 없다 라는 의미의 nullptr을 명시적으로 써줌으로써 정수 타입들간의 암묵적인 변환을 금지하고 컴파일 에러를 낸다.

Categories:

Updated:

Comments