Templates

함수나 클래스를 정의할 때 다룰 자료형(Data Types)을 구체적으로 지정하지 않고 일반화된 형식(Generic Types)으로 지정한다. 이 때 일반화된 형식으로 지정한 자료형을 Template Parameters라고 한다

함수의 리턴 자료형, 파라미터 자료형, 지역변수 자료형만 다르고 함수의 기능이 같은 경우 여러개의 함수를 선언할 필요 없이 함수 템플릿을 사용하면 된다.


클래스 안에서 사용되는 변수의 자료형을 일반화하여 선언하려면 함수 템플릿이 아니라 클래스 템플릿을 사용하면 된다.


코딩시 템플릿으로 정의된 함수를 호출할 때나 템플릿을 정의된 클래스의 인스턴스를 생성하는 코드 부분에서 Template Parameter에 구체적인 자료형을 지정해주면 컴파일러가 해당 함수나 클래스의 템플릿을 사용하여 구체적인 함수나 클래스를 생성하고 컴파일하게 된다.


자료형이 다르다는 이유로 여러개의 함수를 생성하거나 오버로드하는 불편을 덜고 컴파일러가 대신하도록 할 수 있다. 


C++의 STL에 포함된 자료구조나 알고리듬을 잘 이해하고 이용하려면 템플릿에 대한 개념이 필수적이다



함수 템플릿 선언 형식

class, typename 이라는 키워드를 사용할 수 있고 2개의 의미는 완전히 동일하다. 새로운 표준에서 typename 이라는 키워드를 도입했다

template <class identifier> function_declaration;

template <typename identifier> function_declaration;



함수 템플릿 선언 예

template <typename myType>

myType GetMax (myType a, myType b) 

{

    return (a>b?a:b);

}



함수 템플릿을 사용하여 구체적 자료형을 지정하고 함수를 호출하는 형식

function_name <type> (parameters);



함수 템플릿의 템플릿 파라미터(myType)에 실제 자료형을 지정하고 함수를 호출하는 예

int max_int            = GetMax<int>(2, 3);

double max_double = GetMax<double>(2.1, 3.2);



함수 템플릿을 실제로 사용하는 예

#include <iostream>

using namespace std;


template <typename T>

GetMax(T a, T b) 

{

T result;

result = (a > b) ? a : b;

return (result);

}


int main() 

{

int i = 5, j = 6, k;

float m = 10.3, n = 5.5, p;

k = GetMax<int>(i, j);

p = GetMax<float>(m, n);

cout << k << endl;

cout << p << endl;

return 0;

}



템플릿 파라미터(<typename T>)의 실제 자료형을 결정하는 위의 <int>, <float> 형식을 생략해도 아규먼트 자료형을 참조하여 자동으로 해당 자료형으로 구체화된 함수가 생성되고 호출된다

k = GetMax(i, j);

p = GetMax(m, n);



템플릿 파라미터가 다수개인 경우

template<typename T1, typename T2

double Add(T1 a, T2 b);



템플릿에 const와 참조(&)형을 사용하는 예

template <typename T

const T& Max(const T& a, const T& b )

{

return a > b ? a : b;

}



클래스 템플릿의 예

#include <iostream>

using namespace std;


template <typename T>

class mypair 

{

T values[2];

public:

mypair(T first, T second)

{

values[0] = first; values[1] = second;

}

};


int main() 

{

mypair<double> pair(1.2, 3.4);


return 0;

}



위의 예제에서 클래스 선언부 밖에 멤버함수를 정의하는 경우에는 각 멤버함수 정의부마다 클래스 템플릿 선언이 선행되어야 한다

template <typename T>

class mypair 

{

    T a, b;

  public:

    mypair (T first, T second)

    {

        a=first; b=second;

    }

    T getmax ();

};


// 선언부 밖에 함수 정의를 두는 경우

template <typename T>

T mypair<T>::getmax ()

{

  T retval;

  retval = a>b? a : b;

  return retval;

}




템플릿 전문화(Template Specialization)

템플릿 파라미터에 특정 자료형이 지정되는 경우 함수 안에서 해당 자료형의 처리방법이 달라서 템플릿에서 처리할 수 없는 경우에는 특별한 처리방법을 적용해야 한다. 이런 경우에 템플릿 전문화 방법을 사용하여 함수 템플릿을 오버라이드하면 된다



함수 템플릿 전문화의 예

#include <iostream>


using namespace std;


// 일반 함수 템플릿

template<typename T>

T Add(T a, T b)

{

T result = a+b;

return result;

}


// 함수 템플릿 전문화(위의 함수 템플릿을 오버라이드 한 것)

// 템플릿 파라미터가 모두 double 자료형이라면 이 함수가 우선적으로 호출된다

template<>

double Add(double a, double b)

{

double result = a+b;

return result;

}


int main() 

{

int sum = Add<int>(2, 3);                       // <int> 는 생략가능

cout << "정수 합계:" << sum << endl;


double dsum = Add<double>(2.1, 3.2);    // <double> 은 생략가능

cout << "실수 합계:" << dsum << endl;


return 0;

}



클래스 템플릿 전문화 선언 형식

template <typename T> class mycontainer { ... };     // 일반 클래스 템플릿 선언

template <> class mycontainer <char> { ... };   // 클래스 템플릿 전문화(위의 클래스 템플릿을 오버라이드함)



클래스 템플릿 전문화 선언 및 사용 예

// template specialization

#include <iostream>

using namespace std;


// class template:

template <typename T>

class mycontainer {

    T element;

  public:

    mycontainer (T arg) {element=arg;}

    T increase () {return ++element;}

};


// 클래스 템플릿 전문화: 기본 템플릿 클래스의 모든 멤버를 빠짐없이 다시 정의해야 한다

template <>

class mycontainer <char> {

    char element;

  public:

    mycontainer (char arg) {element=arg;}

    char uppercase ()

    {

      if ((element>='a')&&(element<='z'))

      element+='A'-'a';

      return element;

    }

};


int main () {

  mycontainer<int> myint (7);       // 템플릿 파라미터에 전달할 <int> 를 생략하면 안됨

  mycontainer<char> mychar ('j');  // 클래스 템플릿 전문화 사용 <char>를 생략하면 안됨

  cout << myint.increase() << endl;

  cout << mychar.uppercase() << endl;

  return 0;

}



Non-type 파라미터를 가진 템플릿

함수 템플릿이나 클래스 템플릿은 자료형을 일반화하여 선언하고 함수의 호출부나 클래스 객체 생성시에 실제 자료형을 지정하여 실제 함수나 클래스를 생성하는 수단으로 자주 사용된다. 그러나 자료형을 일반화하는 것이 아니라 자료형은 결정되어 있고 실제 값을 템플릿 파라미터로 지정하는 방법도 지원되며 이 경우 자료형(Type)을 지정하는 것이 아니라 실제 값을 지정하는 것이므로 Non-type 파라미터 템플릿이라고 한다



Non-type 파라미터 템플릿의 선언 및 사용 예


#include <iostream>

using namespace std;


template <typename T, int N// Non-type 파라미터(int N) 선언

class mysequence {

    T memblock [N];           // Non-type 파라미터 사용

  public:

    void setmember (int x, T value);

    T getmember (int x);

};


template <typename T, int N>

void mysequence<T,N>::setmember (int x, T value) {

  memblock[x]=value;

}


template <typename T, int N>

T mysequence<T,N>::getmember (int x) {

  return memblock[x];

}


int main () {

  mysequence <int,5> myints;            // Non-type 파라미터에 실제 값 5를 지정함

  mysequence <double,5> myfloats;

  myints.setmember (0,100);

  myfloats.setmember (3,3.1416);

  cout << myints.getmember(0) << '\n';

  cout << myfloats.getmember(3) << '\n';

  return 0;

}



템플릿 파라미터에도 디폴트 값을 지정할 수 있다

template <typename T=char, int N=10>

class mysequence {..};



위에 선언된 클래스 템플릿을 이용하여 객체를 생성하는 경우

아래의 2가지 템플릿 사용법은 완전히 동일한 결과를 갖는다

mysequence<> myseq;

mysequence<char,10> myseq;




부분 템플릿 전문화의 예

template< typename T1, typename T2 >

class Test

{

  public:

  T1 Add( T1 a, T2 b )

  {

     cout << "일반 클래스 템플릿." << endl;

     return a;

  }

};


template< typename T1 >

class Test<T1,float>

{

  public:

  T1 Add( T1 a, float b )

  {

     cout << "부분 전문화 템플릿." << endl;

     return a;

  }

};



// 위에 선언된 템플릿 클래스 부분 전문화 사용 예

void main()

{

   Test<int, int> test1;

   test1.Add( 2, 3 );

   Test<int, float> test2;

   test2.Add( 2, 5.8f );

}



템플릿 파라미터에 포인터 형을 사용할 경우의 템플릿 전문화 예

template< typename T >

class TestP

{

  public:

  void Add()

  {

    cout << "일반 템플릿." << endl;

  }

};


// T를 T*로 부분 전문화

template< typename T >

class TestP<T*>

{

  public:

  void Add()

  {

    cout << "포인터를 사용한 부분 전문화 템플릿." << endl;

  }

};


#include <iostream>

using namespace std;

void main()

{

  TestP<int> test1;

  test1.Add();

  TestP<int*> test2;

  test2.Add();

}


Posted by cwisky

VS 2015에서는 문자열 변수 선언시 아래의 문장은 아무런 문제가 없이 실행된다.


char * str = "Hello";


그러나 VS 2017에서는 에디터에서 상수 문자열을 일반 문자열로 형변환할 수 없다는 컴파일러 오류가 발생한다

VisualStudio 2017 15.5 버전부터는 모든 솔루션에 [준수모드]가 디폴트로 Permissive 상태로 설정되어 있기 때문에 위의 문장에서 오류가 발생한다. 


"const char *" 형식의 값을 사용하여 "char *" 형식의 엔티티를 초기화할 수 없습니다"



 VS 2017에서는 더 엄격한 문법 준수 모드로 설정되어 있기 때문이다

그러므로 아래와 같은 방법으로 표현해야 한다

const char * str = "Hello" 



이를 원래의 설정으로 변경할 필요가 있다면 아래의 절차에 따라서 설정하면 된다


프로젝트 > [프로젝트이름] 속성 > C/C++ > 언어 > 준수모드 > 아니오 선택 > 적용 > 확인



Posted by cwisky
Unreal C++/C++ Pawn2018. 8. 22. 18:49

언리얼 엔진에서 C++를 이용한 Pawn 클래스 기반으로 커스텀 폰 클래스 정의하기


언리얼 엔진에서 Actor와 Pawn이 다른 점은 Pawn은 AddMovementInput()함수를 사용하여 전후좌우상하로 이동할 수 있으므로 키보드의 입력을 Pawn에 반영할 수 있다는 것이다


여기서는 C++를 사용하여 Pawn 기반의 MyPawn 클래스를 생성하고 키보드(W, A, S, D)키를 누를 때마다 해당 폰이 전후좌우로 이동하는 기능을 작성해보고자 한다


C++ 프로젝트 생성

Pawn 기반 C++ 클래스 생성(MyPawn)



.h 헤더파일 ( 적색 코드는 추가한 내용)

// Fill out your copyright notice in the Description page of Project Settings.


#pragma once


#include "CoreMinimal.h"

#include "GameFramework/Pawn.h"

#include "Components/InputComponent.h"

#include "GameFramework/FloatingPawnMovement.h"

#include "Camera/CameraComponent.h"

#include "MyPawn.generated.h"


UCLASS()

class CPP_PAWN_TEST_API AMyPawn : public APawn

{

GENERATED_BODY()


public:

// Sets default values for this pawn's properties

AMyPawn();


UPROPERTY(EditAnywhere)

UStaticMeshComponent* Mesh;


UPROPERTY(EditAnywhere)

UCameraComponent* Camera;


protected:

// Called when the game starts or when spawned

virtual void BeginPlay() override;


public:

// Called every frame

virtual void Tick(float DeltaTime) override;


// Called to bind functionality to input

virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;


void MoveForward(float Value);

void MoveRight(float Value);

};



.cpp 파일 (적색 코드는 추가된 내용)

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyPawn.h"


// Sets default values

AMyPawn::AMyPawn()

{

  // Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.

PrimaryActorTick.bCanEverTick = true;


Mesh = CreateDefaultSubobject<UStaticMeshComponent>("MyMesh");

CreateDefaultSubobject<UFloatingPawnMovement>("PawnMovement");

Camera = CreateDefaultSubobject<UCameraComponent>("MyCamera");

}


// Called when the game starts or when spawned

void AMyPawn::BeginPlay()

{

Super::BeginPlay();

}


// Called every frame

void AMyPawn::Tick(float DeltaTime)

{

Super::Tick(DeltaTime);

}


// Called to bind functionality to input

void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)

{

Super::SetupPlayerInputComponent(PlayerInputComponent);

PlayerInputComponent->BindAxis("MoveForward", this, &AMyPawn::MoveForward);

PlayerInputComponent->BindAxis("MoveRight", this, &AMyPawn::MoveRight);

}


void AMyPawn::MoveForward(float Value)

{

AddMovementInput(GetActorForwardVector(), Value);

}


void AMyPawn::MoveRight(float Value)

{

AddMovementInput(GetActorRightVector(), Value);

}



Axis Mappings

언리얼 에디터에서 Edit > 프로젝트 세팅 > 엔진 > 입력 > Axis Mappings

MoveForward 라는 바인딩 이름으로 W(1.0), S(-1.0) 등록

MoveRight 라는 바인딩 이름으로  D(1.0), A(-1.0) 키를 등록



실행 테스트

완성된 C++ MyPawn 클래스를 Content Browser에서 드래그하여 뷰포트에 놓는다

컴포넌트로 포함된 카메라의 위치와 각도를 언리얼 에디터에서 변경하려면 Camera 변수에 매크로UFUNCTION(EditAnywhere0 를 설정해 주어야 한다

뷰포트에서 MyPawn을 선택하고 [디테일] 뷰에서 Mesh 변수를 선택한 후 Static Mesh 패널에서 Cube 등 임의의 메시를 선택하여 설정한다


게임을 실행하여 W, S, A, D 키를 이용하여 이동할 수 있는지 확인한다

만약 카메라만 이동하고 메시가 이동하지 않으면, Mesh 변수를 선택하고 [디테일]뷰의 Pawn 패널에서 Auto Possess Player 항목에 Disabled 가 아닌 Player0를 설정하면 된다


Posted by cwisky