많이 사용하는 모던 C++ 키워드

개발까마귀·2023년 4월 2일
0
post-thumbnail

서문

C++ 언어는 C++11 이 발표되기 전까지 오랜기간동안 정체되어 있었다. 프로그래머가 하나부터 열까지 관리해야되서 사소한 실수(Mistake)에 프로그램이 죽는 대형사고가 빈번했다. 이런 특성 때문에 생산성에서 많이 외면 당한 부분이 많다. 하지만 최근 C++은 많이 발전하고 편리(메모리는 아직...)해졌다. 고전 C++ 에 비해 많은 것이 바뀐 모던 C++ 에서 필자가 자주 사용하는 키워드를 소개해보려고 한다.

auto

필자가 애정하는 키워드다. 게임컨텐츠의 데이터 혹은 메모리에 로드하는 리소스 관리를 위해 필연적으로 컨테이너를 사용하게 되는데, 데이터의 구조에 따라 단순하게 unordered_map 이나 vector, list 로 끝나지만, 복잡한 데이터라면 필연적으로 여러개의 컨테이너 구조를 복합적으로 사용하게 된다.
예를 들어 아래와 같은 코드를 보자.

#include <unordered_map>
#include <vector>

class GameData;
template <class T> void SafeDelete(T * p)
{
    if(p != NULL)
    {
        delete p;
        p = NULL;
    }
}

int main()
{
    std::unordered_map<int, std::vector<GameData *>> * gameDatas = new std::unordered_map<int, std::vector<GameData *>>();
    
    //...
    int gameDataKey = 0;
    std::unordered_map<int, std::vector<GameData *>>::iterator findIter = gameDatas->find(gameDataKey);
    if(findIter != gameDatas->end())
    {
        //...
    }

    SafeDelete(gameDatas);
    return 0;
}

이 코드는 int, vector<GameData *> 을 Key-Value 타입으로 게임데이터를 가지고 있는 구조다.

std::unordered_map<int, std::vector<GameData *>>

이 데이터 타입을 보라. 흉악한 놈이다. 데이터를 찾기 위해 find() 를 호출하거나, for문 한번 돌리면 코드 지옥이 펼쳐진다. 이런 상황에 auto 키워드는 한줄기 빛과 같다. auto 키워드를 사용하면 코드는 다음과 같이 단순해진다.

#include <unordered_map>
#include <vector>

class GameData;
template <class T> void SafeDelete(T * p)
{
    if(p != NULL)
    {
        delete p;
        p = NULL;
    }
}

int main()
{
    auto * gameDatas = new std::unordered_map<int, std::vector<GameData *>>();
    
    //...
    auto gameDataKey = 0;
    auto findIter = gameDatas->find(gameDataKey);
    if(findIter != gameDatas->end())
    {
        //...
    }

    SafeDelete(gameDatas);
    return 0;
}

로컬 변수들이 아름답게 정리되었다. 그렇다면 auto 키워드는 어떻게 사용해야 될까? 결론부터 말하면 템플릿(template)을 사용하듯이 사용하면된다. 포인터 타입의 변수라면 auto * 로 사용하고, 레퍼런스라면 auto &과 같이 사용하는 데이터 타입에 맞춰서 사용하면된다.
그렇다면....템플릿과 다른것은 무엇일까?

가장 큰 차이점은 중괄호({})를 사용한 초기화 과정에서 차이가 있다. 고전 C++ 의 경우 구조체, 배열, 클래스에서 사용했는데, 모던 C++ 로 확장되며 std::Initializer_list 로 중괄호({})초기화를 지원한다. 이 경우 auto 키워드로 바인딩할 경우 auto 키워드로 선언된 변수는 std::Initializer_list 타입으로 추론된다.

auto temp = { 1, 2, 3}; // std::Initializer_list< int> 타입으로 추론됨

이 과정에서 auto 는 두번의 추론이 이루어지는데 템플릿은 이러한 추론은 에러를 내뿜는다. 궁금하면 템플릿 매개변수(parameter)를 가지는 함수 만들어서 초기화 해보라. 그럼 그 외는??

auto 를 리턴하는 함수의 경우 템플릿과 완전히 동일하다. 람다함수의 매개변수로 사용할 경우도 템플릿과 완전히 동일하다.

override / final

C++ 으로 개발을 진행하면 클래스(class) 없이는 너무 힘들다. 클래스에서 자주 사용하는 기능은 무엇일까? 상속이 아닐까 싶다. 상속으로 목적과 기능을 기준으로 구분하고 관리하고 사용하는 것이 너무 편리하다. 그 과정에서 빛을 발하는 것은 역시 오버라이딩(overriding) 이 아닐까 싶다.

template <class T> void SafeDelete(T * p)
{
    if(p != nullptr)
    {
        delete p;
        p = nullptr;
    }
}

class BaseEnchant
{
public:
    virtual float UpdateEnchantRate() = 0;
};

class WeaponEnchanter : public BaseEnchant
{
public:
    virtual float UpdateEnchantRate() override
    {
        //..무기 강화 확률 구함
        float enchantRate = 0.0f;
        //..
        return enchantRate;
    }
};

class WeaponEnchanterEx: public WeaponEnchanter
{
public:
    virtual float UpdateEnchantRate() final
    {
        //...무기 추가 강화학률
        float enchantRate = 0.3f;
        //..

        return enchantRate;
    }
};

int main()
{
    auto * weponEnchanter = new WeaponEnchanter();
    auto * weaponEnchanterEx = new WeaponEnchanterEx();

    weponEnchanter->UpdateEnchantRate();
    weaponEnchanterEx->UpdateEnchantRate();

    SafeDelete(weponEnchanter);
    SafeDelete(weaponEnchanterEx);

    return 0;
}

위의 코드에서 BaseEnchant 클래스는 장비를 강화를 담당하는 최상위 클래스로 쓰인다. 이를 상속받은 WeaponEnchanter 클래스가 있고, WeaponEnchanter 를 상속받는 WeaponEnchanterEx 클래스가 있다. 각 클래스에는 UpdateEnchantRate() 라는 강화확률을 구하는 가상함수가 존재한다.

그런데...

상속받은 가상함수를 오버라이딩하는 과정에 override 와 final 이라는 키워드가 있다. 어떤역할을 할까? 어렵지 않다. override 키워드는 해당함수가 오버라이딩 되었다는 것을 오버라이딩 되었다는 것을 알려준다. final도 해당 함수가 오버라이딩 되었다는 것을 가르키는데 둘 사이에는 차이점이 존재한다.

final 로 오버라이된 함수는 더 이상 자식 클래스에서 해당 함수를 오버라이딩 할 수 없다. 위 코드를 기준으로 WeaponEnchanterEx 클래스를 상속받는 자식클래스는 더 이상 UpdateEnchantRate() 함수를 오버라이딩할 수 없다.

필자가 선호하는 이유는 개발중에 다양한 라이브러리를 만들거나, 타인이 만든 라이브러리를 사용해야될 경우가 많은데, override / final 키워드는 가상함수에 대한 정보를 알려주므로 유용하게 사용가능하다.

default / delete

개발을 하다보면 특정 기능을 전담하는 클래스를 많이 만든다. 예를 들면 유저데이터, 리소스(텍스쳐, 셰이더, 사운드) 관리, 전투 / 스킬 같은 기능을 관리할 것들. 보통 singleton 패턴을 사용하여 매니저라는 클래스를 만든다. 이때 C++11 이전에는 복사 생성자와 대입 생성자를 private 멤버로 선언하여, 하나의 인스턴스가 내부에서 존재하고 데이터를 관리하도록 했다.

class ResourceManager 
{
private:
    static ResourceManager * _instance;

public:
    static ResourceManager * Get();
    static void Release();

private:
    ResourceManager(const ResourceManager * other); //복사 못함
    ResourceManager * operator=(const ResourceManager * other); //대입 못함

public:
    ResourceManager();
    ~ResourceManager();

};

이제는 C++11 으로 넘어오며 delete 키워드를 사용하여 아에 생성자를 삭제시켜버리면 간단히 해결된다.

class ResourceManager 
{
private:
    static ResourceManager * _instance;

public:
    static ResourceManager * Get()
    static void Release()

public:
    ResourceManager(const ResourceManager * other) = delete;
    ResourceManager * operator=(const ResourceManager * other) = delete;
    ResourceManager();
    ~ResourceManager();

};

생성자에 delete 키워드를 사용하게 되면 해당 생성자는 삭제되어버린다. 깔끔하게 흔적도 없이 삭제되어버린다. 반면 default 키워드를 사용하면 생성자 코드를 구현하지 않아도 컴파일러에서 자동으로 해당 클래스의 생성자를 기본으로 만들어준다. 존재는 해야하지만 아무런 코드가 필요없는 클래스 생성자가 필요할때가 있는데, 이때 써주면 코드량은 더더욱 줄어들게 된다.

nullptr

C++ 은 프로그래머가 직접 메모리를 할당하고 해제해야된다. 보통 메모리를 malloc 이나 new 로 할당 전에 보통 NULL 으로 초기화를 하고 할당을 받는다. 왜 초기화 할까?
만약 이 글을 읽는 여러분들은 포인터에 들어가있는 쓰레기값 0xcccccccc 와 실제 메모리 주소 0xcccccccc을 구분할 방법이 있는가? 없다!

때문에 초기화를 관습적으로 사용하는데 이때 사용하는 초기화 값이 NULL 이다. NULL은 일반적으로 다음과 같이 정의되어 있다.

#define NULL 0

C++11 이전까지는 NULL 초기화를 했지만, 모던 C++ 부터 nullptr 이라는 초기화 키워드를 지원한다.

char * characterName = nullptr;

물론 예전처럼 NULL 초기화도 가능하다. 그럼 NULL 과 nullptr 의 차이점은 무엇일까? 초기화는 똑같이 되지만 NULL은 상수 0이고, nullptr 은 포인터로 취급된다. 따라서 포인터를 처리할 때 생기는 문제를 대처할 수 있다. 무슨문제?? 바로 NULL 이 상수 0으로 정의되어 있으므로 생기는 문제다. 리터럴값 0과 혼용사용하여 생기는 문제를 nullptr을 사용하여 원천차단 가능해진다.

마치며

필자가 즐겨 사용하는 모던 C++ 키워드를 소개해보았다. 사실 별로 특별한 것도 없지만, 익숙해지면 굉장히 편리하고, 생산성이 있는 코드를 작성할 수 있다고 생각한다. 필자가 소개한 것 뿐만 아니라 다른 추가 내용들도 확인해보고 본인에게 필요한 것을 습득하길 바란다.

profile
게임 개발자입니다. 안까먹으려고 기록합니다.

0개의 댓글