람다 표현식

 

함수의 익명 표기법이다.

일급 함수 및 순수 함수를 만들 때 용이하다.

 

람다식은 크게 캡처 목록 [], 매개변수 목록 (), 본문 {} 3가지로 구성되어있다.

 

간단한 함수를 람다로 표현하기

한 번만 실행되는 한 줄짜리 간단한 함수의 경우 클래스의 멤버 함수나 전역 함수로 만들기보다는 사용되는 곳에 직접 정의하면 가독성 향상 및 최적화될 가능성이 높아진다.

 

std::for_each(std::begin(vehicles), std::end(vehicles), PrintOut); /* 기존 방법 */

std::for_each(std::begin(vehicles),
              std::end(vehicles),
              [](const Vehicle &vehicle) {
                  std::cout << vehicle.GetType << '\n';
              });

 

 

여러 줄의 함수를 람다로 표현하기

한줄 뿐만 아니라 여러줄의 함수도 람다 표현식으로 만들 수 있다.

 

std::for_each(std::begin(vect),
              std::end(vect),
              [](int n) {
                  std::cout << n << " is";
                  if (n < 2) {
                      if (n == 0) std::cout << " not";
                  }
                  else {
                      for (int j = 2; j < n; ++j) {
                          if (n % j == 0) {
                              std::cout << " not";
                              break;
                          }
                      }
                  }
                  std::cout << " prime number" << '\n';
              });

 

 

람다 표현식에서 값 반환

std::transform(std::begin(vect2),
               std::end(vect2),
               begin(vect3),
               [](int n) -> double {
                   return n / 2.0;
               });

 

람다 표현식에서 값 반환시 타입 명시가 필요하다면 매개 변수와 본문 사이에 명시해주면 된다.

명시하지 않으면 컴파일러가 추정한다.

 

 

람다 표현식에서 값 캡처하기

[=](int){ ... }; // 외부 변수 값 복사
[&](int){ ... }; // 외부 변수 참조
[=, &a](int){ ... }; // 외부 변수 값 복사. 단, a만 참조

 

외부 변수를 값 복사로 캡처하면 const화 되기때문에 수정이 불가능하다.

하지만 값을 수정하되 외부에 영향을 미치지 않고 싶다면(call by value) mutable 키워드를 추가하면 된다.

 

int a = 1;
int b = 2;

std::for_each(std::begin(vect),
              std::end(vect),
              [=](int& x) mutable {
                  int old = a;
                  a = b;
                  b = old;
              });

 

덧붙여서 [=], [&] 같은 암시적 캡처의 사용은 절대적으로 지양하는것이 좋다. 필요한 것만 명시적으로 캡처해야한다.

 

 

초기화 캡처

C++14부터 추가된 기능이다.

외부 변수의 값을 캡처해서 람다 표현식의 변수에 할당할 수 있다.

 

int a = 5;
auto myLambda = [&x = a]() { x += 2; }; /* 람다에서 사용하는 x에 a값을 대입한다 */
myLambda();
std::cout << a << '\n'; /* 람다식에 의해 2가 증가했으므로 7이 출력된다 */

 

외부 변수 참조 캡처와 같아보이지만 복사가 불가능한 unique_ptr같은 이동 전용 변수도 캡처하여 람다에서 사용할 수 있다.

 

pNums = std::make_unique<std::vector<int>>(nums);
auto a = [ptr = std::move(pNums)]() { ... }; /* 단순히 pNums를 값으로 캡처하면 오류가 발생한다 */

 

위와 같은 경우는 캡처될 때 move가 사용되었으므로 pNums는 empty가 된다.

 

제너릭 람다 표현식

C++14부터 람다 표현식의 매개변수에 auto 타입이 사용가능해졌다.

 

auto findMax = [](auto &x, auto &y) { return x > y ? x : y; };

 

 

추가로 C++17부터는 캡처 목록에 *this를 사용해서 객체의 복사본을 캡처할 수 있다.

또한 람다 표현식으로 constexpr 객체를 컴파일 타임에 생성할 수 있다.

 

constexpr

객체나 함수 앞에 붙일 수 있는 키워드. 해당 객체나 함수의 반환값을 컴파일 타임에 알 수 있다는 의미를 명시한다.

 

C++ 표준 라이브러리 구현 살펴보기

 

컨테이너 안에 객체 배치하기

std::array는 C++11에 새로 추가된 컨테이너이다.

vector는 가변 배열, array는 고정 배열이라는 차이가 있다.

 

std::array<int, 10> a = { 0, 1, 2, .. };
std::vector<int> v = { 0, 1, 2, .. };

 

둘 다 인덱스로 컨테이너의 특정 요소에 접근할 때는 [] 연산자 대신 at 함수를 사용하는 것이 안전하다.

인덱스가 컨테이너 범위를 벗어날 때, at 함수는 out_of_range 예외를 던지는데 [] 연산자는 아무것도 하지 않기 때문에 미정의 동작이 발생할 수 있다.

 

 

알고리즘 사용하기

배열이나 벡터에 저장된 요소는 표준 라이브러리를 사용하여 정렬이나 특정 값을 찾을 수 있다.

 

◾ 정렬

/* 정렬 */
bool comparer(int a, int b)
{
    return a > b;
}

std::vector<int> vect = { 20, 43, 11, 78, 5, 96 };

std::sort(std::begin(vect), std::end(vect)); /* 기본은 오름차순 정렬 */
std::sort(std::begin(vect), std::end(vect), comparer); /* comparer를 이용해서 내림차순 정렬 */

 

3번째 인자로 비교 함수를 넘겨주면 임의의 조건으로 정렬시킬 수 있다.

 

◾ 탐색

bool TwoWheeled(const Vehicle &vehicle)
{
    return vehicle.GetNumOfWheel() == 2 ? true : false;
}

std::vector<Vehicle> vehicles;
...
auto tw = find_if(std::begin(vehicles), std::end(vehicles), TwoWheeled); // TwoWheeled : 탐색 조건
auto ntw = find_if_notstd::begin(vehicles), std::end(vehicles), TwoWheeled);

 

find_if는 탐색 조건에 부합하는 인자들을 반환하고 find_if_not은 탐색 조건에 부합하는 인자들을 제외하고 반환한다.

 

◾ 순회(루프)

void PrintOut(const Vehicle &vehicle)
{
    std::cout << vehicle.GetType() << '\n';
}

vector<Vehicle> vehicles;
...
std::for_each(std::begin(vehicles), std::end(vehicles), PrintOut);

 

루프를 돌며 컨테이너의 요소를 인자로 넘겨서 필요한 함수를 실행시킬 수도 있다.

모던 C++의 새로운 기능 익히기

 

auto 키워드로 데이터 타입을 자동으로 정의하기

auto a = 1; // int
auto b = 1.0; // double

auto add(int i, int j) // 반환 타입 추론
{
    return i + j;
}

auto add(int j, int j) -> int // 후행 반환 타입
{
    return i + j;
}

 

초기화에 선언된 변수의 실제 타입을 "컴파일 타임"에 추론하기 위해 사용한다.

변수 뿐만 아니라 함수의 반환 타입도 자동으로 추론한다.

반환타입에 auto를 사용하더라도 반환 타입을 지정할수도 있다.

 

함수의 매개변수에 auto 타입을 사용할 수 없지만 람다 함수의 매개변수에는 사용이 가능하다. (클로저는 런타임에 생성되기 때문에 가능한 것으로 보인다.)

 

auto 타입 반환은 C++11부터 가능해졌다.

덧붙여서 C++11에서의 auto 타입 반환에는 반드시 후행 반환 타입이 지정되어야 했지만 C++14부터는 생략이 가능해졌다.

 

 

decltype 키워드로 표현식 타입 질의하기

decltype(declared type, 선언된 형식)은 객체나 표현식의 타입을 "컴파일 타임"에 추출하고 싶을 때 사용한다.

 

const int func1();
const int& func2();
int i;

struct X { double d; };
const X* x = new X();

decltype(func1()) f1; // func1()의 반환 타입을 사용해서 const int f1 선언
decltype(func2()) f2; // func2()의 반환 타입을 사용해서 const int& f2 선언
decltype(i) i1; // i의 타입을 사용해서 int i1 선언

decltype(x->d) d1; // x의 멤버변수 d의 타입을 사용해서 double d1 선언
decltype((x->d)) d2; // 표현식 x->d의 주소 타입을 사용해서 const double& d2 선언

 

auto와 decltype을 조합하면 간결하게 코드를 작성할 수 있다.

 

template<typename I, typename J>
auto add(I i, J j) -> decltype(i + j) // decltype(auto) add(I i, J j) 같은 의미이다
{
    return i + j;
}

 

만약 반환 타입이 auto임에도 후행 반환 타입을 지정하지 않았다면 의도하지 않은 결과가 나올 수 있다.

후행 반환 타입을 지정하지 않는다면 반환 타입을 auto 대신 decltype(auto)로 하는것이 안전하고 간결해진다.

 

참고로 decltype((x->d))처럼 표현식이 lvalue라면 똑같이 lvalue 참조로 보고된다.

 

 

null 포인터

과거부터 사용되던 NULL 매크로를 대체하기 위해 만들어졌다. 모호함이 해결된다.

 

void funct(const char *);
void funct(int);

funct(NULL); // 널 포인터인지 0인지 모호해진다
funct(nullptr); // 확실하게 널 포인터이다

 

 

비멤버 함수 begin()과 end()

본래 begin과 end는 각 컨테이너에서 제공되는 멤버 함수였다. 그래서 배열같은 경우는 인덱스를 이용해서 순회해야 했다.

하지만 C++11부터 비멤버 함수로도 제공된다.

 

int arr[] = {0, 1, 2, ... };
for(unsigned int i = 0; i < sizeof(arr)/sizeof(arr[0]); ++i) // 과거 스타일의 배열 순회
for(auto i = std::begin(arr); i != std::end(arr); ++i) // 비멤버 begin, end 사용

 

더 이상 배열의 길이를 신경쓰지 않아도 되기 때문에 코드를 간결하게 작성할 수 있다.

 

std::vector<int> v;
auto iter1 = v.begin();
auto iter2 = std::begin(v);

 

당연히 둘은 동일한 타입으로 추론된다.

 

 

범위 기반 for 루프로 컬렉션 내 요소 순회

모던 C++은 컬렉션 내 요소를 순회하기 위해 범위 기반 for 루프를 제공한다.

 

int arr[] = { 0, 1, 2, .. };
for(auto a : arr)

+ Recent posts