C++/C++ : Study

9. Generic Programming과 템플릿 (6) - Non-템플릿 인자

더블유제이플로어 2025. 7. 1. 05:45

Generic Programming using Template

중요 Part이다.

◆  클래스 템플릿 매개 변수

   ●   타입이 아닌 다른 매개변수를 사용할 수 있음(non-type template argument)
        ☞  아래 예제에서 N의 값을 템플릿 클래스의 사용 시에 직접 명시해 줌

template <typename T, int N>
class Array {
    int size = N;
    T Values[N];
};
* 정적 배열이기 때문에 고정된 크기가 필요해서, 숫자 N 자체를 템플릿 인수로 같이 받도록 정의함!
int main()
{
    Array<int, 5> nums;
    Array<double, 10> nums2;
}
해당 코드를 보고 컴파일러는 아래처럼 작성한다.
class Array<int, 5> {
    int size = 5;
    int Values[5]; 
};

class Array<double, 10> {
    int size = 10;
    int Values[10];
};

📘 클래스 템플릿 매개 변수: Non-type Template Argument

🎯 템플릿의 매개변수는 꼭 타입(typename) 일 필요는 없다.
**정수형 값과 같은 타입이 아닌 인자(non-type argument)**도 사용할 수 있다.

📄 예제: 정수형 매개변수를 사용하는 클래스 템플릿

template <typename T, int N>
class Array {
private:
    int size = N;
    T Values[N];  // 정적 배열 (크기가 N인 배열)
};
  • T는 배열의 요소 타입
  • N은 배열의 크기
  • Values[N]는 컴파일 타임에 크기가 고정된 정적 배열

📄 사용 예제

Array<int, 5> nums;        // int형 5칸짜리 배열
Array<double, 10> nums2;   // double형 10칸짜리 배열
➡ 위 선언은 아래처럼 컴파일러가 클래스를 생성하게 된다:
class Array<int, 5> {
    int size = 5;
    int Values[5];
};

class Array<double, 10> {
    int size = 10;
    double Values[10];
};

📌 즉, T, N 조합에 따라 서로 완전히 다른 클래스가 만들어지는 것.

❓ 왜 non-type 템플릿 매개변수가 중요한가?

✅ 이유는 다음과 같다:

  1. 정적 배열 크기를 컴파일 타임에 안전하게 관리 가능
    • 배열 사이즈를 템플릿으로 전달하면, 컴파일 시점에 크기가 고정
    • 런타임 오류를 방지할 수 있음
  2. 코드 재사용성 증가
    • 템플릿 하나로 다양한 크기의 배열을 처리할 수 있음
    • 매번 클래스 새로 만들 필요 없음
  3. STL의 기반 구조와 동일
    • std::array <T, N> 같은 STL 클래스도 이 구조 사용
    • C++을 제대로 이해하려면 필수 개념

📌 특히 임베디드 시스템, 머신비전 장비처럼
동적 메모리 할당을 피하고 정적 메모리를 써야 하는 경우
이 문법은 아주 유용하다.

❓ C#의 List는 동적할당인가? 크기를 지정하지 않고 선언해도 되는 이유는?

✅ C#의 List<T>는 기본적으로 동적할당 구조다.

List<int> numbers = new List<int>(); // 초기 크기 없이 생성 가능
  • 내부적으로 배열을 사용하지만, 필요할 때 자동으로 확장
  • 메모리는 Heap에 동적할당됨
  • C++의 vector와 비슷함

즉, 선언만 하면 내부적으로 new T []를 하고, 크기가 초과되면 배열을 복사해서 다시 할당함.
정적 메모리 기반인 C++ 배열과는 구조적으로 다르기 때문에,
C++에서는 T arr[N]처럼 컴파일 시점에 크기를 알아야 하는 구조가 필요해.

🎯 요약

  • 템플릿 인자는 타입만 쓸 수 있는 게 아니다
  • int N처럼 정수값도 인자로 사용 가능
  • 이 기능은 STL, 특히 std::array와 같이 고정 크기 컨테이너 구현 시 필수
  • 머신비전, 임베디드 개발에서 유용하며,
    메모리 안정성과 성능 면에서 큰 장점이 있다

📄 예제: 2_Class_Template.cpp

🎯 설명
기본적인 클래스 템플릿 사용 예제
한 가지 타입 인자(T)를 받아서 저장하고 출력하는 박스 클래스

 
#include <iostream>
#include <string>

template <typename T>
class Box {
private:
    T data;
public:
    Box(T value) : data(value) {}

    void print() const {
        std::cout << "Value: " << data << std::endl;
    }

    T getData() const {
        return data;
    }
};

int main() {
    Box<int> b1(42);
    Box<std::string> b2("Hello, Template!");
    Box<double> b3(3.1415);

    b1.print();   // Value: 42
    b2.print();   // Value: Hello, Template!
    b3.print();   // Value: 3.1415

    return 0;
}
  • Box <T>는 단일 타입 인자 사용
  • 다양한 타입으로 템플릿 인스턴스화 가능

📄 예제: 3_Class_Template_Array.cpp

🎯 설명
Non-type 템플릿 매개변수를 포함한 클래스 템플릿 예제
고정 크기 배열을 관리하는 템플릿 클래스

#include <iostream>

template <typename T, int N>
class Array {
private:
    T data[N];
public:
    void set(int index, T value) {
        if (index >= 0 && index < N) {
            data[index] = value;
        }
    }

    T get(int index) const {
        if (index >= 0 && index < N) {
            return data[index];
        }
        return T(); // 기본값 반환
    }

    void print() const {
        for (int i = 0; i < N; ++i) {
            std::cout << data[i] << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    Array<int, 5> arr1;
    Array<double, 3> arr2;

    for (int i = 0; i < 5; ++i) arr1.set(i, i + 1);  // 1 2 3 4 5
    arr2.set(0, 1.1);
    arr2.set(1, 2.2);
    arr2.set(2, 3.3);

    arr1.print();
    arr2.print();

    return 0;
}
  • Array <T, N>은 타입과 크기 둘 다 템플릿 인자로 받음
  • T data[N]로 정적 배열을 구성
  • STL의 std::array와 유사한 구조

Generic Programming과 템플릿

◆  Generic Programming 이란 : 타입에 관계없이 동작하는 코드 작성 개념

   ●  Macro를 활용한 Generic Programming : 매크로를 활용하여 코드를 대처하는 방식, 주의가 필요

◆  템플릿을 활용한 Generic Programming

   ●   함수 템플릿 : template <typename T>, 템플릿 인자와 특수화
   ●   클래스 템플릿 : 클래스에도 동일하게 사용 가능, 템플릿 인자에도 int N 등도 사용 가능함.


📘 이번 주 핵심 정리: Generic Programming과 템플릿

🎯 Generic Programming이란?

타입에 관계없이 동작하는 코드를 작성하는 프로그래밍 기법이다.
C++에서는 이를 위해 매크로 또는 템플릿 방식을 활용할 수 있다.

📌 방법 1: 매크로를 활용한 Generic Programming

  • #define을 사용하여 코드 조각을 복사-붙여넣기처럼 대체함
  • 예시:
#define SQUARE(a) a * a
int result = 100 / SQUARE(5);  // → 100 / 5 * 5 → 100
  • 단점
    • 연산자 우선순위 오류 발생 가능
    • 디버깅이 어렵다
    • 타입 체크가 되지 않음

📌 방법 2: 템플릿을 활용한 Generic Programming

✔️ 함수 템플릿

  • 타입을 T로 일반화한 함수 정의
  • 컴파일 시 실제 타입에 맞게 코드가 생성됨
  • 예시:
template <typename T>
T Max(T a, T b) {
    return (a > b) ? a : b;
}

Max<int>(3, 5);        // → int 버전 생성
Max<double>(3.2, 4.5); // → double 버전 생성

✔️ 다중 템플릿 인자

  • 서로 다른 타입을 받을 수 있음
template <typename T1, typename T2>
void func(T1 a, T2 b) { ... }

✔️ 함수 템플릿 특수화

  • 특정 타입에 대해 다르게 동작하도록 구현 가능
template <>
std::string Max(std::string a, std::string b) {
    return (a.length() > b.length()) ? a : b;
}

📌 클래스 템플릿

  • 함수 템플릿과 동일한 문법으로 클래스에도 사용 가능
  • 멤버 변수나 멤버 함수에 사용되는 타입을 일반화함
template <typename T>
class Box {
private:
    T data;
public:
    Box(T value) : data(value) {}
    T getData() const { return data; }
};

📌 클래스 템플릿의 다중 인자

  • T1, T2 등 다중 타입으로 일반화 가능
  • 특정 인자만 고정해서 부분 특수화도 가능
template <typename T1, typename T2>
class Item { ... };

template <typename T1>
class Item<T1, double> {
    // T2가 double일 때만 이 클래스 사용됨
};
 

📌 Non-type 템플릿 인자 (클래스 템플릿에서)

  • 템플릿 인자로 값(int) 을 전달할 수 있음
  • 정적 배열처럼 컴파일 타임에 크기를 알아야 하는 구조에 적합
template <typename T, int N>
class Array {
    T values[N];
};
  • 사용 예:
Array<int, 5> arr1;     // int[5]
Array<double, 10> arr2; // double[10]
  • STL의 std::array 구현 원리와 유사

❓ 머신비전 개발 환경에서는 어떻게 활용되는가?

  • 정적 메모리 사용이 많은 머신비전 분야에서는
    메모리 효율과 성능이 중요하기 때문에
    Non-type 템플릿과 정적 배열 구조가 자주 사용됨
  • DIO, 카메라 인터페이스 등 하드웨어 제어 코드에서
    템플릿으로 타입 추상화하거나 배열 크기를 고정할 수 있음
  • 예:
    Array<BYTE, 16>처럼 고정 크기 버퍼나
    Packet<uint8_t, 64>처럼 타입 + 크기 기반의 통신 구조 설계에 활용됨

✅ 마무리

  • 템플릿은 매크로보다 안전하고 강력한 방식
  • 컴파일러가 타입에 따라 자동으로 코드 생성
  • STL 이해를 위해 반드시 필요한 개념
  • 템플릿 함수, 클래스, 특수화, non-type 인자까지 이번 주 핵심

다음 강의인 STL의 기반이 되는 내용이므로 이번 정리를 꼭 복습해두기 💙


◆  템플릿의 특수화

   ●   특정 자료형에 대해서는 템플릿을 사용하지 않고 별도 구현한 함수를 사용하도록 구현 가능
   ●   아래 경우에는 템플릿 특수화를 하지 않아도 오버로딩을 통해 동일하게 동작
   ●   하지만 미묘한 차이가 있으므로 실제 사용할 때 문제가 생기면 참고

c++ - Should you prefer overloading over specialization of function templates? - Stack Overflow

c++ - Difference between explicit specialization and regular functions when overloading a template function - Stack Overflow

template<typename T>
T min(T a, T b)
{
    return (a < b) ? a : b;
}
template<>
std::string min(std::string a, std::string b)
{
    return (a.length() < b.length()) ? a : b;
}
이 예제에서 특수화를 명시하지 않아도 std::string 에 대해서 동작

❓ "템플릿 특수화 안 했는데도 왜 std::string 함수가 호출되는지?"

바로 아래 코드 예제를 보자:

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

template<>
std::string min(std::string a, std::string b) {
    return (a.length() < b.length()) ? a : b;
}

🎯 핵심 개념 구분

구분  설명  예시 
함수 템플릿 특수화 (specialization) 템플릿 함수의 특정 타입에 대해 별도로 구현 template<> std::string min(std::string, std::string)
함수 오버로딩 (overloading) 같은 이름의 함수지만 매개변수 타입이 다름 std::string min(std::string, std::string) (템플릿 아님)

💡 이 코드에서는 특수화지만, 왜 오버로딩처럼 동작하냐?

➤ 실제로는 "템플릿 특수화"처럼 보이지만, 함수 오버로딩과 매우 유사한 방식이기 때문에

컴파일러는 아래와 같이 일반 함수가 더 정확히 일치하면 그걸 우선 호출함:

std::string a = "hi";
std::string b = "hello";
min(a, b); // → 특수화된 템플릿이 아닌, 일반 함수처럼 우선 일치된 버전 호출

✅ 그럼 차이는 뭐야?

상황  오버로딩  특수화 
함수명만 보고 매칭 가능 O O
일치하는 타입이 있을 때 우선 호출 O (가장 정확한 매개변수 타입) △ (정확히는 템플릿 매칭 규칙 적용)
템플릿 내부 구조를 활용 가능 X O
클래스 템플릿처럼 다양한 타입 대응 X O

✅ 결론

  • 예시는 "템플릿 특수화"지만, 오버로딩처럼 작동 가능해 보인다.
  • 둘의 차이는 미묘하지만, 템플릿 내부 로직이 필요한 경우는 특수화를 선택하고,
  • 간단히 타입이 다를 때만 다르게 동작하고 싶다면 오버로딩이 더 낫다는 의견이 많다.

💬 머신비전 쪽에서는?

템플릿 특수화보다 오버로딩을 더 많이 사용한다.
왜냐하면 장비 제어 함수에서 타입에 따라 살짝 다른 처리만 필요할 때가 많고,
컴파일러가 오버로딩을 더 명확하게 처리해줘서 유지보수가 쉬운 편이다.

https://youtu.be/sMt6KRA-XGI?si=vi-nPU_dyZu_JLSq

사실 올해 STL 부터 공부했다가 아예 이해가 되지 않았는데 이제야 대략적으로 이해가 간다.

제너릭프로그래밍 아니 상속부터 다시 공부했었어야 했다.