Programming/C, C++

[C++] 람다(Lambda)가 없던 시절 사용했던 방법 알아보기

변화의 물결1 2025. 7. 11. 01:50

 

 

 

안녕하세요.

 

 오늘날 C++ 개발에서 람다(Lambda) 표현식을 알게 모르게 사용하고 있습니다. 코드를 간결하게 만들고 알고리즘과 함께 사용할 때 가독성을 크게 높여줍니다.

 하지만 람다가 표준으로 채택된 것은 C++11부터입니다.

 

 그렇다면 람다가 없던 C++03 시절에는 비슷한 작업을 어떻게 처리했는지 알아보겠습니다.

 count_if 알고리즘을 예제로 C++ 프로그래밍 기법으로 확인해 보겠습니다.

 


 

1. 가장 전통적인 방식: 함수 포인터 (Function Pointer)

 

 가장 고전적인 방법은 함수 포인터를 알고리즘에 직접 전달하는 것입니다. 특정 조건을 검사하는 전역 함수를 만들고, 이 함수의 주소를 넘겨주는 방식입니다.

 

<코드 예시>

 

#include <stdio.h>
#include <vector>
#include <functional>

// 1. 조건 검사를 위한 전역 함수
bool bLess1(int i) {
    return i < 5;
}
 
void main()
{
 	int data[] = { 1, 2, 3, 4, 5, 6 };

	int iCount = count_if(&data[0], &data[6], bLess1);
	printf("bLess1 count = %d\n", iCount); // 출력: 4
}

 

 

<코드 설명>

 

 count_if 함수는 bLess1 함수의 메모리 주소(포인터)를 받아, data 배열의 각 요소를 순회하며 bLess1을 호출합니다. 조건이 참일 때마다 카운트가 증가합니다.

 간단하지만, 만약 5가 아닌 다른 값과 비교하고 싶다면 새로운 함수를 또 만들어야 하는 유연성의 한계가 있습니다.

 

 

2. 함수 객체 (Function Object / Functor)

 

 함수 포인터의 한계를 극복하기 위해 등장한 것이 바로 함수 객체(Functor)입니다. 클래스에 operator() 연산자를 오버로딩하여, 객체인데도 마치 함수처럼 호출할 수 있게 만드는 기법입니다.

 

// 2. 함수처럼 동작하는 클래스
class CLess2 {
public:
    bool operator()(int left) {
        return left < 5;
    }
};

void main()
{
 	...
	CLess2 cLess2; // 객체 생성
	iCount = count_if(&data[0], &data[6], cLess2);
	printf("cLess2 count = %d\n", iCount); // 출력: 4
}

 

 

<코드 설명>

 

cLess2 객체는 함수가 아니지만 () 연산자 덕분에 count_if 내부에서 함수처럼 호출할 수 있습니다. 함수 객체는 멤버 변수(상태)를 가질 수 있어 함수 포인터보다 훨씬 유연합니다. (예: 생성자로 비교 값을 전달받아 저장) 하지만 위 예제 자체는 여전히 비교 값 5가 코드에 고정되어 있습니다.

 

 

3. std::bind와 함수 객체의 조합하여 유연성 주기

 

 count_if는 인자를 하나만 받는 조건 함수(Unary Predicate)를 요구합니다. 하지만 우리가 만들고 싶은 일반적인 비교 로직은 보통 a < b처럼 인자가 두 개 필요합니다.

 

 C++03 시절, 이 문제를 해결하고 유연성을 극대화하기 위해 사용한 최고의 조합이 바로  std::bind와 일반화된 함수 객체였습니다.

 

<std::bind란?>

 

 std::bind는 이름 그대로 함수의 인자를 묶어주는 도구입니다. 기존 함수의 인자 중 일부를 특정 값으로 미리 채워 넣어, 마치 새로운 함수인 것처럼 만들어 줍니다.

 

  - 인자 개수 조절 : 인자 2개짜리 함수를 인자 1개짜리 함수로 바꿀 수 있습니다.

  - 자리 표시자(std::placeholders::_1): "이 자리는 나중에 채워질 첫 번째 인자를 위한 자리야"라고 표시하는 역할을 합니다.

 

<코드 예시>

  

// 3. 인자를 두 개 받는 일반적인 비교 함수 객체

template<typename T>
class CLess3 {
public:
    bool operator()(const T& left, const T& right) {
        return left < right;
    }
};

void main()
{
	...
	// CLess3의 두 번째 인자를 5로 '묶어서' 인자 1개짜리 함수 객체로 변환
	iCount = count_if(&data[0], &data[6],
    std::bind(CLess3<int>(), std::placeholders::_1, 5));
	printf("cLess3 + bind count = %d\n", iCount); // 출력: 4
}

 

 

<코드 설명>

 

std::bind 처리 부분의 설명입니다.

 

 (1) 인자 두 개를 받는 CLess3 객체를 가져옵니다.

 (2) 두 번째 인자는 5로 고정(bind)합니다.

 (3) 첫 번째 인자는 std::placeholders::_1을 사용해 비워둡니다.

 

결과적으로 std::bind는 count_if`가 사용할 수 있는 "인자 하나를 받아서 그 인자와 5를 비교하는" 새로운 임시 함수 객체를 즉석에서 만들어 전달한 것입니다.

 

 

4. 람다(Lambda) 등장

 

 std::bind가 있었지만, 문법이 복잡하고 placeholders 개념을 이해해야 해서 코드를 읽기 어려웠습니다.

 C++11에서 람다가 등장하며 이 부분의 문제를 해결하게 되었습니다.

 

 std::bind로 길게 작성했던 코드는 람다로 이렇게 표현됩니다.

 

	// 람다를 사용한 가장 현대적인 방식
	iCount = count_if(&data[0], &data[6], [](int i) { return i < 5; });
	printf("lambda count = %d\n", iCount); // 출력: 4

 

 

 std::bind와 함수 객체를 따로 만들 필요 없이, 코드가 필요한 그 위치에 훨씬 직관적이고 간결하게 로직을 표현할 수 있습니다.

 이것이 바로 람다가 등장하며 std::bind의 쓰임새가 크게 줄어든 이유이며, 현대 C++ 개발에 사용하는 이유입니다.

 

 

<전체 코드>

 

#include <stdio.h>
#include <vector>
#include <functional> //for bind

bool bLess1( int i ) //function pointer
{
	return i < 5;
}

class CLess2 //function object
{
public:
	bool operator()(int left) { 
		return left < 5;
	}
};

template<typename T>
class CLess3 
{
public:
	typedef const T	first_argument_type;
	typedef const T	second_argument_type;
	typedef bool	result_type;

	result_type operator() (first_argument_type& _left, second_argument_type& _right) {
		return _left < _right;
	}
};

template<typename T, typename U>
int count_if(T first, T last, U _predicate) 
{
	int ret = 0;

	while (first != last)
	{
		if (_predicate(*first))
		{
			ret++;
		}
		++first;
	}
	return ret;
}

void main() {
	int data[] = { 1,2,3,4,5,6 };
	int iCount = 0;

	iCount = count_if(&data[0], &data[_countof(data)], bLess1);
	printf("bLess1 count =  %d\n", iCount);

	CLess2 cLess2 = CLess2();
	iCount = count_if(&data[0], &data[_countof(data)], cLess2);
	printf("cLess2 count =  %d\n", iCount);
	
	iCount = count_if(&data[0], &data[_countof(data)], std::bind(CLess3<int>(), std::placeholders::_1, 5));
	printf("cLess2 count =  %d\n", iCount);

	iCount = count_if(&data[0], &data[_countof(data)], [](int i) { return i < 5; });
	printf("lambda count =  %d\n", iCount);

}//main

 

 

 

 

 간단히 람다까지 발전하게 된 것을 보았는데 람다는 한번 더 다뤄 보겠습니다.

 

 

 감사합니다.

 

 

<참고 사이트>

1. C++ lambda01 function pointer and function object

https://www.youtube.com/watch?v=cRG-wW5cUdw&list=PLrrTotxaO6khn83BjtBN-1HMDc9MZ__yt&index=20    

 

 

반응형