개선된 함수 기능

yu-podong·2021년 3월 30일
0

CPP

목록 보기
4/9
post-thumbnail

인라인 함수


인라인 함수 = 함수 호출 시 발생하는 오버헤드를 줄이기 위해 함수가 호출되는 곳을 해당 함수의 코드를 복사하여 넣어주는 함수이다.
-> 작은 크기의 함수는 함수 실행 시간 < 호출을 위한 부가적인 시간 오버헤드이기에 이런 함수의 경우 인라인 함수를 사용하는 경우가 많다.

int add(int num) {
	retrun num+2;
}
int main() {
	int num = 3;
    	for(int i =0; i<1000; i++)
    		add(3);		//함수 호출에 따른 엄청난 오버헤드 발생
	return 0;
}
  1. 장점 : 프로그램의 시간이 빨라진다.
  2. 단점 : 코드 삽입으로 인해 컴파인된 코드의 전체 길이가 길어진다.

인라인 함수 사용방법 & 목적

인라인 함수 사용 방법

함수의 선언 or 정의에 inline 키워드를 작성

인라인 함수에 대한 처리

인라인 함수가 호출되는 곳에 해당 인라인 함수 코드를 확장 삽입한다. 매크로와 유사하고, compiler에 의해 코드 확장 후에는 인라인 함수가 사라진다.

목적

자주 호출되는 짧은 코드의 함수 호출에 대한 시간 소모를 줄여서 오버헤드 감소시켜 C++ 프로그램의 실행 속도를 향상시키기 위해 쓰인다.

간단한 사용 방법이지만, 제약사항이 있다.
우선, inline은 컴파일러에세 주는 요구 메세지이므로, 컴파일러가 판단해서 inline 요구를 수용할 지 결정한다. 즉, 컴파일러가 인라인 함수로 쓸 필요가 없다고 느끼면 수행되지 않을 수도 있다.
그 중, recursion, long code function, 반복문, goto문을 가진 함수를 수용하지 않는다.

인라인 함수 vs 매크로 함수

  1. 매크로 함수 : 선행처리기에 의한 문자열 대치 방식이다.
    -> 그러나 매크로 함수는 연산자 우선 순위 문제, 인자의 형 검사를 진행하지 않는다.
  2. 인라인 함수 : compiler에 의한 코드 대치 방식이다.

디폴드 인자


디폴트 인자 : 인자에 값이 넘어오지 않는 경우, 디폴트 값을 받도록 선언된 인자이다.
                  -> param = default value
디폴트 인자를 지정할 때는 함수의 원형에서 지정한다.
지정 방법은 인자형.인자이름 = 설정하고 싶은 default value이고, 함수 정의 방법은 기존 함수 정의방법과 동일하다.

void default_func(int num1, int num2 = 10);
int main() {
	default_func(10,5);	// num1=10, num2=5
    	default_func(20); 	// num1=20, num3=10
}	

디폴트 인자 규칙

1. 디폴트 인자 지정 : 함수의 가장 오른쪽 인자부터 지정해야 한다.

void func(char a, int b, double c);
void func(char a, int b, double c = 2.3);
void func(char a, int b = 4, double c = 2.3);
void func(char a = 'a', int b = 4, double c = 2.3);

2. 함수 인자의 생략 : 함수의 가장 오른쪽부터 생략해 나간다.

void func(char a = 'a', int b = 4, double c = 2.3);
func('b', 5);	//a='b', b=5, c=2.3
func('c');	//a='c', b=4, c=2.3
func();		//a='a', b=4, c=2.3

함수 오버로딩


함수 오버로딩
이름은 같지만, 인자의 개수 or 데이터형이 다른 함수를 여러 번 정의할 수 있는 기능이다. 이는 다형성 특징을 가지고 있다.

이렇게 함수 오버로딩을 사용하여 같은 이름을 갖는 함수를 내용물이 다르도록 여러 번 정의할 수 있다. 함수가 호출될 때는 전달된 인자의 시그니처를 비교하여 compiler가 어떤 함수를 호출할 지 결정한다.
인자의 시그니처 = 인자의 개수나 인자의 데이터 형

함수 오버로딩을 사용하면
   1. 함수 이름을 구분하여 기억할 필요가 없다
   2.함수 호출을 잘못하는 실수를 줄일 수 있다.
즉, 비슷한 알고리즘을 수행하는 함수를 각각 만들 필요가 없어, 효율성을 높혀준다.

함수 오버로딩 시 주의사항

먼저, 정리를 하자면 중복된 함수의 이름은 동일하되 인자의 시그니처가 달라야 한다.
그러나 return 타입은 함수 중복과 무관하다.

1. 인자의 이름만 다른 경우 : 같은 data type, 같은 개수이기 때문에 compiler가 구분 불가능하다.

//function overloading 불가능
int func(int a, int b);
int func(int c, int d);
func(20,10);

2. 함수의 return만 다른 경우 : 마찬가지로 함수 호출 부분에서 return type이 정해진 것이 아니기에 compiler가 구분할 수 없기 때문에, 오버로딩을 할 수 없다.

int func(int a, int b);
int func(float c, char d);
func(20,10);	//함수 호출 부분에서 return type을 알 수 없다.

3. 데이터 형과 해당 형의 레퍼런스 형으로 오버로딩 : 결국 같은 데이터 타입의 인자와 개수이기 때문에 compiler가 구분할 수 없다.

//function overloading 불가능
int foo3(int a, int b); 
int foo3(int &a, int &b); 
int x = 10, y = 20; 
foo3(x, y);

4. typedef로 정의된 데이터 형에 대해 오버로딩 : 위와 같은 이유로 compiler가 구분할 수 없어 오버로딩을 할 수 없다.

typedef unsigned int UINT; 
void foo4(UINT a, UINT b); 
void foo4(unsigned int a, unsigned int b); 
foo4(10, 20);

5. 디폴트 인자에 의해 인자의 개수가 같은 경우 : 오버로딩 실패

void foo5(int a); 
void foo5(int a, int b = 0); 
foo5(10);

디폴트 인자 vs 함수 중복

디폴트 인자 사용은 함수의 처리 과정이 비슷하고 어떤 함수가 특별한 경우로 간주될 때 사용

int GetSum(int x, int y, int z = 0)  { // 디폴트 인자 지정 
	return x + y + z; 
}
int main()   { 
	GetSum(10, 20); // GetSum(10, 20, 0); 호출 
    	GetSum(10, 20, 30); 
}

함수의 오버로딩은 두 함수의 구체적인 알고리즘은 다르지만, 같은 이름으로 표현할 수 있다는 공통점이 있다.

int GetSum(int x, int y) { 
	return x + y; 
}
int GetSum(const int arr[], int size) { 
	int sum = 0; 
    	for(int i = 0 ; i < size ; i++) 
		sum += arr[i]; 
	return sum; 
}

함수 템플릿


함수 오버로딩의 약점은 완전히 동일한 알고리즘인데 type이 다를 경우, 인자의 시그니처를 달리 해서 선언해야 한다. 즉, type만 다를 뿐인 동일한 코드를 여러 개 생성하는 코드 중복 문제가 발생한다.

//type만 다른 동일 알고리즘을 가진 두 개의 함수 탄생
int add(int a, int b) {
	return a+b;
}
double add(double a, double b) {
	return a+b;
}

제네릭와 템플릿

제네릭(Generic)

함수나 클레스를 일반화시키고, 매개 변수 타입을 지정하여 틀에서 찍어내듯이 함수나 클래스 코드를 생산하는 기법이다.
-> 형식만 지정하고, 실제 함수나 클래스는 call될 때 만들어진다.

템플릿

힘수나 클래스를 일반화하는 도구로, template를 이용하여 함수나 클래스를 선언한다.
즉, template 함수/클래스는 변수나 매개변수의 타입만 다르고, 코드 부분은 완전히 동일하다.
✏ 제네릭 타입 : 일반화를 위한 데이터 타입

처리할 데이터의 데이터 형은 다양하지만, 처리 알고리즘은 동일한 함수를 만들 때 유용하다.

template <class T> // or template <typename T>
void myswap (T & a, T & b) 
{ 
	T tmp; 
	tmp = a; 
	a = b; 
	b = tmp; 
} 

함수 템플릿

개발자가 직접 함수를 정의하는 대신, compiler에게 함수의 정의를 알려주는 것이다.
즉, 사용자가 함수 템플릿을 만들면 함수가 호출될 때마다, 사용된 인자의 데이터 형을 검사하여 함수 템플릿으로부터 함수의 정의를 자동으로 생성한다.

그리고 함수가 호출되면 함수 템플릿의 인스턴스화가 진행된다. (= 함수가 실제로 만들어진다.)

템플릿의 인스턴스화

함수 템플릿에서 사용된 범용형 데이터가 어떤 데이터 형인지 알려줌 -> 함수 정의를 생성하는 것이다.
이때, 범용형 데이터 자리에 쓰이는 데이터 형 = 템플릿의 파라미터이다.

1. 암시적 인스턴스화
    : 인자값을 보고 파라미터를 정한다.

cout << GetMax(10, 20) << endl; 	// T = int 
char ch = GetMax('A', 'B'); 		// T = char 
cout << GetMax(3.14, 10.5) << endl; 	// T = double 
cout << GetMax(5, 10.5) << endl; 	// 동일한 데이터 형이 아니라서 컴파일 에러

2. 명시적 인스턴스화
     : 직접 템플릿의 파라미터를 지정한다.

cout << GetMax<int>(5, 10.5) << endl;        // T= int 
cout << GetMax<double>(5, 10.5) << endl;     // T= double 

템플릿의 제네릭 타입에 구체적인 타입을 지정하는 것 = 템플릿으로부터의 구체화(specicalizaion)이라고 한다.
즉, 템플릿의 파라미터를 지정하는 것을 의미한다.
     ex. GetMax(10,20); GetMax<double>(2, 10);

0개의 댓글