C++ Functional Programming

LeemHyungJun·2022년 9월 21일
0

C++ Study

목록 보기
7/12
post-thumbnail

Functional Programming Introduction

  • C++ => OOP + High Performance
    -> high performance를 위해서 function programming 지원(C++11의 lambda expression)
    -> functional programing은 함수를 변수처럼 다루는 것
    -> call-back, state 등이 없어서 side effect가 없다
  • javascript 예시 (functional programming이 발전한 언어)
    -> 해당 구현의 장점은 plus3이라는 함수는 "오직" 3을 더하는 용도로만 쓸 수 있고, 내부 변수에는 접근이 불가능 하다.
    -> 함수 자체를 다른 함수에게 넘겨줄 수 있는 기능이 가능하다.
  • C++에서 javascript 예시와 유사하게 구현한 예시
    -> function object를 이용
#include<iostream>

class Plus
{
public:
	explicit Plus(int a)
		:localVar{ a }
	{}
	int operator() (int x)const
	{
		return localVar + x;
	}
private:
	int localVar;
};

int main()
{
	Plus plus3{ 3 }; //function object
	Plus plus5{ 5 };

	std::cout << plus3(10) << std::endl; //13
	std::cout << plus5(10) << std::endl; //15

	return 0;
}

Lambda Expression

  • function object와 lambda function은 어셈블리 코드로 분석하면 같은 동작을 수행한다.(같은 메모리 공간 차지)
  • lambda function : [capture](paramter){body} 형식으로 구성
#include<iostream>

class Plus
{
public:
	explicit Plus(int a)
		:localVar{ a }
	{}
	int operator() (int x)const
	{
		return localVar + x;
	}
private:
	int localVar;
};

int main()
{
	Plus plus3{ 3 }; //function object

	auto lambdaPlus3 = 
		[localVar = 3](int x) //lambda function
	{
		return localVar + x;
	};
}
  • capture 모드의 종류
    • capture by value
      -> lambda function 안에서 쓰는 variable이나 object를 value로 캡쳐
      -> deep copy
    • capture by reference
      -> capture하려는 값이 너무 큰 경우 reference를 이용하면 효율적이다.
    • capture by this
	int three{ 3 }; // local variable
	auto lambdaPlus3 = [three](int x) // capture by value
	{
		return x + three; //three : value로 capture(복사)
	};

	VERYLARGEOBJECT object;
	auto lambdaLarge = [&object](int x) // capture by reference
	{
		return x + object;
	};
  • = 와 & 를 이용해서 자동적으로 캡쳐해주는 기능
    -> capture할 값이 너무 복잡하거나 많은 경우가 아니라면 explicit하게 적어주는 방식이 좋다.
	auto lambda1 = [=](int x)  //value
	{
		return x + three;
	};

	auto lambda2 = [&](int x) //reference
	{
		return x + object;
	};

Lambda This

  • lambda expression function body안에서는 바깥 스코프로는 접근 불가능하다.
  • automatic reference capture를 이용하면 접근 가능해진다.
    • automatic reference capture : [&]()
    • automatic reference capture의 경우 lambda function body안에서 member variable, member function을 call 한다면 컴파일러가 알아서 this로 캡쳐를 한다. [this]()
#include<iostream>

class Cat
{
public:
	explicit Cat(int age)
		:mAge{age}
	{}
	void speak() const
	{
		std::cout << "CAT" << std::endl;
	}
	void test() const
	{
		auto lambda = []()
		{
			std::cout << "lambdaFunction" << std::endl;
			std::cout << mAge << std::endl; //컴파일 에러
			speak(); //컴파일 에러
		};
		lambda();
	}
private:
	int mAge;
};

int main()
{
	Cat kitty{ 1 };
	kitty.test();

	return 0;
}
  • this 캡쳐로 동작하는 방식
    -> 위의 main 함수의 동작이 문제없이 동작한다.
auto lambda = [&]() // auto lambda = [this]()와 같은 의미
{
	std::cout << "lambdaFunction" << std::endl;
	std::cout << this->mAge << std::endl;
	this->speak();
};
lambda();

Lambda expression + STL

  • for_each()를 사용한 예시
#include<iostream>
#include<vector>
#include<algorithm>

int main()
{
	std::vector<int>nums{ 1,2,3,4,5,6,7,8,9,10 };

	auto lambdaAdd10 = [](int& n)
	{
		n += 10;
	};
	
	int n = 10;
	lambdaAdd10(n);
	std::cout << n << std::endl; //20
	
	//for_each
	std::for_each(nums.begin(), nums.end(), lambdaAdd10);

	for (int num : nums)
	{
		std::cout << num << " ";
	}
	std::cout << std::endl;

	return 0;
}
  • lambda function과 for_each() 함수를 합쳐서 표현하는 방식
std::for_each(nums.begin(), nums.end(), [](int& n)
{
	n += 10;
});

Higher order function

  • Higher order function : 함수를 argument로 받는 혹은 함수를 결과로 내는 function
  • 사용한 STL 함수는 차후에 정리할 예정
#include<iostream>
#include<algorithm>
#include<vector>
#include<numeric>

int main()
{
	std::vector<int> nums{ 1,2,3,4,5,6,7,8,9,10 };

	//========================================================
	//홀수 걸러줌
	auto filterOdd = [](int n)
	{
		return n % 2 == 1;
	};
	nums.erase(std::remove_if(nums.begin(), nums.end(), filterOdd),nums.end());

	/*nums.erase(std::remove_if(nums.begin(), nums.end(), [](int n)
		{
			return n % 2 == 1;
		}), nums.end());*/
	//========================================================
	//내림차순 sorting 
	std::sort(nums.begin(), nums.end(), [](int a, int b)
		{
			return a > b;
		});
	//========================================================
	//5에 가까운 수부터 sorting
	std::sort(nums.begin(), nums.end(), [](int a, int b)
		{
			return std::abs(a - 5) < std::abs(b - 5);
		});
	//========================================================
	//reduce 이용해서 합 구현
	int sum = std::reduce(nums.begin(), nums.end(), 0, [](int a, int b)
	{
		return a + b;
	});

	std::cout << sum << std::endl;
	//========================================================
	//reduce -> 곱 구현
	int multi = std::reduce(nums.begin(), nums.end(), 1, [](int a, int b)
		{
			return a * b;
		});

	std::cout << multi << std::endl;
	//========================================================
}

std::function

  • functionObj와 lambda function은 변수처럼 가리킬 수 있다.
  • freeFunction은 함수처럼 호출은 가능하지만 변수처럼 가리킬 수 없다.
    -> function pointer 와 std::function을 통해서 구현
#include<iostream>
#include<functional>
#include<vector>

class FunctionObj
{
public:
	void operator() (int i)
	{
		std::cout << "functionObj " << i << std::endl;
	}
};

void freeFunction(int i)
{
	std::cout << "freefunction " << i << std::endl;
}

int main()
{
	//일반 function
	freeFunction(10); //freefunction 10 출력
    
    //function object
    FunctionObj functionObj;
    functionObj(10); //functionObj 10 출력
    
    //lambda function
	auto lambdaFunc = [](int i)
	{
		std::cout << "lambda function " << i << std::endl;
	};
	lambdaFunc(10); //lambdaFunc 10 출력
    
    return 0;
}
  • function pointer
    -> function pointer를 통해 일반 function도 argument로 넣을 수 있다.
...
void runFunction(int i, void (*fnPtr)(int))
{
	(*fnPtr)(i);
}

...

int main()
{
	void(*fnPtr)(int);
    fnPtr = freeFunction();
    (*fnPtr)(20); // freeFunction 20 출력
}
  • std::function
    -> #include <functional> 필요
    -> function pointer, function object, lambda function등 모두를 넣을 수 있다.
    -> 함수를 object처럼 쓸 수 있다.
...
void runFunctionBetter(int i, const std::function<void(int)> &fn)
{
	fn(i);
}

void runFunctions(const std::vector<std::function<void(int)>>& functions)
{
	int i = 0;
	for (const auto& fn : functions)
	{
		fn(++i);
	}
}
...
int main()
{
	void(*fnPtr)(int);
	fnPtr = freeFunction;
	runFunctionBetter(10, fnPtr); //freeFunction 10 출력
    runFunctionBetter(20, functionObj);//functionObj 20 출력
	runFunctionBetter(30, lambdaFunc);//lambdaFunc 30 출력

	std::vector<std::function<void(int)>> functions;
	functions.emplace_back(freeFunction);
	functions.emplace_back(functionObj);
	functions.emplace_back(lambdaFunc);
	runFunctions(functions);
    
	return 0;
}

0개의 댓글