묻공러
[MC++] Lambda
묻공러
묻지마공부
묻공러
전체
오늘
어제
  • 분류 전체보기 (494) N
    • C (54)
      • [코드조선] C 핵심 (35)
      • [언어본색] C 기초 (19)
    • C++ (72)
      • [루키스] C++ (9)
      • [루키스] 콜백함수 (6)
      • [루키스] STL (8)
      • [루키스] Modern C++ (11)
      • [노코프] C++ (10)
      • [노코프] Tips (16)
      • [일지] C++ (12)
    • C# (7) N
      • [루키스] C# (7) N
      • [루키스] 자료구조 (0)
      • [루키스] 실전 문법 (0)
    • 자료구조 & 알고리즘 (50)
      • [코드조선] C 자료구조 & 알고리즘 (6)
      • [합격자되기] C++ 코딩테스트 (12)
      • [루키스] C++ 자료구조 & 알고리즘 (32)
    • CS (69)
      • [널널한 개발자] CS 개론 (19)
      • [혼자 공부하는] 컴퓨터 구조 (16)
      • [혼자 공부하는] 운영체제 (18)
      • [널널한 개발자] 네트워크 (16)
    • 게임 그래픽스 (46)
      • [전북대] OpenGL (25)
      • [일지] DirectX (21)
    • 게임 엔진 (124)
      • [코드조선] 언리얼 (53)
      • [코드조선] 언리얼 데디서버 (8)
      • [일지] 언리얼 (59)
      • [일지] 언리얼 (2) (3)
      • 유니티 (1)
    • 게임 서버 (17)
    • 게임 수학 & 물리 (19)
      • 게임 수학 (12)
      • 게임 물리 (7)
    • GIT & GITHUB (4)
    • 영어 (18)
      • [The Outfit] 대본 공부 (11)
      • the others (7)
    • 그 외 (14)
      • In (5)
      • Out (5)
      • Review (4)

인기 글

최근 글

hELLO · Designed By 정상우.
C++/[루키스] Modern C++

[MC++] Lambda

2023. 7. 2. 23:26

함수 객체를 이용한 기존 방식과 람다를 사용하는 방식을

비교하며 차이점과 람다의 장점을 알아보자

 

기존에 함수객체를 이용하는 방식은 아래와 같았다

enum class Rarity
{
	Common,
	Rare,
	Unique,
};

enum class ItemType
{
	None,
	Armor,
	Weapon,
	Jewerly,
	Consumable,
};

class Item
{
public:
	Item()
	{

	}

	Item(int itemId, Rarity rarity, ItemType type)
		: mItemId(itemId)
		, mRarity(rarity)
		, mType(type)
	{

	}

public:
	int mItemId = 0;
	Rarity mRarity = Rarity::Common;
	ItemType mType = ItemType::None;
};

int main()
{
	std::vector<Item> v;
	v.push_back(Item(1, Rarity::Common, ItemType::Weapon));
	v.push_back(Item(1, Rarity::Common, ItemType::Armor));
	v.push_back(Item(1, Rarity::Rare, ItemType::Jewerly));
	v.push_back(Item(1, Rarity::Unique, ItemType::Weapon));

	{
		struct IsUniqueItem
		{
			bool operator()(Item& item)
			{
				return item.mRarity == Rarity::Unique;
			}
		};

		auto findIt = std::find_if(v.begin(), v.end(), IsUniqueItem());
		if (findIt != v.end())
			std::cout << "아이템 ID: " << findIt->mItemId << std::endl;
	}
}

 

lambda를 사용하는 방식은 아래와 같다

enum class Rarity
{
	Common,
	Rare,
	Unique,
};

enum class ItemType
{
	None,
	Armor,
	Weapon,
	Jewerly,
	Consumable,
};

class Item
{
public:
	Item()
	{

	}

	Item(int itemId, Rarity rarity, ItemType type)
		: mItemId(itemId)
		, mRarity(rarity)
		, mType(type)
	{

	}

public:
	int mItemId = 0;
	Rarity mRarity = Rarity::Common;
	ItemType mType = ItemType::None;
};

int main()
{
	std::vector<Item> v;
	v.push_back(Item(1, Rarity::Common, ItemType::Weapon));
	v.push_back(Item(1, Rarity::Common, ItemType::Armor));
	v.push_back(Item(1, Rarity::Rare, ItemType::Jewerly));
	v.push_back(Item(1, Rarity::Unique, ItemType::Weapon));

	{
                // 비교를 위해
		// 함수 객체를 이용한 예전 방식을 작성해두었다
		struct IsUniqueItem
		{
			bool operator()(Item& item)
			{
				return item.mRarity == Rarity::Unique;
			}
		};

		// 람다 표현식을 이용한 클로저(closure)
		// 클로저(closure): 람다에 의해 만들어진 실행시점 객체
		auto IsUniqueLambda = [](Item& item)
		{
			return item.mRarity == Rarity::Unique;
		};

		auto findIt1 = std::find_if(v.begin(), v.end(), IsUniqueLambda);// 한번 거치는 버전
		auto findIt2 = std::find_if(v.begin(), v.end(), [](Item& item) { return item.mRarity == Rarity::Unique;});// 바로 사용하는 버전
		if (findIt1 != v.end())
			std::cout << "아이템 ID: " << findIt1->mItemId << std::endl;
	}
}

 

참고로 return type을 지정하려면 아래와 같이

"-> int"와 같은 형식으로 작성하면 된다

auto IsUniqueLambda = [](Item& item) -> int
		{
			return item.mRarity == Rarity::Unique;
		};

		auto findIt1 = std::find_if(v.begin(), v.end(), IsUniqueLambda);// 한번 거치는 버전
		auto findIt2 = std::find_if(v.begin(), v.end(), [](Item& item) -> int { return item.mRarity == Rarity::Unique;});// 바로 사용하는 버전

 

함수객체에 데이터가 있는 경우에도 람다를 사용할 수 있다

기존에 함수객체에서는 아래와 같이 사용했었다

enum class Rarity
{
	Common,
	Rare,
	Unique,
};

enum class ItemType
{
	None,
	Armor,
	Weapon,
	Jewerly,
	Consumable,
};

class Item
{
public:
	Item()
	{

	}

	Item(int itemId, Rarity rarity, ItemType type)
		: mItemId(itemId)
		, mRarity(rarity)
		, mType(type)
	{

	}

public:
	int mItemId = 0;
	Rarity mRarity = Rarity::Common;
	ItemType mType = ItemType::None;
};

int main()
{
	std::vector<Item> v;
	v.push_back(Item(1, Rarity::Common, ItemType::Weapon));
	v.push_back(Item(1, Rarity::Common, ItemType::Armor));
	v.push_back(Item(1, Rarity::Rare, ItemType::Jewerly));
	v.push_back(Item(1, Rarity::Unique, ItemType::Weapon));

	{
		struct FindItemByItemId
		{
			FindItemByItemId(int itemId)
				: mItemId(itemId)
			{

			}
			bool operator()(Item& item)
			{
				return item.mItemId == mItemId;
			}

			int mItemId;
		};

		int itemId = 4;

		auto findIt = std::find_if(v.begin(), v.end(), FindItemByItemId(itemId));
		if (findIt != v.end())
			std::cout << "아이템 ID: " << findIt->mItemId << std::endl;
	}
}

 

 

위의 상황을 람다를 사용해서 만든다면 아래와 같다

enum class Rarity
{
	Common,
	Rare,
	Unique,
};

enum class ItemType
{
	None,
	Armor,
	Weapon,
	Jewerly,
	Consumable,
};

class Item
{
public:
	Item()
	{

	}

	Item(int itemId, Rarity rarity, ItemType type)
		: mItemId(itemId)
		, mRarity(rarity)
		, mType(type)
	{

	}

public:
	int mItemId = 0;
	Rarity mRarity = Rarity::Common;
	ItemType mType = ItemType::None;
};

int main()
{
	std::vector<Item> v;
	v.push_back(Item(1, Rarity::Common, ItemType::Weapon));
	v.push_back(Item(1, Rarity::Common, ItemType::Armor));
	v.push_back(Item(1, Rarity::Rare, ItemType::Jewerly));
	v.push_back(Item(1, Rarity::Unique, ItemType::Weapon));

	{
		// 비교를 위해 
		// 함수 객체를 이용한 예전 방식을 작성해두었다
		struct FindItemByItemId
		{
			FindItemByItemId(int itemId)
				: mItemId(itemId)
			{

			}
			bool operator()(Item& item)
			{
				return item.mItemId == mItemId;
			}

			int mItemId;
		};

		int itemId = 4;

		// [ ] 캡쳐(capture): 함수 객체 내부에 변수를 저장하는 개념과 유사
		// 기본적으로 
		// 값(복사) 방식으로 하려면 [=]
		// 참조 방식으로 하려면 [&]
		auto FindItemIdLambda = [=](Item& item) { return item.mItemId == itemId; };

		auto findIt = std::find_if(v.begin(), v.end(), FindItemIdLambda);
		if (findIt != v.end())
			std::cout << "아이템 ID: " << findIt->mItemId << std::endl;
	}
}

 

함수객체에 데이터가 여러 개 있는 경우에

람다 없이는 아래와 같이 사용했었다

enum class Rarity
{
	Common,
	Rare,
	Unique,
};

enum class ItemType
{
	None,
	Armor,
	Weapon,
	Jewerly,
	Consumable,
};

class Item
{
public:
	Item()
	{

	}

	Item(int itemId, Rarity rarity, ItemType type)
		: mItemId(itemId)
		, mRarity(rarity)
		, mType(type)
	{

	}

public:
	int mItemId = 0;
	Rarity mRarity = Rarity::Common;
	ItemType mType = ItemType::None;
};

int main()
{
	std::vector<Item> v;
	v.push_back(Item(1, Rarity::Common, ItemType::Weapon));
	v.push_back(Item(1, Rarity::Common, ItemType::Armor));
	v.push_back(Item(1, Rarity::Rare, ItemType::Jewerly));
	v.push_back(Item(1, Rarity::Unique, ItemType::Weapon));

	{
		// 함수 객체를 이용한 예전 방식
		struct FindItem
		{
			FindItem(int itemId, Rarity rarity, ItemType type)
				: mItemId(itemId)
				, mRarity(rarity)
				, mType(type)
			{

			}
			bool operator()(Item& item)
			{
				return item.mItemId == mItemId && item.mRarity == mRarity && item.mType == mType;
			}

			int mItemId;
			Rarity mRarity;
			ItemType mType;
		};

		auto findIt = std::find_if(v.begin(), v.end(), FindItem(4, Rarity::Unique, ItemType::Weapon));
		if (findIt != v.end())
			std::cout << "아이템 ID: " << findIt->mItemId << std::endl;
	}
}

 

위와 같이 여러 데이터를 가지는 경우 람다를 사용하면 

아래처럼 매우 편하게 사용 가능하다

enum class Rarity
{
	Common,
	Rare,
	Unique,
};

enum class ItemType
{
	None,
	Armor,
	Weapon,
	Jewerly,
	Consumable,
};

class Item
{
public:
	Item()
	{

	}

	Item(int itemId, Rarity rarity, ItemType type)
		: mItemId(itemId)
		, mRarity(rarity)
		, mType(type)
	{

	}

public:
	int mItemId = 0;
	Rarity mRarity = Rarity::Common;
	ItemType mType = ItemType::None;
};

int main()
{
	std::vector<Item> v;
	v.push_back(Item(1, Rarity::Common, ItemType::Weapon));
	v.push_back(Item(1, Rarity::Common, ItemType::Armor));
	v.push_back(Item(1, Rarity::Rare, ItemType::Jewerly));
	v.push_back(Item(1, Rarity::Unique, ItemType::Weapon));

	{
		int itemId = 4;
		Rarity rarity = Rarity::Unique;
		ItemType type = ItemType::Weapon;

		auto findItemLambda = [=](Item& item)
		{
			return item.mItemId == itemId && item.mRarity == rarity && item.mType == type;
		};

		auto findIt = std::find_if(v.begin(), v.end(), findItemLambda);
		if (findIt != v.end())
			std::cout << "아이템 ID: " << findIt->mItemId << std::endl;
	}
}

 

람다의 변수마다 캡처 모드를 지정해서 사용 가능하며 

아래처럼 다양하게 사용가능하다

auto findItemLambda = [=](Item& item)// 모든 변수 값(복사) 방식
{
	return item.mItemId == itemId && item.mRarity == rarity && item.mType == type;
};

auto findItemLambda2 = [&](Item& item)// 모든 변수 참조 방식
{
	return item.mItemId == itemId && item.mRarity == rarity && item.mType == type;
};

auto findItemLambda3 = [=, &type](Item& item)// 기본적으로 값(복사) 방식 사용하고 type만 참조 방식
{
	return item.mItemId == itemId && item.mRarity == rarity && item.mType == type;
};

auto findItemLambda4 = [&itemId, rarity, &type](Item& item)// 하나 하나씩 지정해주는 방식
{
	return item.mItemId == itemId && item.mRarity == rarity && item.mType == type;
};

// 위와 같은 하나씩 지정해주는 방식을 지향하는 것이 좋다
// 가독성, 어떤 방식을 쓰는지 알고 있어야 문제 발생 확률을 줄임
// 특히, 참조 방식은 매우 위험하기 때문이다

 

물론, 값 복사 방식이더라도 위험이 발생할 가능성은 있다

아래를 통해 알아보자

class Knight
	{
	public:
		auto ResetHpJob()
		{
			auto f = [=]()
			{
				mHp = 200;
			};

			return f;
		}

	public:
		int mHp = 100;
	};

	Knight* k = new Knight();
	auto job = k->ResetHpJob();
	delete k;// delete를 해놓고
	job();// 람다로 만들어진 함수 객체를 실행해버리는 대참사 발생

 

혹시나 위의 예시에서 Knight의 함수 객체가 이해가 안 된다면

C++의 콜백함수와 함수 객체에 대해 공부하고 오는 것이 좋다

간단하게 방금 위의 예시에서 만든 함수 객체는 아래와 같은 의미이다

class Functor
	{
	public:
		Functor(Knight* k)// 주소를 받아놓고
			: mKnight(k)
		{

		}

		void operator()()// ()연산자를 실행하는 것
		{
			mKnight->mHp = 200;
		}

	public:
		Knight* mKnight;
	};

 

다시 돌아와서,

[&], [=]처럼 작성을 해두면 우리가 파악하기 힘들고

어떤 결과를 초래할지 예측하기 힘들어지기 때문에

의도적으로 [ ] 안에 다양한 변수들을 하나씩 다 캡처 모드를 작성해 주거나

아래의 예시처럼 작성해 두는 것이 좋다

class Knight
	{
	public:
		auto ResetHpJob()
		{
			auto f = [this]()// this를 의도적으로 작성
			{
				//mHp = 200;
				this->mHp = 200;// 멤버변수에 접근할 때, this 포인터는 생략되어 있지만 의도적으로 작성
			};

			return f;
		}

	public:
		int mHp = 100;
	};
    
	Knight* k = new Knight();
	auto job = k->ResetHpJob();
	delete k;
	job();
    // 위에서 의도적으로 this를 작성해둠으로써
    // delete 되었는데 함수 객체를 사용하는 것이 위험하다는 사실을 인지하도록 만드는 것이 좋다
    // 물론 안전장치의 역할은 아니다 벽에 붙여진 안전 수칙 같은 느낌이다
저작자표시 비영리 변경금지 (새창열림)

'C++ > [루키스] Modern C++' 카테고리의 다른 글

[MC++] 스마트 포인터 (Smart pointer)  (0) 2023.07.03
[MC++] 전달 참조 (Forwarding reference)  (0) 2023.07.02
[MC++] 오른 값 참조 (Rvalue reference)와 std::move  (0) 2023.07.02
[MC++] override, final  (0) 2023.07.01
[MC++] delete  (0) 2023.07.01
'C++/[루키스] Modern C++' 카테고리의 다른 글
  • [MC++] 스마트 포인터 (Smart pointer)
  • [MC++] 전달 참조 (Forwarding reference)
  • [MC++] 오른 값 참조 (Rvalue reference)와 std::move
  • [MC++] override, final
묻공러
묻공러
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.