모두의 코드: 씹어먹는 C++ - <9 - 1. 코드를 찍어내는 틀 - C++ 템플릿(template)>

YP J·2022년 7월 3일
0

모두의코드 C++

목록 보기
11/11

https://ansohxxn.github.io/cpp/chapter13-1/

일단 모두의 코드 말고 이분의 블로그를 정리 하겠다.

C++ Chapter 13.1 : 함수 템플릿

int getMax(int x, int y)
{
    return (x > y) ? x : y;
}

double getMax(double x, double y)
{
    return (x > y) ? x : y;
}

float getMax(float x, float y)
{
    return (x > y) ? x : y;
}

템플릿 을 쓰기전에는

  • 리턴 타입과 매개변수의 타입만 다른데 코드는 같다 그렇다면 일반화 할수 없을까 ?? -> Template !

함수 템플릿

일반화

템플릿은 컴파일 타임에 결정되는 것만 쓸 수있다? (프로세싱타임 아니야?)

template <typename T>
T getMax(T x, T y)
{
	return (x > y) ? x : y;
}
  • T 자리에 int, double, 혹은 객체 타입 등등을 넣어 특정 타입으로 구체화 하면 된다.
    • 단 T자리에 들어갈수 있는 것들은 전부 컴파일 타임에 결정되어야한다.

구체화

int main()
{
	std::cout << getMax(1, 2) << std::endl; 
    // T가 int로 구체화
    // int getMax(int x, int y)
    std::cout << getMax(3.14, 1.1592) << std::endl;
    // T 가 double로 구체화
    // double getMax(double x, double y)
    std::cout << getMax(1.0f, 3.4f) << std::endl;
    // T 가 float 로 구체화
    
    std::cout << getMax(Cents(5), Cents(9)) << std::endl;
    // T가 Cents객체로 구체화
    // Cents getMax(Cents x, Cents y)
}
  • std::cout <<getMac(Cents(5),Cents(9))
    • 이때 Cents 클래스에 << 와 < 연산자에 대해 오버로딩이 되어 있어야 한다.
      • std::cout <<
      • return (x >y)?X:y

C++ Chapter 13.2 : 클래스 템플릿

클래스 템플릿

일반화

#include <iostream>

template <typename T>
class MyArray
{
	private:
		int m_len;
		T *m_data;
	public:
		MyArray(int len)
		{
			m_data = new T[len];
			m_len = len;
		}

		~MyArray()
		{
			reset();
		}

		void reset()
		{
			delete[] m_data;
			m_data = nullptr;
			m_len = 0;
		}

		T &operator[](int index)
		{
			assert (index >= 0 && index < m_len);
			return m_data[index];
		}

		int getLen()
		{
			return m_len;
		}
		void print()
		{
			for (int i=0; i<m_len; i++)
				std::cout << m_data[i] << " ";
			std::cout << std::endl;
		}
};
  • 꼭 이름이 typename 과 T일 필요는 없다. 근데 보통 이렇게 쓴다.

클래스 템플릿은 그 자체로 클래스는 아니다. 클래스 틀일 뿐

구체화

int main()
{
	MyArray<char> my_array(10);

	for (int i =0; i< my_array.getLen(); ++i)
		my_array[i] = i + 65;

	my_array.print();
	return 0;
}
  • MyArray<char>my_array(10);
    • MyArray클래스 템플릿이 char타입으로 구체화 됐다
      • 이말은 이클래스를 인스턴스화 할때 메모리를 얼만큼 할당 받아야 하는지 알게 되었다는 얘기이다.
  • my_array[i] = i+65;
    • [] 연산자 오버로딩이 되어 있어서 객체이름[] 으로 바로 동적 배열 멤버의 원소에 접근할 수 있다.

클래스 템플릿은 헤더파일, cpp 로 분리하지 않는게 좋다.

분리할 경우 -> 링킹 에러 발생

  • print() 멤버함수의 선언과 정의를 나눠보자

  • MyArray.h

#include <iostream>

template <typename T>
class MyArray
{
    ...  // 다른 부분들은 위와 동일

    void print();  // ⭐ 선언만
};
  • MyArray.cpp
#include "MyArray.h"

template <typename T>
void MyArray<T>::print()
{
    for (int i = 0; i < m_length; ++i)
        std::cout << m_data[i] << " ";
    std::cout << std::endl;
}
  • main.cpp
#include "MyArray.h"

int main()
{
    MyArray<char> my_array(10);

    for (int i = 0; i < my_array.getLength();++i)
        my_array[i] = i + 65;

    my_array.print(); // 💥 링킹 에러 발생

    return 0;
}
  • 컴파일엔 문제가 없으나 링킹 에러 발생 !

  • 링킹 에러이유

  • MyArray.cpp 파일을 컴파일 할 때, 어떤 자료형으로 클래스를 구체화 해야하는지 몰라서

  • print()함수의 바디 부분이 메모리에 정의가 되지 않기 때문이다.

  • 컴파일

    • 헤더파일은 컴파일 하지 않으며
    • cpp 파일 내에서 include 한 헤더 파일 내용이 복사될 뿐이다.
  • cpp 파일들만 컴파일 한다.

    • 각각의 cpp 파일들이 독립적으로 컴파일 된 후
    • 컴파일이 완료된 같은 프로젝트 내의 cpp파일들 끼리 링킹이 된다.
  • 문법체크 + static 한 영역들 메모리 할당 일을 수행한다.

    • 컴파일시 메모리를 정의하려면 구체적인 자료형을 알아야만 얼만큼 메모리를 할당할지 알 수있는데
    • MyArray.cpp 파일은 템플릿 타입만 달랑 있어서 어느정도로 할당해야 할지 알수가 없기 때문에 print() 바디 부분이 메모리에 정의가 되지 못하고 그냥 넘어가게 된다.
    • 따라서 링킹시 print()바디 부분을 알 수 없어서 링킹 에러가 난다.
      • 컴파일 에러는 없다.
      • main.cpp 에서 MyArray.h를 include 하고 있기 때문에 print()라는 멤버 함수가 있다는건 알고 있기 때문.

해결방법

  1. main.cpp 에서
#include "MyArray.h"
#include "MyArray.cpp"
  • 메인 에서 MyArray.cpp 파일도 직접 include 해주는것이다 이러면 링킹 에러 발생 하지 않는다.
    • main.cpp에 MyArray.cpp 의 내용을 복사해오는 것이므로.

    그러나 이 방법은 비추. .cpp파일 까지 포함하는 일은 프로그램이 커질시 복잡해짐.

  1. explicit instantiation
  • MyArray.cpp
#include "MyArray.h"

template <typename T>
void MyArray<T>::print()
{
    for (int i = 0; i < m_length; ++i)
        std::cout << m_data[i] << " ";
    std::cout << std::endl;
}

template void MyArray<char>::print();
template void MyArray<double>::print();
  • 이렇게 MyArray.cpp내에서 직접 특정 자료형들을 구체화 해두어 Myarray.cpp컴파일시 각각 메모리가 미리 잡히도록 해주는 방법이 있다.
  • char , double 로
#include "MyArray.h"

template <typename T>
void MyArray<T>::print()
{
    for (int i = 0; i < m_length; ++i)
        std::cout << m_data[i] << " ";
    std::cout << std::endl;
}

template class MyArray<char>;
template class MyArray<double>;
  • 이렇게 클래스 단위로 구체화 시킬수도 있다.
  1. 가장 추천 !! 하는 방법
  • 클래스 템플릿 헤더 파일 내에서 전부 구현 하는것.

  • 클래스 템플릿은 선언부, 구현부 각각 헤더파일, cpp파일로 나누지 말고

  • 그냥 헤더파일 내에서 다 구현 ! 선언 + 구현 까직

  • MyArray.h

#include <iostream>

template <typename T>
class MyArray
{

    ...

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

C++ Chapter 13.3 : 자료형이 아닌 템플릿 매개 변수

컴파일 타임에 정해지는 값으로만 구체화 된다.

#include <iostream>

template <typename T, unsigned int T_SIZE>  // ⭐
class MyArray
{
private:
    // int m_length;   ⭐
    T * m_data;   
public:
    MyArray()
    {
        m_data = new T[T_SIZE]; // ⭐
    }

    ~MyArray()
    {
        reset();
    }

    void reset()
    {
        delete [] m_data;
        m_data = nullptr;
        // m_length = 0;
    }

    T & operator [] (int index) // ⭐
    {
        assert (index >= 0 && index < T_SIZE); 
        return m_data[index];
    }

    int getLength()
    {
        return T_SIZE;   // ⭐
    }

    void print()
    {
        for (int i = 0; i < T_SIZE; ++i)  // ⭐
            std::cout << m_data[i] << " ";
        std::cout << std::endl;
    }
};
  • T_SIZE 는 unsinged int 형 이며 템플릿 매개변수가 된다.
    • 이 클래스 템플릿을 구체화 할때 T_SIZE 자리에 unsigned int 형 상수 리터럴을 넣어주면 된다.
      • 컴파일 타임에 결정되어야 하기 때문에 리터럴로 구체화 해야한다.
  • 멤버 변수 m_lenth가 필요 없어짐 !
 #include "MyArray.h"

int main()
{
    MyArray<double, 100> my_array;

    for (int i = 0; i < my_array.getLength();++i)
        my_array[i] = i + 65;

    my_array.print();

    return 0;
}
MyArray<double, 100> my_array;
  • T_SIZE 자리에 100 이 들어간다.
    • 위 for 문은 100 번 돈다.
    • my_array의 멤버 m_data 크기는 100 이된다.
int a = 100;
MyArray<double, a>my_array; // Error
  • 일반 변순느 컴파일 때 메모리가 정해지는게 아니기 때문에 안 된다.
const int a = 100;
MyArray<double, a>my_array; // Error
  • const 상수는 컴파일때 메모리가 정해지기 때문에 가능 !
profile
be pro

0개의 댓글