TIL 2024/9/5

Sung Joo Lee·2024년 9월 5일

Generic Programming

제네릭 프로그래밍

  • 타입에 관계없이 동작하는 “일반적인” 코드를 작성하는 방법
    • 여러가지 덧셈을 수행하는 함수들을 작성할 때, 기존에는 아래와 같은 함수들을 개별적으로 구현해야 했음
    • add(int , int) / add(double, double) / add(Point , Point)
    • 이를 한 번의 코드 작성으로 가능하게 하는 방법제네릭 프로그래밍 기법
  • 제너릭 프로그래밍의 구현 방법
    • 매크로 사용 ← 주의가 필요함!
    • 함수/클래스 템플릿 사용

매크로를 사용한 제네릭 프로그래밍

  • 매크로
    • #define

      • 코드의 단순 대체( 복붙 )

        #define MAX_SIZE 100
        #define PI 3.14159
    • 전처리 단계에서 대체된다.

      #define PI 3.141592 //전처리 단계에서 대체해줌
      
      #include <iostream>
      
      int main()
      {
      	double a = PI * 2; // 3.141592 * 2 로 대체
      	double b = PI * 4; // 3.141592 * 4 로 대체
      
      	return 0;
      }

Generic 하지 않은 프로그래밍

  • double, char 타입으로 확장하려면 추가적인 구현이 필요
    • (암시적 형 변환을 통해 추가 구현하지 않아도 가능한 경우도 있으나 제한적)
#include <iostream>

int max(int a, int b) 
{
	return (a > b) ? a : b;//3항 연산자
}

double max(double a, double b)
{
	return (a > b) ? a : b;//3항 연산자

}

int main()
{
	int x = 100;
	int y = 200;
	
	std::cout << max(x, y);
		
	double z = 1.234;
	double w = 3.456;

	std::cout << max(z, w);//double 반환 max함수 호출

	return 0;
}

이를 전부 구현하지 않고 매크로를 사용하여 Generic하게 만들고 싶다. 즉, 한번만 정의하면 모든 것에 다 사용하고 싶다.

적용 코드

#include <iostream>

#define Max(a,b) ((a>b)? a:b)

int main()
{
	int x = 100;
	int y = 200;
	
	std::cout << Max(x, y);
		
	double z = 1.234;
	double w = 3.456;

	std::cout << Max(z, w);//double 반환 max함수 호출

	return 0;
}

전처리기를 통해서 본문의 Max(a,b) 형식의 코드가 ((a>b)? a:b)로 대체가 된다.

💡

즉, 함수없이 본문에 코드로 대체가 되어 오류가 발생 하지 않게된다.

매크로 사용할 때의 유의사항

  • 코드가 단순 대체됨에 유의해야 함
  • 매크로문을 괄호로 감싸는 것이 안전
  • 자주 사용하면 좋지 않음
    1. 디버깅이 너무 어려움
    2. 매크로는 namespace가 없어서 전역범위로 적용이 되어서 예기치 못한 상황에서 코드가 대체되어 ERR를 발생시킬 수 있음.
#define Sqr(a) a*a

result = Sqr(5);
result = 5*5;

result = 100/Sqr(5);
result = 100/5*5; // 내가 의도한 계산값이 나오지 않음

템플릿을 활용한 Generic Programming

  • “설계도” 개념

  • 함수 템플릿과 클래스 템플릿 구현을 지원

  • 어떤 데이터 타입이든, 컴파일러가 적절한 함수/클래스를 설계도를 기반으로 생성

    • 다른 언어의 경우 런타임에 생성하는 경우도 있음
    • 사용하지 않는 경우 생성하지 않음
  • 제네릭 프로그래밍 / 메타 프로그래밍

즉, 데이터 타입에 따라서 함수를 각각 구현하는 것이 아니라 템플릿으로 코드를 구현해 놓으면 컴파일러가 알아서 생성함

템플릿을 사용한 max 함수의 구현

  1. 타입명을 T로 대체
  2. T가 템플릿 인수라는 것을 명시
    1. template or template
    2. (꼭 T를 써야 하는 것은 아니지만 일반적으로 많은 개발자들이 T를 우선적으로 사용함)
int max(int a, int b)
{
		return (a > b)?a:b;
}

//위의 코드를 아래와 같이 template로 구현

template <typename T>
T max(T a, T b)
{
		return (a>b)?a:b;
}

컴파일러가 볼 설계도!

사용 코드

#include <iostream>

template <typename T>//이후에 나오는 T라는 문자는 typename이다.
T max(T a, T b)
{
	return (a > b) ? a : b;
}

int main()
{
	int a = 10;
	int b = 20;
	std::cout << max<int>(a, b) << std::endl;//<>안에 type의 이름을 적어줘야함,그래야 컴파일러가 생성해줌

	double c = 1.234;
	double d = 3.456;
	std::cout << max<double>(c, d) << std::endl;
}

컴파일러가 <>안의 type을 보고 T에 넣어서 함수를 생성해준다. 이 때 템플릿 함수만 존재 할 경우 아무 함수도 생성되지 않는다. 즉, 정의한 템플릿 함수를 사용해야 사용이 된다.

max<int>(a,b);

max(a,b);//생략이 가능하다. 쓰는게 좋다.

우리가 정의한 type에 대해서 사용하고 싶을 때

Point 객체에 대해서 적용을 해보자!

	Point p1;
	Point p2;

	Max<Point>(p1, p2);//오버로딩을 해줘야 가능함
  • main에 다음과 같이 선언하면 컴파일러는 템플릿을 보고 Point type을 넣어서 함수를 만들지만
Point Max(Point a, Point b)//
{
	return (a > b) ? a : b;// > 연산 불가능 -> 오버로딩 필요
}
  • Point 객체는 크기를 비교하는 것이 불가능 하다
  • 우리는 이를 overloading하여 직접 구현해 주어야 한다.

구현 코드

#include <iostream>

class Point {
public:
	bool operator>(const Point& rhs) {
		return true;
	}
};

template <typename T>
T Max(T a, T b)
{
	return (a > b) ? a : b;
}

int main()
{
	int a = 4;
	int b = 5;

	std::cout << Max<int>(a, b) << std::endl;

	double c = 5.8;
	double d = 5.7;
	std::cout << Max<double>(c, d) << std::endl;

	Point p1;
	Point p2;

	Max<Point>(p1, p2);//오버로딩을 해줘야 가능함

	return 0;
}
profile
개발로그

0개의 댓글