본문 바로가기
C++/문법 정리

Chapter 05. 템플릿과 파일 시스템

by GameStudy 2022. 5. 7.

5.1 템플릿 프로그래밍

  5.1-1 템플릿이란,

    Note) 너무 많은 add() 함수 구현 코드

      만약 우리가 모든 primitive type에 대응하는

      add() 함수를 만든다고 해보자.

      그럼 아래와 같은 코드가 쏟아져 나옴.

      이를 좀 더 간편하게 하기 위해서 나온 기능이 템플릿.

char add(char op1, char op2);
int add(int op1, int op2);
float add(float op1, float op2);
...

 

    Def) 템플릿(Template)

      사용자가 템플릿 매개 변수에 대해 제공하는 인자를 기반으로

      컴파일 타임에 클래스 또는 함수 코드를 생성하는 구문.

 

    Note) 어떻게보면 복붙과 비슷함. 

      ex. 인라인 함수, 매크로 함수, ...

      클래스와도 비슷함. 청사진을 만들어 두고

      개체 생성 요청이 올 때마다 찍어내는 것.

      템플릿은 이들보다 좀 더 상위 개념임.

 

  5.1-2 함수 템플릿

    Note) 함수 템플릿을 호출할 때 템플릿 매개변수를 생략할 수도 있음.

      특히 min(), max() 함수 같은 경우,

      템플릿 매개변수에 대한 인자를 생략하는 경우가 다반사.

 

    Ex05010101)

<hide/>

// main.cpp

#include <iostream>

using namespace std;

template <typename T>
T add(T op1, T op2);

int main(void)
{
    cout << add<double>(2.3, 4.1) << endl;
    cout << add<short>(2, 4) << endl;
    cout << add(2, 4) << endl;

    return 0;
}

template<typename T>
T add(T op1, T op2)
{
    return op1 + op2;
}

 

    Note) typename Vs. class

      차이가 없음. 둘 다 써도됨.

      다만 typename 이라고 적을 때가 많음.

 

    Ex05010102)

<hide/>

// main.cpp

#include <iostream>

using namespace std;

template <class T>
T sub(T op1, T op2);

int main(void)
{
    cout << sub<double>(2.3, 4.1) << endl;
    cout << sub<short>(2, 4) << endl;
    cout << sub(2, 4) << endl;

    return 0;
}

template<class T>
T sub(T op1, T op2)
{
    return op1 - op2;
}

 

    Note) 템플릿은 내부적으로 어떻게 동작할까?

      템플릿을 인스턴스화 할 때마다 컴파일러가

      내부적으로 코드를 생성해줌.

 

    Note) Compile-time polymorphism

      일단 런타임 다형성에 대해서 되짚어보자.

      가상함수는 런타임 중에 점프해야하므로 좀 더 느렸음.

      그럼 컴파일 타임에 다 해버리면 빠르지 않을까?

      해서 템플릿 프로그래밍이 좀 더 흥했음.

      런타임 속도가 더 빨라질 가능성이 있음.

 

    Note) 물론 템플릿 프로그래밍이 완벽하진 않음.

      1. 템플릿 매개변수에 대해 전달한 인자의 개수만큼 

        템플릿 코드를 생성해 주느라고 컴파일 시간이 증가.

      2. 생성된 코드 때문에 헤더 파일이 커짐.

        헤더 파일은 여기저기서 include 되고, 

        덩달아 include한 헤더파일도 커져서 컴파일 시간 증가.

 

    Note) 템플릿 함수의 구현을 cpp 파일에서 해도 될까?

      안됨. 컴파일 타임에 알아야하기 때문에.

      템플릿의 모든 구현체는 헤더 파일에 들어가야함.

 

  5.1-3 클래스 템플릿

    Note) 클래스 템플릿과 static 멤버의 관계

      클래스 템플릿을 상속 받는 경우와 일반 클래스를 상속 받는다고 해보자.

      클래스 템플릿의 static 멤버는 상속될 시에 상속 받은 클래스 개수만큼 생김.

      그러나 일반 클래스의 static 멤버는 상속이 된다해도 1개만 존재함.

      따라서 특정 static 멤버가 자식 클래스마다 생성되었으면 좋겠다면

      클래스 템플릿을 이용한다.

  

    Ex05010301)

<hide/>

// MyVector.h

#pragma once

template <typename T>
class MyVector
{
public:
    MyVector(const T& x, const T& y)
        : mX(x)
        , mY(y)
    {}
    
    virtual ~MyVector() {}

    const T& GetX() const { return mX; }
    const T& GetY() const { return mY; }

private:
    T mX;
    T mY;

};
<hide/>

// main.cpp

#include <iostream>

#include "MyVector.h"

using namespace std;

int main(void)
{
    MyVector<int> v = MyVector<int>(2, 3);

    cout << v.GetX() << ", " << v.GetY() << endl;

    return 0;
}

 

    Note) 클래스 템플릿은 템플릿 매개변수를 생략하면 안됨.

      함수 템플릿은 함수 매개변수에 대한 인자값으로

      컴파일러가 자료형을 추측할 수 있었음.

      그러나 클래스 템플릿은 전혀 예측할 수 없음.

add<int>(3, 2)   // 컴파일 성공
add(3, 2);       // 컴파일 성공

MyVector<int> v; // 컴파일 성공
MyVector v;      // 컴파일 실패

 

    Note) 클래스에 정적 배열을 멤버로 갖고 싶다면?

      가끔 배열 멤버를 쓰고 싶은데, 

      배열 크기를 정할 때 난감한 경우가 많음.

      미리 결정되어져야 하는데, 작게잡기도 크게 잡기도 난감.

      그럴 때 템플릿 프로그래밍을 쓰면 좋음.

 

    Ex05010302)

<hide/>

// MyArray.h

#pragma once

#include <iostream>

using namespace std;

template <typename T, size_t size>
class MyArray
{
public:
    MyArray();

    void Add(const int& data);

    void Print()
    {
        for (size_t i = 0; i < size; ++i) { cout << mArray[i] << ' '; }
        cout << endl;
    }
    
private:
    T mArray[size];
    size_t mIdx;

};

template<typename T, size_t size>
inline MyArray<T, size>::MyArray()
    : mArray{ 0, }
    , mIdx(0)
{
}

template<typename T, size_t size>
inline void MyArray<T, size>::Add(const int& data)
{
    if (size <= mIdx) { return; }

    mArray[mIdx++] = data;
}
<hide/>

// main.cpp

#include <iostream>

#include "MyArray.h"

using namespace std;

int main(void)
{
    MyArray<int, 10> arr;

    arr.Print();

    arr.Add(1);
    arr.Add(3);
    arr.Add(4);

    arr.Print();

    return 0;
}

 

    Note) 템플릿 매개변수 자료형을 두 개 쓸수도 있음.

 

    Ex05010303)

<hide/>

// MyPair.h

#pragma once

template <typename T1, typename T2>
class MyPair
{
public:
    MyPair(const T1& first, const T2& second)
        : mFirst(first)
        , mSecond(second)
    {
    }

    const T1& GetFirst() const { return mFirst; }
    const T2& GetSecond() const { return mSecond; }

private:
    T1 mFirst;
    T2 mSecond;

};
<hide/>

// main.cpp

#include <iostream>

#include "MyPair.h"

using namespace std;

int main(void)
{
    MyPair<string, int> score = MyPair<string, int>("Park", 100);

    cout << score.GetFirst() << ": " << score.GetSecond() << endl;    

    return 0;
}

 

    Note) C++ STL은 템플릿 프로그래밍으로 작성됨.

      C++ STL의 컨테이너를 돌게하는 원동력이 템플릿.

      대표적인 게임엔진인 언리얼 엔진은 

      자체적인 컨테이너를 템플릿 프로그래밍으로 구현함.

      ex. TArray, TMap

 

 

5.2 템플릿 특수화

  5.2-1 템플릿 특수화란,

    Note) 템플릿 프로그래밍은 결국 일반화(Generic)를 의미함.

      근데 가끔 한 두개 정도가 일반적이지 않을때가 있음.

      이를 커버하기 위해서 템플릿 특수화가 필요해짐.

 

    Note) 템플릿 특수화 예시

      C++ STL 컨테이너 중, vector가 있음.

      std::vector<bool> 특수화의 경우, 할만한 가치가 있음.

      불리언은 1bit만 있어도 하나를 저장할 수 있으니

      굳이 4바이트를 쓸 필욘 없음. 잘하면 32배의 효용이 있는 셈.

      특히나 메모리가 부족할 수 있는 임베디드 시스템에선 효율적임.

 

  5.2-2 함수 템플릿 특수화

 

 

    Ex05020201)

<hide/>

// main.cpp

#include <iostream>

using namespace std;

template <typename T, typename EXP>
T power(const T& value, EXP exponent)
{
    T res = 1;
    while (exponent-- > 0) { res *= value; }

    return res;
}

template <>
float power(const float& value, float exponent)
{
    return std::powf(value, exponent);
}

int main(void)
{
    cout << power(2, 10) << endl;
    cout << power(2.f, 2.5f) << endl;

    return 0;
}

 

 

  5.2-3 클래스 템플릿 특수화

    Ex05020301)

<hide/>

// MyArray.h

#pragma once

#include <iostream>

using namespace std;

template <typename T, size_t size>
class MyArray
{
public:
    MyArray();

    void Add(const int& data);

    void Print()
    {
        for (size_t i = 0; i < size; ++i) { cout << mArray[i] << ' '; }
        cout << endl;
    }
    
private:
    T mArray[size];
    size_t mIdx;

};

template<typename T, size_t size>
inline MyArray<T, size>::MyArray()
    : mArray{ 0, }
    , mIdx(0)
{
}

template<typename T, size_t size>
inline void MyArray<T, size>::Add(const int& data)
{
    if (size <= mIdx) { return; }

    mArray[mIdx++] = data;
}

template <size_t size>
class MyArray<bool, size>
{
public:
    MyArray()
        : mArray{ 0, }
        , mIdx(0)
    {}

    void Add(const bool& data)
    {
        if (size <= mIdx) { return; }

        mArray[mIdx++] = data;
    } 

    void Print()
    {
        for (size_t i = 0; i < size; ++i) { cout << mArray[i] << ' '; }
        cout << endl;
    }

private:
    bool mArray[size];
    size_t mIdx;
};
<hide/>

// main.cpp

#include <iostream>

#include "MyArray.h"

using namespace std;

int main(void)
{
    MyArray<int, 10> arr;

    arr.Print();

    arr.Add(1);
    arr.Add(3);
    arr.Add(4);

    arr.Print();


    MyArray<bool, 5> bitflags;

    bitflags.Add(true);
    bitflags.Add(true);

    bitflags.Print();

    return 0;
}

 

    Note) 템플릿 특수화의 종류

      1. 전체 템플릿 특수화: 템플릿 매개변수 목록이 비어있음.

template<typename VAL, typename EXP>
VAL power(const VAL& value, EXP exponent) { ... }       // 모든 자료형을 받도록 일반화(Generic)된 power() 함수 템플릿

template<>
float power(const float& value, float exponent) { ... } // float 자료형을 받도록 특수화된 power() 함수 템플릿

      2. 부분 템플릿 특수화

template <typename T, typename Allocator>
class std::vector<T, Allocator> { ... }    // 모든 자료형을 받도록 일반화된 vector 클래스 템플릿

template <typename Allocator>
class std::vector<bool, Allocator> { ... } // bool 자료형을 받도록 특수화된 vector 클래스 템플릿

 

    Note) 템플릿 프로그래밍의 베스트 프렉티스

      1. 함수 템플릿은 최대한 짧게 유지하자.

        안써도 될때는 그냥 함수로 작성하는 것도 좋음.

        이 함수가 인라인 될 수도 있음.

      2. 컨테이너의 경우, 템플릿 프로그래밍이 아주 적합함.

        아주 다양한 자료형들을 저장할 수 있음.

      3. 컨테이너가 아닌 경우, 3~4개 자료형 이상 자료형을

        다룰 때만 템플릿 프로그래밍으로 작성하자.

        두 가지 이하라면 그냥 클래스 2개를 만드는게 올바른 방법.

 

 

5.3 파일 시스템

  5.3-1 파일 시스템 연산

    Note) 파일 시스템은 C++17의 새로운 라이브러리.

      이전 표준에서는 파일 시스템과 아래의 구성요소에 대해

      연산할 방법이 없었음.

      경로 / 일반 파일 / 디렉터리

 

    Note) 따라서 비쥬얼 스튜디오에서 현재 프로젝트 우클릭 -> 속성

      일반 탭에 C++ 언어 표준을 "ISO C++ 17 표준"으로 설정해야함.

 

    Note) 파일 읽기/쓰기에 관한 라이브러리가 아님.

      파일 속성 변경, 디렉터리 순회, 파일 복사 등에 관한 라이브러리.

      이 모든걸 std::filesystem으로 할 수 있음.

 

    Note) 파일 시스템 연산

      - 플랫폼 공통적인 방법으로 경로 합치기

      - 파일과 디렉터리를 복사, 이름 바꾸기, 삭제

        하위 폴더까지 싹 다 복사하는 재귀적 복사가 있고,

        비 재귀적으로 복사하는 것도 옵션으로 설정 가능.

      - 디렉터리에서 파일 / 디렉터리 목록 가져오기

      - 파일 권한 읽기 및 설정

      - 파일 상태 읽기 및 설정

      이 링크에서 추가적인 정보를 얻을 수 있음.

 

    Ex05030101)

<hide/>

// main.cpp

#include <iostream>
#include <filesystem>

using namespace std;

namespace fs = filesystem;

int main(void)
{
    fs::path path1 = "C:\\Lecture";
    fs::path path2 = "examples";
    path1 /= path2;                 // 하위 폴더로 경로 덧붙힘. 리눅스와 윈도우즈에서 모두 정상작동.
    cout << path1 << endl;

    fs::path path3 = "C:\\Lecture";
    fs::path path4 = "examples";
    path3 += path4;                 // 문자열 이어 붙이기.
    cout << path3 << endl;

    return 0;
}

 

    Ex05030102)

<hide/>

// main.cpp

#include <iostream>
#include <filesystem>

using namespace std;

namespace fs = filesystem;

int main(void)
{
    fs::path originalTextPath = "C:\\Examples\\MyRank.txt";
    fs::path copiedTextPath = "C:\\Examples\\[Copied]MyRank.txt";
    fs::copy(originalTextPath, copiedTextPath);                                   // 파일 복사

    fs::path originalForderPath = "C:\\Examples\\Forder1";
    fs::path copiedForderPath1 = "C:\\Examples\\[Copied]Forder1";
    fs::copy(originalForderPath, copiedForderPath1);                              // 비재귀적 디렉터리 복사
    
    fs::path copiedForderPath2 = "C:\\Examples\\[CopiedRecursively]Forder1";
    fs::copy(originalForderPath, copiedForderPath2, fs::copy_options::recursive); // 재귀적 디렉터리 복사
    
    return 0;
}

 

    Ex05030103)

<hide/>

// main.cpp

#include <iostream>
#include <filesystem>

using namespace std;

namespace fs = filesystem;

int main(void)
{
    fs::path filePath = "C:\\Examples\\MyRank.txt";
    fs::path renamePath = "C:\\Examples\\Forder1\\MyRank.txt";
    fs::rename(filePath, renamePath); // 파일 또는 디렉터리의 이름 변경 혹은 이동
    
    return 0;
}

 

    Ex05030104)

<hide/>

// main.cpp

#include <iostream>
#include <filesystem>

using namespace std;

namespace fs = filesystem;

int main(void)
{
    fs::path currentPath = fs::current_path();
    fs::create_directories(currentPath / "Data"); // 프로젝트 폴더에 새 폴더 생성
    fs::remove_all(currentPath / "Data");         // 폴더 안에 있는 내용물을 제거
    fs::remove(currentPath / "Data");             // 폴더 제거
    
    
    return 0;
}

 

    Ex05030105)

<hide/>

// main.cpp

#include <iostream>
#include <filesystem>

using namespace std;

namespace fs = filesystem;

int main(void)
{
    for (auto& path : fs::recursive_directory_iterator("C:\\Examples\\FileSystemExamples")) { cout << path << endl; }
      // 디렉터리의 요소들을 순회하고, 재귀적으로 하위 디렉터리의 요소들을 순회함.
      
    return 0;
}

 

    Ex05030106)

<hide/>

// main.cpp

#include <iostream>
#include <filesystem>

using namespace std;

namespace fs = filesystem;

void printPermission(fs::perms permission)
{
    cout << ((permission & fs::perms::owner_read) != fs::perms::none ? 'r' : '-')
         << ((permission & fs::perms::owner_write) != fs::perms::none ? 'w' : '-')
         << ((permission & fs::perms::owner_exec) != fs::perms::none ? 'x' : '-')
         << ((permission & fs::perms::group_read) != fs::perms::none ? 'r' : '-')
         << ((permission & fs::perms::group_write) != fs::perms::none ? 'w' : '-')
         << ((permission & fs::perms::group_exec) != fs::perms::none ? 'x' : '-')
         << ((permission & fs::perms::others_read) != fs::perms::none ? 'r' : '-')
         << ((permission & fs::perms::others_write) != fs::perms::none ? 'w' : '-')
         << ((permission & fs::perms::others_exec) != fs::perms::none ? 'x' : '-')
         << endl;
}

int main(void)
{
    fs::path filePath = "C:\\Examples\\Folder1\\CopiedMyRank.txt";
    printPermission(fs::status(filePath).permissions());
      // status() 함수는 파일의 상태를 반환함.
      // 디렉터리, 블록 파일(block file), FIFO, 소켓(socket), ...
      // permissions() 함수는 파일의 권한을 반환함.
      // 소유자의 읽기, 쓰기, 실행
      // 소유자와 같은 그룹의 읽기, 쓰기, 실행
      // 외부인의 읽기, 쓰기, 실행

    return 0;
}

 

    Ex05030107)

<hide/>

// main.cpp

#include <iostream>
#include <filesystem>

using namespace std;

namespace fs = filesystem;

int main(void)
{
    static const char* FOLDER_NAME = "FileSystemExample";
    static const char* COPIED_FOLDER_NAME = "Copied";
    static const char* FILE_NAME = "test.txt";
    static const char* RENAMED_FILE_NAME = "renamed_text.txt";

    const fs::path WORKING_DIRECTORY = fs::current_path() / FOLDER_NAME;
    const fs::path COPIED_FOLDER_DIRECTORY = WORKING_DIRECTORY / COPIED_FOLDER_NAME;
    fs::create_directory(COPIED_FOLDER_DIRECTORY);
    fs::path originalFileLocation = WORKING_DIRECTORY / FILE_NAME;
    cout << originalFileLocation << endl;
    fs::path copiedFileLocation = COPIED_FOLDER_DIRECTORY / FILE_NAME;
    fs::copy(originalFileLocation, copiedFileLocation);
    fs::path renamedFileLocation = WORKING_DIRECTORY / RENAMED_FILE_NAME;
    fs::rename(originalFileLocation, renamedFileLocation);
    for (auto& path : fs::recursive_directory_iterator(WORKING_DIRECTORY))
    {
        std::cout << path << std::endl;
    }
    fs::copy(copiedFileLocation, originalFileLocation);
    fs::remove(renamedFileLocation);
    fs::remove_all(COPIED_FOLDER_DIRECTORY);

    return 0;
}

 

 

'C++ > 문법 정리' 카테고리의 다른 글

Chapter 07. 스마트 포인터  (0) 2022.05.08
Chapter 06. C++11/14/17  (0) 2022.05.08
Chapter 04. 캐스팅과 인라인  (0) 2022.05.06
Chapter 03. 상속과 다형성  (0) 2022.05.05
Chapter 02. 클래스  (0) 2022.05.01

댓글