template
-
[C++] Template Instantiation (템블릿 인스턴스화)2021.08.27
[C++] Class, Aliasing, Variable Template (여러 템플릿들)
이번 포스트에서 다룰 내용은 다양한 Template에 대해서 공부한다. 이제까지 배운 Template은 Temlate Function밖에 없었지만 이번에는 다양한 Template에 대한 사용법을 알아본다. 먼저 배울 Template은 아래와 같다.
- Class Template
- Aliasing Temlate
- Variable Template
Class Template (클래스 템플릿)
Class template은 우리가 알게 모르게 사용을 많이 한다. STL(Dtandard Template Library) 자체가 Template으로 구성된 라이브러리이다. Class Template도 Template Function과 동일하게 사용을 하면 된다. 필자는 Template에 대해서 공포감을 느끼기 때문에 충분히 이해가 가능하지만 거부감이 든다. 아래는 Template을 이용해서 push와 pop만 가능한 Data Sturcture의 Stack을 만든 예제이다.
이미 다 공부한 내용이라 코드 첨부로 설명을 마친다.
template<typename T>
class Stack
{
public:
void push(T elem)
{
mVec.emplace_back(std::move(elem));
}
bool pop(T& elem)
{
if (mVec.size() == 0)
return false;
elem = mVec[mVec.size() - 1];
mVec.pop_back();
return true;
}
private:
std::vector<T> mVec;
};
int main()
{
Stack<std::string> stack;
stack.push("jiyong");
stack.push("is");
stack.push("he");
std::string n;
while (stack.pop(n))
{
std::cout << n << '\n';
}
}
Aliasing Template (별명 템플릿?)
Alias는 별명 혹은 별칭이라는 뜻을 가지고 있다. 결국 Aliasing Template은 별명을 가지게 하는 게 Template을 이용해서 별명을 만든다고 생각하면 쉽다.
#include <vector>
#include <array>
template<typename T>
using pretendKeys = std::vector<std::array<T, 64>>;
int main()
{
/*
* alias
* using pretendInt = int;
* using pretendKeys = std::vector<int*_t, 64>;
*/
pretendKeys<float> floatKeys;
//std::vector<std::array<float, 64>> floatKeys
pretendKeys<double> doubleKeys;
//std::vector<std::array<double, 64>> doubleKeys
}
alias을 만드는 법은 using 키워드를 통해 만들 수 있다. C++03에서는 typedef를 사용했지만 C++11에서부터 using을 사용해서 더 직관적이게 "별명"을 붙일 수 있다.
Variable Template (변수 템플릿) C++14
먼저 Variable Template을 설명하기 전에 const와 constexpr에 대해서 짧게 설명한다.
int main()
{
int n = 10;
const int a = 10 + n;
constexpr int b = 11 + n; //ERROR 컴파일 타임에 n이 정해지지 않음.
return 0;
}
const와 constexpr 둘 다 상수를 선언하는 키워드이다. 둘의 차이점은 컴파일 상수이냐 런타임 상수이냐의 차이이다. const는 런타임에 초기값이 정해져 있어야 하고 constexpr는 컴파일 타임에 초기값이 정해져 있어야 한다는 차이점이다.
이제 Variable Template (변수 템플릿)에 대해서 공부를 하자.
변수 템플릿도 별거 없다. C++14부터 지원되는 기능이다. 아래 예제를 보고 충분히 이해할 수 있다.
#include <iostream>
template<typename T>
constexpr T number = T(123.123);
int main()
{
std::cout << number<int>;
return 0;
}
Variable Template으로 number라는 상수를 선언하고 123.123이라는 값으로 초기화를 하였다.
main함수에서 number상수에 Anlge Bracket으로 Type을 int로 명시하여 출력해주면 integer값으로 Casting이 되고 123이 출력이 된다.
Ref.
https://www.youtube.com/watch?v=87nJ-3U4LkA&list=PLDV-cCQnUlIb2oezNpNTmxiiX_NibMrlO&index=5
'Modern C++' 카테고리의 다른 글
[C++] STL std::vector (벡터) Intro. (0) | 2021.08.29 |
---|---|
[C++] C++20 찍먹 Concepts (0) | 2021.08.28 |
[C++] Template Instantiation (템블릿 인스턴스화) (0) | 2021.08.27 |
[C++] Template Type Deduction (템플릿 타입 추론) : Perfect Forwarding (0) | 2021.08.26 |
[C++] Template Intro. (템플릿) : Function Template (함수 템플릿) (0) | 2021.08.26 |
[C++] Template Instantiation (템블릿 인스턴스화)
Template Build (Instantiation)은 C++ 코드를 짤 때 Header와 Cpp파일을 분리해서 코드를 따게 되는데 Template 함수를 Header에 Declaration을 하고 Cpp파일에 Definition을 하고 컴파일을 하면 에러가 뜨고 컴파일이 되지 않는다. 해당 문제가 왜 발생하는지 알아보자.
Template Instantiation (템플릿 인스턴스화)
템플릿 인스턴스화에는 크게 아래 두 가지 종류가 있다.
- Implicit Instantiation (암시적 인스턴스화)
- Explicit Instantiation (명시적 인스턴스화)
Implicit Instantiation (암시적 인스턴스화)는 우리가 Template함수를 정의하고 main함수에서 Angle Bracket으로 차입을 명시해주어 함수를 호출하는 방식을 말한다. 암시적 인스턴스화는 두 가지가 있는데 다 이미 배웠던 내용이다.
코드를 통해서 두 가지 암시적 인스턴스화에 대해서 알아본다.
암시적 인스턴스화는 두 가지가 있다.
- 사용자가 Angle Bracket ('<', '>')에 Type을 명시해주는 방법
- Template Type Deduction (타입 추론)을 이용해 Type을 명시하지 않는 방법
#include <iostream>
template<typename T>
T foo(T a)
{
std:::cout << a << '\n';
}
int main()
{
foo<int>(12); //사용자가 Type을 직점 Angle Bracket에 넣어주는 방법
foo(12); //Template Type Deduction을 이용해 Type을 명시해주지 않는 방법
return 0;
}
Template 함수는 함수를 호출(인스턴스화) 하기 전까지 코드로 존재하다가 인스턴스 화가 되면서 해당 Type혹은 Deduction을 통해서 함수를 만들게 된다.
그렇다면 Header 파일에 Template함수에 대한 Declaration을 작성하고 Cpp 파일에 Definition을 작성하게 되면 정상적으로 코드가 동작하는지 확인해보자.
main.cpp
#include <iostream>
int main()
{
foo<int>(12); //사용자가 Type을 직점 Angle Bracket에 넣어주는 방법
foo(12); //Template Type Deduction을 이용해 Type을 명시해주지 않는 방법
return 0;
}
foo.h
#pragma once
template<typename T>
T foo(T a);
foo.cpp
template<typename T>
T foo(T a)
{
std:::cout << a << '\n';
}
위와 같이 Template 함수의 Declaration를 foo.h에 함수의 Definition을 foo.cpp에 적는다면 컴파일 에러가 나게 된다. 컴파일 에러가 나게 되는 이유는 Template는 함수 자체가 아니고 컴파일러가 Instantiation이 발생하면 코드로 만들어주는 기능을 하게 된다. 여기서 Template을 보고 코드를 만들게 되는데 foo.h에 있는 내용 즉 Declaration만 보고는 어떠한 코드를 만들지를 모르기 때문에 컴파일 에러가 난다.
위 에러를 해결하기 위해서 두 가지 방법을 사용할 수 있는데 코드로 알아본다.
main.cpp
#include <iostream>
int main()
{
foo<int>(12); //사용자가 Type을 직점 Angle Bracket에 넣어주는 방법
foo(12); //Template Type Deduction을 이용해 Type을 명시해주지 않는 방법
return 0;
}
foo.h
#pragma once
template<typename T>
T foo(T a)
{
std:::cout << a << '\n';
}
위와 같이 cpp파일에 Definition을 따로 적지 않고 header 파일에 정의까지 해주는 방법이 있다.
굳이 cpp 파일에 정의를 해서 코드를 만들고 싶다면 cpp 파일 안에 Type Explicit Instantiation을 하면 문제가 해결된다. 이 뜻은 무엇이냐면 cpp 파일 안에 특정 Type에 대한 함수로 컴파일을 해달라고 요청한다고 생각하면 된다. 하지만 이럴 경우 사용하고자 하는 Type에 대해서 모두 Explicit 하게 선언을 해주어야 한다.
main.cpp
#include <iostream>
int main()
{
foo<int>(12); //사용자가 Type을 직점 Angle Bracket에 넣어주는 방법
foo(12); //Template Type Deduction을 이용해 Type을 명시해주지 않는 방법
return 0;
}
foo.h
#pragma once
template<typename T>
T foo(T a);
foo.cpp
template<typename T>
T foo(T a)
{
std:::cout << a << '\n';
}
template int foo<int>(int); //explicit template instatiation
'Modern C++' 카테고리의 다른 글
[C++] C++20 찍먹 Concepts (0) | 2021.08.28 |
---|---|
[C++] Class, Aliasing, Variable Template (여러 템플릿들) (0) | 2021.08.27 |
[C++] Template Type Deduction (템플릿 타입 추론) : Perfect Forwarding (0) | 2021.08.26 |
[C++] Template Intro. (템플릿) : Function Template (함수 템플릿) (0) | 2021.08.26 |
[C++] Weak Pointer (위크 포인터) : Circular Reference (순환 참조) 해결 (0) | 2021.08.25 |
[C++] Template Type Deduction (템플릿 타입 추론) : Perfect Forwarding
Template으로 만들어진 함수를 사용하기 위해서는 함수명 뒤에 Argument Type을 명시를 해주어야 했다. 하지만 Argument Type을 명시하지 않아도 컴파일러가 Argument를 보로 Deduction (추론)을 하여 해당 타입의 함수를 만들어 준다.
Template Type Deduction (템플릿 타입 추론)
Template으로 구선된 함수는 타입을 추론할 수 있다. 아래 코드는 타입이 명시됐을 때와 안 됐을 때의 예를 보여주는 코드이다.
#include <iostream>
template<typename T>
void printVar(T a)
{
std::cout << typeid(a).name() << std::endl;
std::cout << a << std::endl;
}
int main()
{
int a = 100;
printVar<int>(a); //type 명시
printVar(a); //type 미명시
return 0;
}
main함수에서 Type을 명시하여 Template함수를 호출하는 것과 Type을 명시하지 않고 Template함수를 호출하였다. 출력 화면은 아래와 같다.
출력 콘솔에서 확인을 하게 되면 두 호출 모두 int 타입의 함수가 만들어지고 Argument로 넘어간 값도 출력이 잘 되었다. 이와 같이 Template 함수는 Argument를 보고 Type을 Deduction (추론)을 하게 된다. 일만 integer나 string클래스에 대해서는 다 문제없이 잘 작동하는데 Parameter를 Reference로 받거나 R-Value로 받을 경우는 어떻게 되나 궁금할 수 있다.
Template 함수도 일반 함수와 같이 Reference Type 혹은 R-value Reference Type으로 받는 것이 가능하다. 먼저 Reference와 R-Value Reference의 일반 함수를 만들어 보면 아래와 같다.
#include <iostream>
template<typename T>
void printLRef(int& a)
{
std::cout << a << std::endl;
}
void printRRef(int&& a)
{
std::cout << a << std::endl;
}
int main()
{
int a = 100;
printLRef(a);
printRRef(a); //ERROR
return 0;
}
printLRef는 L-Value를 printRRef는 R-Vlaue를 받아주는데 main함수에서 printRRef(a);는 L-Value를 넘겨주기 때문에 컴파일 에러가 난다. 해당 에러를 고쳐주기 위해서는 std::move() 함수를 통해서 R-Vlaue로 변환을 한 후에 Argument로 넘겨줘야 한다.
Template 함수를 이용해 위와 같은 함수를 만든다고 가정을 해보자. Template을 사용하는 이유는 어떠한 Type을 Argument로 주어도 Compile-time에 해당 타입에 맞는 함수를 만들어 줘야 한다. Template 함수에서 Reference를 이용해서 Argument를 넘겨주는 방법은 아래와 같다.
#include <iostream>
template<typename T>
void printVar(T&& a)
{
std::cout << a << std::endl;
}
int main()
{
int a = 100;
printVar(a);
printVar(std::move(a));
return 0;
}
위 코드에서 Template함수의 Parameter는 T&& R-Value를 받게 되어있다. 하지만 main함수에서 L-Value를 Argument로 넘겨주었는데 에러 없이 컴파일이 문제없이 되었다. 이유는 Template에서 T&&는 R-Value Reference가 아니라 Forward Reference라고 부른다. 당연하게 이는 L-Value Reference가 되기도 하고 R-Value Reference가 되기도 한다.
더 자세히 위 내용을 이해하기 위해 Compiler Explorer를 이용해서 어셈블리 코드를 보자. 아래는 위 코드를 어셈블리 코드로 변환시킨 화면이다.
어셈블리 코드를 보게 되면 컴파일러가 함수의 Argument를 보고 맞는 타입의 함수를 만든 것을 확인할 수 있다. L-Value reference를 Argument로 받은 함수는 T가 int&이고 함수의 Parameter는 int&가 된다. R-Value Reference를 Argument로 받은 함수는 T가 int고 Parameter는 int&&가 된다.
R-Value Reference를 사용하는 이유 중 하나는 소유권을 뺏어오는 목적이 있다. 위와 같은 L-Value와 R-Value를 둘 다 넘겨줄 수 있는 Template 함수에서 소유권은 어떻게 취득하는지 알아보자.
Perfect Forwarding
#include <iostream>
template<typename T>
void printVar(T&& a)
{
std::string localVar{ std::forward<T>(a) };
std::cout << localVar << std::endl;
}
int main()
{
std::string str = "pretending";
printVar(str);
printVar(std::move(str));
return 0;
}
main 함수에서 string 객체인 str을 선언하고 template 함수의 Argument로 각각 L-Value Reference와 R-Value Reference로 호출하였다. Template 함수 내에서 localVar이라는 string 객체를 만들어주고 std::forward <T>(a);로 초기화를 해주었는데 이때 std::forward <T>(a);에서 만약 a가 L-Value Reference라면 그대로 L-Vlaue Reference로 놔두게 되고 R-Vlaue Reference라면 그래도 R-Value Reference로 놔두게 된다.
Ref.
https://www.youtube.com/watch?v=Ifo2RtSzqvQ&list=PLDV-cCQnUlIb2oezNpNTmxiiX_NibMrlO&index=3
'Modern C++' 카테고리의 다른 글
[C++] Class, Aliasing, Variable Template (여러 템플릿들) (0) | 2021.08.27 |
---|---|
[C++] Template Instantiation (템블릿 인스턴스화) (0) | 2021.08.27 |
[C++] Template Intro. (템플릿) : Function Template (함수 템플릿) (0) | 2021.08.26 |
[C++] Weak Pointer (위크 포인터) : Circular Reference (순환 참조) 해결 (0) | 2021.08.25 |
[C++] Shared Pointer (쉐어드 포인터) : Circular Reference (순환 참조) (0) | 2021.08.25 |
[C++] Template Intro. (템플릿) : Function Template (함수 템플릿)
학기 중 Template을 이용하여 코드를 짜는 과제가 있었는데 정말 template을 어떻게 사용하는지 모르겠어서 진짜 이상하게 코드를 짰던 기억이 난다. (물론 코드는 짜서 점수는 받았지만...) 지금 다시 template을 제대로 처음부터 다시 공부를 해본다.
Template (템플릿)
Tempalate은 아래와 같이 많은 종류의 Template이 존재한다.
- Function template
- Class Template
- Alias Template
- Variable Template
Template이란 변수의 Type을 정해주지 않고 필요한 Type을 Compile-time에 정의를 해서 사용을 한다. 이러한 특성은 우리가 tyep 때문에 Function Overloading을 해야 할 경우 가장 유용하게 쓰인다.
뭔 뜻인지 이해가 안 될 수 있다. 코드를 통해서 이해를 해보자.
#include <iostream>
int add(int a, int b)
{
return a + b;
}
float add(float a, float b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
int main()
{
std::cout << add(1, 2);
std::cout << add(1.2f, 2.3f);
std::cout << add(1.3, 1.4);
}
위와 같이 두 수를 더해주는 함수를 만드려고 한다. integer타입, float타입 그리고 double타입에 대한 Funciton Overloading에 의해서 3개의 함수가 만들어져야 한다. 하지만 이렇게 타입이 더 늘어나게 되면 많은 함수가 만들어져야 하기 때문에 Template을 이용해서 함수를 만들 수 있다.
Function Template (함수 템플릿)
Template을 이용하여 추상적인 Type을 만들고 컴파일 타임에 각 타입에 맞는 함수가 생성된다고 생각하면 된다.
#include <iostream>
template<typename T>
T add(T a, T b)
{
return a + b;
}
int main()
{
std::cout << add<int>(1, 2);
std::cout << add<float>(1.2f, 2.3f);
std::cout << add<double>(1.3, 1.4);
}
위와 같이 template을 이용하여 함수를 만들게 되면 Function Overloading을 이용하여 만든 것보다 더 짧고 명확하게 코드를 짤 수 있다.
Template으로 만들어진 함수는 Compile-time에 정의된다. 이를 확인하기 위해 compiler explorer을 통해서 확인해 보자.
template으로 만들어진 함수만 어셈블리로 변 활했을 경우는 어떤 어셈블리 코드도 만들어지지 않는다.
이와 같이 원래는 code로만 존재를 하다가 main함수에서 해당 template function을 사용을 할 때 그 Type에 맞는 함수가 Compile이 되면서 함수가 만들어진다.
여기서 궁금한 것은 모든 자료형에 대해서 적용이 가능한가?
두 개의 const char*형의 + 연산을 한다고 가정을 해보자. 아래 코드와 같이 작성이 가능하다.
#include <iostream>
template<typename T>
T add(T a, T b)
{
return a + b;
}
int main()
{
std::cout << add<const char*>("aaa", "bbb");
}
위와 같은 코드를 컴파일이 안된다. 이유는 const char*형에 대해서 +연산이 정의되어있지 않기 때문이다. Template Function은 모든 것이 만들어져 있는 것이 아니라 넘겨지는 Type에 따라서 그때 Compile이 되게 된다.
이번 포스트는 Template에 대해서 간단하게 알아보았다.
Ref.
'Modern C++' 카테고리의 다른 글
[C++] Template Instantiation (템블릿 인스턴스화) (0) | 2021.08.27 |
---|---|
[C++] Template Type Deduction (템플릿 타입 추론) : Perfect Forwarding (0) | 2021.08.26 |
[C++] Weak Pointer (위크 포인터) : Circular Reference (순환 참조) 해결 (0) | 2021.08.25 |
[C++] Shared Pointer (쉐어드 포인터) : Circular Reference (순환 참조) (0) | 2021.08.25 |
[C++] Smart Pointer (스마트 포인터): Unique Pointer (유니크 포인터) (0) | 2021.08.23 |