Mordern C++ Lambda

CJB_ny·2022년 8월 30일
0

C++ 정리

목록 보기
82/95
post-thumbnail

Lambda

존나 유용함.

함수 객체를 빠르게 만드는 문법


이런식으로 enum class, class가 있다고 가정을 해보도록 하자.

그리고 벡터에 이 데이터들을 넣으면

복습도 잡깐하자면 지금 오른값 참조로 데이터를 넣어주고있다.

임시 객체이기 때문에...

Lambda

함수 객체를 손쉽게 만드는 문법
람다 자체로 C++11에 '새로운' 기능이 들어간 것은 아니다.

어떤 아이템을 특정조건에 맞게 찾는다고 가정을 해보도록 하자.

지금 < Algorithm > 사용해서 find_if마지막인자에

'함수 포인터'나 '함수 객체'를 만들어서 넣어서 찾을 수가 있었다.

지금은 IsUniqueItem이라는 Functor를 만들어서 넣을 수 있을 것이다.

그래서 62번째 줄의 저러한 경우에

IsUniqueItem이라는 객체 자체(임시객체)를 만들어서 인자로 넣어줄 수 있었다.

이게 '함수객체'와 'STL'의 find_if를 같이 응용한 것이다.

이게 또 반환하는게 iterator일 것인데

auto로 받아주도록 하자. 이런식으로 하면 잘 사용할 수 있었다.

그런데 여기서 문제인 부분이 뭐냐하면은

아이디랑 아이템 타입에 따라서 찾을 수도 있고, 타입이랑 희귀도에 따라서 찾을 수도있음.

함수 객체 문제점 ❓❓❓

그러면 그럴 때 마다 함수객체를 일일히 만들어서 넣어주어야하는

'귀찮은'일이 발생을 한다.

얘는 한번만 쓰고 평생 안 쓸 녀석인데 이렇게 매번마다 적어야한다...

람다 선언

[]만들고 () 만들고 {}만든다.
[]() {} => 이게 lambda의 시작이다.

이게 지금 무명함수를 만든다고 보면은 된다.

( )이 지금 IsItem의 ()에 있는거랑 똑같다고 보면은 된다.

{ } 안에는 구현부가 들어간다.

이렇게 만들어주면 이게 '람다 표현식'이라고한다.

=> 'lambda expression'

이게지금 struct IsUniqueItem이랑 똑같은 상황이라고 보면된다.

람다는 이름을 지어주지 않았는데 컴파일러가 알아서 원하는 이름을 지어주게된다.

또한 반환타입을 명시주지 않았는데 컴파일러가 boolean을 return 하겠거니~ 라고 유추를 한다.

이녀석 자체도 '객체'처럼 만들어 질 수 있다.

lambda 객체라는 것을 알 수 있다.

반환값 변경하고 싶을 때

지금은 컴파일러가

return item._rarity == RArity::Unique를 보고 boolean을 뱉을 것이라고 유추하는데

이게 아니라 다른 반환값을 반환하고싶으면

() 옆에 -> int로 덫붙일 수 있기는 하다.

진짜 일회성이라면은

한줄에다가 다 때려박아도 됨.

Closure

Lambda로 만들어진 객체를 'Closure'라고 한다.

람다에 의해 만들어진 실행시점 객체

함수객체의 장점

어떤 데이터를 저장할 수 있는 용도로 사용할 수 있다고 했다.

이렇게하면 _itemId라는 정보를 가지고 있다.

그리고 나서 operator ()호출할 때(함수 처럼 실행이 될 때)


이런 고정값이 아니라

비교해야할 _itemId 고정값이 아니라 인자로 넘겨준 녀석이랑 비교할 수 있다.

실행이 될때 인자로 넘어온 녀석이랑 비교가 가능하다.

그런데 이것을 람다를 이용해서 표현할 수도 있다.

객체로 만들것이라면 마지막에 ; 붙인다.

그런데 지금 _itemId정보를 모르는 문제 발생.

그래서 람다를 만들 때도 _itemId를 저장할 수 있게 뭔가를 만들어 줘야한다는 것인데

그게 바로 [] 의 역할이다.

기본 캡쳐모드 ❗

캡쳐

capture => '[ ]'

함수 객체 내부에 변수를 저장하는 개념과 유사

94번째 줄에있는 외부에있는 애들 그대로 갖다 쓸 수 있다.

(Functor는 내부에서만 사용할 수 있는데...)

사진을 찰칵[캡처] 하듯.. 일종의 스냅샷을 찍는다고 이해

기본 캡쳐 모드 : 값(복사) 방식(=) 참조 방식(&)

그래서 ' [ ] '에다가 어떤 방식으로 스냅샷찍을지 골랴주어야한다.

외부에서 들어오는 모든 애들을 '값 방식으로 복사'하겠다라는 의미가 된다.

차이점?

방식을 참조 방식으로 바꿔주게되면은

외울려고 하지말고 그냥 상식선에서

이런 느낌으로 이해를 하면 된다.

그러면 실제로 itemId의 값을 복사를 한게 아니라

itemId의 주소값을 참조를 하는 형태이다.

그래서 중간에 값을 바꾸면

10이라는 값이랑 비교를 하게된다.(당연함)

값 방식일 경우

값을 복사를 해서 findByItemLambda라는 객체에 값을 복사를 했기 때문에 제대로 찾아준다.

모든것을 다 확인할 경우

이렇게 삼총사 다 비교해야됨.

그리고 사용할 때는

이렇게 확실하게 명시할 수 있다.

똑같은 방식으로 람다로 만든다고 가정을 하자.

이거 찾는다고 가정 ㄱㄱ.

그러면 이제 Functor사용할라면 설계한다음에

이렇게 인자 하나하나 넣어 줄 수 있다.

그런데 람다로 할경우 구현부 자체를

이렇게 통째로 복사를 한다음에

외부에 있는 변수로 바꿔치기만 해주면된다.

값방식으로 복사를 해서 사용할 수 있다.

변수마다 캡쳐 모드를 지정해서 사용 가능.

현재 비교할 대상인 itemId를 복사방식으로 캡쳐를 하고싶다면

이렇게 넣어주면된다.

참조로 하고싶다면

이렇게 '&'붙인다.

아니면은

나머지애들은 값방식으로 복사하겠다를 이렇게 가능.

기본적인 애들은 복사방식을 사용하고 type에 대해서만 참조로 사용하고 싶을 경우

이런식으로 캡쳐모드를 사용할 수 있다.

근데 C++11에서는 모든 애들에 대해서 캡쳐모드를 사용할 때 모든애들을 복사방식으로 하거나 참조방식으로 사용하는 것을 '지양'해라고 권한다.

그래서 좀 귀찮기는 하지만

이렇게 하나하나 해주면 가독성도 좋아지고 명확해진다.

주의 ❗

그래서 주소값사용할 때는 조심해야하는데

그렇다고 캡쳐모드를 다 복사 방식으로 한다고 해서 문제가 안생기는 것은 아니다!

현재 지금 이런 클래스 만들고 ResetHp를 실행하자마자 hp를 셋팅하는게 아니라

나중에 실행할 수 있게 함수객체를 만들어서 전달해준다고 가정을 하자.

Reset함수의 반환값이 애매할 때는 auto로 설정하고

람다 객체를 하나 만들어 주도록 하자.

그런데 지금 이게 안된다.

그래서 그냥 복사방식으로 해볼까?

해서 막 이래저래 하다가 되니까 대수롭지 않게 넘어갈 수도 있다.

근데이게 지금 Knight클래스에 있는_hp값을 복사를 한게 아니라, 내부적으로

이런 문법이 생략이 되어있다고 보면된다.

내가 간과하고 있던 점 ❗❗❗

어떤 클래스 내부에 있는 함수에서는 항상 this포인터를 이용해서 접근을 하기 때문에...

이렇게 캡처모드에 들어온 변수가 this라는게 생략이 되어있던 것이다.

그래서 아까

이렇게 _hp를 넣어줄려고 해도 아무런 변화가 없던 것이다.

이럴 경우❓

이녀석을 만들어서 외부로 넘겨줄 경우

만든다?? 람다를 만든다?

아무튼 외부에 있다가 Knight가 소멸이 되었다고 하면은 어케됨?

이렇게 동적할당으로 힙에 객체를 만들고나서

k->ResetHpJob()으로 람다 객체를 만들어서 그것을 auto키워드로 job이라는 객체로 받았다고 가정을 하자.

여기서 job은 함수 객체이다 보니까

우리가 원할 때

이런식으로 'job()'은 '함수 객체'이기 때문에 이렇게 실행할 수 있는 '장점'이 있다.

지금 job의 this는 어떤 객체의 주소를 가지고 있는 상태이다.

중요 ❗❗

중간에 이렇게 날려주게 된다면은

this메모리 까면은 이미 값이 날라가있음.

즉 delete하고 호출하면 날라간 메모리를 고치는 엉뚱한 짓 하는거임.

=> 메모리 오염.

클래쉬 안나면 -> 더 끔찍한 상황이다.

그래서 이렇게 명시적으로 this같은거 적어주어야

어? 만약에 동적할당을 받고 delete한다음에 람다를 실행하면 어떻게 되지?

라는 문제를 생각해내서 미리 예방할 수 있다.

이렇게 해놓으면 그냥 _hp값을 복사하는구나~ 라고 생각해서 넘어갈 수도 있는 부분인데...

정리

변수마다 캡쳐모드 지정해서 사용해라.

profile
https://cjbworld.tistory.com/ <- 이사중

0개의 댓글