본문 바로가기

C++/C, C++, STL

C++ Template과 Java Generic의 차이

Primitive 타입을 래핑하는 Wrapped class를 정의하는 상황을 가정합니다.

예를 들어 다음 코드는 int 타입을 래핑하는 클래스를 정의합니다. 클래스는 단순히 값을 저장하고 리턴하는 함수를 구현합니다.

class WrappedInt
{
    private:
        int value;

    public:
        int getValue()
        {
            return value;
        }

        int setValue(int value)
        {
            this->value = value;
            return this->value;
        }
};

마찬가지로 float 타입을 래핑하는 클래스를 정의합니다.

class WrappedFloat
{
    private:
        float value;

    public:
        float getValue()
        {
            return value;
        }

        float setValue(float value)
        {
            this->value = value;
            return this->value;
        }
};

예시의 두 클래스 WrappedIntWrappedFloat는 저장하는 값의 Type에만 차이가 있고 역할(Method)은 동일합니다.

이어서 bool 타입 그리고 long 타입 등을 래핑하는 WrappedBoolean WrappedLong 클래스를 정의해야 한다면 다음 의문이 들게 됩니다.

더보기

타입(Type)을 제외한 나머지 코드가 동일한데 중복을 줄일 수 있지 않을까?

C++ 템플릿(Template)

C++에서는 다형성과 재사용성을 제공하기 위한 템플릿(Template)을 제공합니다.

템플릿을 사용하면 typename이라는 추상화된 키워드를 정의하고, 이를 다양한 타입으로 대입 할 수 있습니다.

template <typename T> class WrappedValue
{
    private:
        T value;
};
코드 비고
Line 1 template <typename T> class 템플릿을 사용하는 클래스를 선언합니다.
템플릿으로 사용하는 typenameT로 정의합니다. 
Line 4 T value 멤버 변수로 T 타입을 정의합니다.
변수의 타입은 변수의 선언부에서 결정됩니다.

상기 예시의 WrappedIntWrappedFloat은 템플릿으로 같이 통합 될 수 있습니다. 

template <typename T> class WrappedValue
{
    private:
        T value;

    public:
        T getValue()
        {
            return value;
        }

        T setValue(T value)
        {
            this->value = value;
            return this->value;
        }
};

int main()
{
    WrappedValue<int> wrappedInt;
    wrappedInt.setValue(100);

    WrappedValue<float> wrappedFloat;
    wrappedFloat.setValue(200);
}
코드 비고
Line 21 WrappedValue<int> wrappedInt int 타입으로 WrappedValue 템플릿 인스턴스를 생성합니다.
Line 24 WrappedValue<float> wrappedFloat float 타입으로 WrappedValue 템플릿 인스턴스를 생성합니다.

만약 두 개 이상의 typename을 선언해야 한다면 다음과 같이 작성합니다.

template <typename T, typename E> class WrappedDoubleValue
{
    private:
        T value0;
        E value1;
};
코드 비고
Line 1 <typename T, typename E> 두 개의 typename을 정의합니다.

Java 제네릭(Generic)과의 차이점

비슷한 기능으로 Java에서는 제네릭(Generic)을 제공합니다.

Java의 제네릭은 Java 5 버전부터 지원하며 이는 C++의 템플릿과 가장 강력한 차이점이 발생한 이유이기도 합니다.

 

기본적으로 Java의 모든 객체는 java.lang.Object를 상속합니다.

제네릭 등장 이전 세대 버전에서는 다형성을 구현하기 위해 다음과 같이 작성했습니다.

public class WrappedValue 
{
	private Map value = new HashMap();
    
	public Object getValue(String type) 
	{
		return (Object) value.get(type);
	}
}

이후 제네릭이 추가된 버전에서는 다음과 같이 변화합니다.

public class WrappedValue 
{
	private Map<String, Object> value = new HashMap();
    
	public Object getValue(String type) 
	{
		return value.get(type);
	}
}

이는 Java의 제네릭이 단순히 타입 캐스팅을 위한 래퍼 클래스임을 의미합니다.

그렇기 때문에 Java의 제네릭은 항상 Object 타입에 근원을 두는 래퍼 클래스만 허용됩니다.

public class WrappedValue<T extends SomeInterface>
{
	private T value;
    
	public T getValue() 
	{
		return value;
	}
}

C++의 템플릿에서는 Primivte 타입이 허용되는 것과 차이가 있습니다.

template <typename T, int E> class WrappedDoubleValue
{
    private:
        T value0;
        E value1;
};

또한 Java의 제네릭은 여러 타입이 입력되더라도 한 번의 컴파일만 발생합니다. 이는 단순히 타입 캐스팅으로 다형성이 구현된다는 점에 기반합니다.

반면, C++ 템플릿을 사용하면 새로운 타입이 사용될 때마다 서로 다른 소스 코드가 생성됩니다.

// template <int T> class WrappedValue 소스 파일 생성
WrappedValue<int> wrappedInt;
wrappedInt.setValue(100);

// template <float T> class WrappedValue 소스 파일 생성
WrappedValue<float> wrappedFloat;
wrappedFloat.setValue(222);

이는 #define 매크로를 사용하는 것과 같으며 컴파일 속도 측면에서 Java의 제네릭보다 느리다는 단점이 있습니다.