C++

C++ 인라인 함수,템플릿

Ta_m 2025. 9. 26. 21:43

1. 인라인 함수 

   함수 호출 시 발생하는 오버헤드를 줄이기 위해서 함수를 호출하는 대신 함수가 호출되는 곳마다

   함수의 코드를 복사하여 넣어주는 특별한 함수

#include <iostream>
using namespace std;

// 홀수인지 판별하는 함수
int odd(int x) {
    return (x % 2);
}

int main() {
    int sum = 0;

    // 1에서 10000까지의 홀수의 합 계산
    for (int i = 1; i <= 10000; i++) {
        if (odd(i)) {
            sum += i;
        }
    }

    cout << "1부터 10000까지 홀수의 합 = " << sum << endl;

    return 0;
}

 

함수 호출의 숨겨진 비용, 오버헤드

for 루프 등에서 odd()와 같이 아주 작은 함수를 수만 번 호출하면, 실제 함수 내용이 실행되는 시간보다 함수를 호출하고 돌아오는 과정에 드는 부가적인 시간이 더 커지는 비효율이 발생합니다.

문제점: 배보다 배꼽이 더 크다! 😥 작은 크기의 함수를 반복 호출하면, 함수 실행 시간에 비해 호출을 위해 소요되는 오버헤드가 상대적으로 매우 커집니다.


✨ 해답: 인라인 함수 (Inline Function)

인라인(inline) 함수는 이러한 오버헤드를 줄이기 위한 해결책입니다.

일반 함수처럼 별도의 공간으로 이동하여 실행되는 것이 아니라, 컴파일 시점에 함수를 호출하는 곳에 함수 코드가 그대로 복사되어 삽입됩니다. 이 덕분에 함수 호출에 따른 시간 소모가 완전히 사라집니다.

동작 방식

  • 일반 함수: 함수 호출 → 함수 코드로 점프 → 실행 → 원래 위치로 복귀
  • 인라인 함수: 컴파일러가 함수 호출 부분을 함수 코드로 대체 → 함수 호출 과정 없이 바로 실행

매크로(#define)와 유사점

이러한 방식은 컴파일 전에 코드를 치환하는 **매크로(macro)**와 매우 유사합니다. 하지만 인라인 함수는 일반 함수처럼 타입 검사 등이 가능하여 매크로보다 훨씬 안전하고 권장되는 방식입니다.

 

예시))

#include <iostream>
using namespace std;
inline int odd(int x) {
 return (x%2);
}
int main() {
 int sum = 0;
 for(int i=1; i<=10000; i++) {
 if(odd(i)) sum += i;
 }
 cout << sum;
}

이런 식으로 사용하면 if(odd(i)) sum += i; 여기에 odd(i) 대신 i%2가 들어가게 된다.

 

디폴트 인자 

인자에 값이 넘어오지 않는 경우, 디폴트 값을 받도록 선언된 인자 : ‘인자 = 디폴트값’ 형태로 선언

 

void default_sample(char c, int i, double d = 0.5 ); // 함수의 선언부
void main() {
 default_sample (‘X', 10);
default_sample (‘Y', 30, 2.0);
}

 위 코드처럼 double d=0.5 디폴트 인자를 함수의 선언부에 지정하는 것이다. 

 

 

 

void foo(char c = 'A', int i = 10, double d = 0.5);
foo('A', 20); // 세 번째 인자만 생략함
foo('B'); // 두 번째, 세 번째 인자만 생략함
foo(); // 인자 모두를 생략함

함수로 쓸 때 가장 오른쪽 인자부터 생략해야 한다.

#include <iostream>
using namespace std;
enum INT_TYPE {DECIMAL, OCTAL, HEXADECIMAL};
void PrintArray(const int arr[], int size = 5, INT_TYPE type = DECIMAL);
int main() {
 int arr1[] = {10, 20, 30, 40, 50};
 int arr2[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
 PrintArray(arr1);
 PrintArray(arr1, 5, HEXADECIMAL);
 PrintArray(arr2);
 PrintArray(arr2, 10);
 return 0;
}
void PrintArray(const int arr[], int size, INT_TYPE type) {
 cout.setf(ios::showbase);
 for(int i = 0 ; i < size ; i++) {
 switch( type ) {
 case DECIMAL: cout << dec; break;
 case OCTAL: cout << oct; break;
 case HEXADECIMAL: cout << hex; break;
 }
 cout.width(5);
 cout << arr[i] << " ";
 }
 cout << endl;
}

 예시 코드 해석

 

이 코드는 정수 배열을 10진수(DECIMAL), 8진수(OCTAL), 또는 **16진수(HEXADECIMAL)**로 유연하게 출력하는

PrintArray 함수를 정의하고 사용하는 예제입니다.

**디폴트 매개변수(default parameter)**를 활용하여 함수 호출을 간결하게 만드는 것이 핵심입니다.


## 코드 주요 구성 요소

1. enum INT_TYPE

enum은 특정 이름에 정수 값을 매겨주는 역할을 합니다.

C++
 
enum INT_TYPE {DECIMAL, OCTAL, HEXADECIMAL};
  • DECIMAL, OCTAL, HEXADECIMAL이라는 이름을 각각 정수 0, 1, 2에 대응시킵니다.
  • 숫자 0, 1, 2를 직접 쓰는 것보다 코드의 가독성이 훨씬 좋아집니다.

2. PrintArray 함수

배열을 받아 화면에 출력하는 함수입니다.

C++
 
void PrintArray(const int arr[], int size = 5, INT_TYPE type = DECIMAL);
  • const int arr[]: 수정할 수 없는 정수 배열을 받습니다.
  • int size = 5: 출력할 원소의 개수를 정합니다. 만약 함수 호출 시 이 값을 생략하면 기본값으로 5가 사용됩니다.
  • INT_TYPE type = DECIMAL: 출력할 진법을 정합니다. 생략하면 **기본값으로 DECIMAL(10진수)**이 사용됩니다.

3. main 함수

프로그램의 시작점으로, 두 개의 배열을 선언하고 PrintArray 함수를 네 가지 방식으로 호출합니다.

  1. PrintArray(arr1);: size와 type을 생략했습니다. arr1의 원소 5개를 기본값인 10진수로 출력합니다.
  2. PrintArray(arr1, 5, HEXADECIMAL);: 모든 인자를 직접 지정했습니다. arr1의 원소 5개를 16진수로 출력합니다.
  3. PrintArray(arr2);: size와 type을 생략했습니다. arr2는 원소가 10개지만, size의 기본값인 5가 적용되어 앞의 5개만 10진수로 출력합니다.
  4. PrintArray(arr2, 10);: type을 생략했습니다. arr2의 원소 10개를 기본값인 10진수로 출력합니다.

 PrintArray 함수 상세 분석

함수 내부에서는 cout의 출력 서식을 변경하여 진법을 조절합니다.

  • cout.setf(ios::showbase);: 진법을 시각적으로 보여주는 접두사를 출력하게 합니다. (8진수는 0, 16진수는 0x)
  • switch (type): type 값에 따라 출력 서식을 변경합니다.
    • cout << dec;: 10진수 모드로 변경
    • cout << oct;: 8진수 모드로 변경
    • cout << hex;: 16진수 모드로 변경
  • cout.width(5);: 각 숫자가 출력될 때 차지할 최소 너비를 5칸으로 설정하여 정렬 효과를 줍니다.

실행 결과 

   10    20    30    40    50 
  0xa   0x14   0x1e   0x28   0x32 
   10    20    30    40    50 
   10    20    30    40    50    60    70    80    90   100 

 

함수 중복의 약점- 중복 함수의 코드 중복 

#include <iostream>
using namespace std;
void myswap(int& a, int& b) {
int tmp;
tmp = a;
a = b;
b = tmp;
}
void myswap(double & a, double & b) {
double tmp;
tmp = a;
a = b;
b = tmp;
}
int main() {
int a=4, b=5;
myswap(a, b); // myswap(int& a, int& b) 호출
cout << a << '\t' << b << endl;
double c=0.3, d=12.5;
myswap(c, d); // myswap(double& a, double& b) 호출
cout << c << ' '\t' << d << endl;
}

두 함수를 보면 매개 변수만 다르고 나머지 코드는 동일하다. 그럴 때 템플릿을 사용한다.

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

C++ 템플릿(Template): 마법 같은 코드 재사용의 비밀 🧙‍♂️

C++ 프로그래밍을 하다 보면 vector<int>, list<string>처럼 꺾쇠괄호와 함께 쓰이는 코드를 자주 만나게 됩니다. 바로 이것이 C++의 강력한 기능, **템플릿(Template)**입니다. 템플릿은 마치 '만능 틀'과 같아서, 자료형에 구애받지 않는 유연하고 재사용성 높은 코드를 작성하게 해주는 핵심 도구입니다.

오늘은 C++ 템플릿의 장점과 단점, 그리고 그 기반이 되는 '제네릭 프로그래밍'에 대해 알아보겠습니다.

✨ 템플릿의 강력한 장점

1. 압도적인 코드 재사용성

템플릿의 가장 큰 장점은 코드의 재사용입니다. 정수(int)를 더하는 함수, 실수(double)를 더하는 함수를 따로 만들 필요 없이, 템플릿으로 add() 함수 하나만 만들어두면 어떤 숫자 타입이든 처리할 수 있습니다.

💡 하나의 코드로 모든 자료형을 다룬다! 이것이 템플릿의 핵심입니다.

2. 뛰어난 생산성과 유용성

코드를 반복해서 작성할 필요가 없으니 버그가 줄어들고 개발 시간은 단축됩니다. 이는 곧바로 소프트웨어의 전체적인 생산성 향상으로 이어집니다. 잘 만들어진 템플릿 하나는 여러 프로젝트에서 유용하게 사용될 수 있습니다.


🤔 알아두어야 할 템플릿의 단점

물론 템플릿에도 몇 가지 단점은 존재합니다.

1. 낮은 포팅(Porting) 가능성

템플릿은 비교적 현대적인 C++ 기능이므로, 오래된 컴파일러나 특정 시스템의 컴파일러에서는 완벽하게 지원하지 않을 수 있습니다. 다른 환경으로 코드를 이식(porting)할 때 예상치 못한 문제에 부딪힐 수 있습니다.

2. 불친절한 컴파일 오류 메시지

템플릿을 사용하다가 오류가 발생하면, 수십 수백 줄에 달하는 길고 암호 같은 오류 메시지를 마주하게 될 수 있습니다. 이는 디버깅을 매우 어렵게 만드는 요인 중 하나로, 특히 초보 개발자에게는 큰 장벽이 되기도 합니다.


📚 제네릭 프로그래밍 (Generic Programming) 이란?

템플릿의 개념을 이해하기 위해선 **제네릭 프로그래밍(Generic Programming)**을 알아야 합니다. '일반화 프로그래밍'이라고도 불리는 이 기법은, 특정 자료형에 의존하지 않고 모든 종류의 자료형에 대해 동작하는 일반화된 코드(제네릭 함수, 제네릭 클래스)를 작성하는 것을 목표로 합니다.

C++에서는 템플릿이 바로 이 제네릭 프로그래밍을 구현하는 도구입니다.

STL (Standard Template Library) C++의 표준 라이브러리인 STL은 제네릭 프로그래밍의 결정체입니다. vector, map, list, algorithm 등 우리가 편리하게 사용하는 모든 것들이 템플릿으로 만들어져 있습니다. 덕분에 우리는 vector<int>, vector<string>, vector<MyClass>처럼 어떤 자료형이든 담을 수 있는 동적 배열을 손쉽게 사용할 수 있습니다.


🌐 제네릭 프로그래밍의 보편화 추세

이러한 제네릭 프로그래밍의 강력함은 C++에만 국한되지 않습니다. Java의 제네릭스(Generics), C#의 제네릭(Generic) 등 현대적인 프로그래밍 언어 대부분이 이 개념을 적극적으로 채택하여 활용하고 있습니다. 그만큼 소프트웨어 개발에서 '일반화'와 '재사용성'이 중요한 가치임을 보여주는 것이죠.

템플릿은 처음엔 다소 복잡하게 느껴질 수 있지만, 한번 익숙해지면 C++ 프로그래밍의 수준을 한 단계 끌어올려 주는 강력한 무기가 될 것입니다