[Advanced C++] 46. std::reference_wrapper, enumerator를 이용한 std::array, 배열을 사용한 enuemrator 활용 및 instream outstream 연산자 오버로딩

dev.kelvin·2025년 5월 1일
1

Advanced C++

목록 보기
46/74
post-thumbnail

1. std::reference_wrapper

std::reference_wrapper

배열은 어떤 객체 타입의 element든 가질 수 있다 (기본 타입, 복합 타입)

	int a{ 1 };
    int b{ 2 };
    
    std::array valarr{ a, b }; //std::array<int, 2>
    std::vector ptrarr{ &a, &b }; //std::vector<int*>

이때 참조는 객체가 아니기 때문에 참조로 이루어진 배열을 만들 수 없다, 또한 배열의 element는 값 변경(할당)이 가능해야 한데 참조는 한번 초기화 되면 다른 대상을 가리키도록 재지정될 수 없다

    int a{ 1 };
    int b{ 2 };

    std::array<int&, 2> refArr{ a }; //compile error

하지만 참조 타입 변수로 초기화 하는건 가능하다 (단순히 참조한 값으로 배열의 element를 구성함)

	int a{ 1 };
    int b{ 2 };

    int& refa{ a };
    int& refb{ b };

    std::array arr{ a, b }; //ok

위 내용들은 std::array뿐 아니라 C-style 배열, std::vector에 전부 적용된다

그렇다면 참조 타입의 element를 가지는 배열을 만들려면 어떻게 해야할까?

이럴때 바로 < functional > 헤더에 정의된 표준 라이브러리 클래스 템플릿인 std::reference_wrapper를 사용해야 한다

타입 T를 받아서 T에 대한 수정 가능한 lvalue 참조처럼 동작시켜준다

std::reference_wrapper는 참조와 같은 동작을 하지만 실제로는 참조를 감싸는 객체이기 때문에 배열에서 사용이 가능하다

    int a{ 1 };
    int b{ 2 };

    std::array<std::reference_wrapper<int>, 2> arr{ a, b };
	//std::reference_wrapper<>로 a객체 참조를 감싸는 객체를 만든다
    
    arr[0].get() = 10; //get()으로 실제 참조를 얻어와 값을 수정했기 때문에 a의 값이 변경됨

    std::cout << a;

get()으로 실제 참조(T&)를 얻을 수 있다, 실제 참조 대상의 값을 변경할 때 많이 사용한다

std::reference_wrapper< T >는 T&로 암시적 변환이 가능하다, 따라서

	std::cout << arr[0] << std::endl;

위 코드에서 컴파일러는 std::reference_wrapper< int > 를 int&로 암시적 변환하고 값을 출력한다

std::reference_wrapper< >는 operator=를 이용하여 참조하는 대상을 변경할 수 있다 (일반 참조는 재지정이 불가능하지만 std::reference_wrapper는 가능함)

    int a{ 1 };
    int b{ 2 };

    std::reference_wrapper<int> tempref{ a };
    tempref = b; //가리키는 대상이 a -> b로 변경됨
    tempref.get() = 200; 

	std::cout << a << std::endl; //1
	std::cout << b << std::endl; //200

C++17 이전에는 CTAD기능이 없었기 때문에 클래스 템플릿 타입 추론이 불가능했다, 따라서 std::reference_wrapper< T > 를 전부 명시적으로 작성해야 했다

하지만 이제는 타입은 CTAD로 추론이 가능하기 때문에 따로 작성하지 않아도 된다

    int a{ 1 };
    int b{ 2 };

    std::reference_wrapper tempref{ a };
    
    auto tempref1{ std::reference_wrapper{ b } };

C++11부터 std::reference_wrapper라는 긴 이름과 템플릿 인수를 명시적으로 적어야 하는 번거로움을 해결하기 위한 함수인 std::ref()와 std::cref()가 제공되었다

이 두 함수는 std::reference_wrapper, std::reference_wrapper< const > 객체를 생성하는 shortcut이라고 보면 된다

cref()로 만든 std::reference_wrapper 객체는 get()으로 수정이 불가능한걸 확인할 수 있다

enumerator를 이용한 std::array

[ ]는 bound check를 하지 않기 때문에 초기화 된 element의 index보다 높은 index로 접근 시 크래시가 발생하게 된다

이를 std::vector와 같은 방식으로 enumerator를 사용하여 보호가 가능하다

    enum Count
    {
        a,
        b,
        c,
        d,
        maxcount
    };

    int main() 
    {
        constexpr std::array fooarr{ 1, 2, 3 };

        fooarr[d]; //crash, out of bound!

        return 0;
    }

static_assert()를 이용하여 검사하고 인덱싱하는게 out of bound방지에 좋다

이전에 정리한 string_view를 받아 enumerator값을 return하는 함수, enumerator를 받아 enumerator의 이름을 출력하는 함수를 std::array를 이용하여 더 깔끔하게 구현할 수 있다

string_view를 받아 enumerator값을 return하는 함수와 enumerator를 받아 enuemrator의 이름을 출력하는 함수는 다음과 같다

    constexpr std::string_view GetEnumName(Colors InColor)
    {
        switch (InColor)
        {
        case Colors::Red:
            return "Red";
        case Colors::Green:
            return "Green";
        case Colors::Blue:
            return "Blue";
        default:
            return "Unknown";
        }
    }

    constexpr std::optional<Colors> GetEnumFromString(std::string_view InStringView)
    {
        if (InStringView == "Red")
        {
            return Colors::Red;
        }
        else if (InStringView == "Green")
        {
            return Colors::Green;
        }
        else if (InStringView == "Blue")
        {
            return Colors::Blue;
        }

        return std::nullopt;
    }

이렇게 함수를 구현하게 되면 enum의 데이터가 추가되고 삭제될 때 마다 switch와 if 수정이 들어가야 한다는 것이다 (유지보수성 저하)

이를 배열을 사용하여 조금 더 유지보수성이 좋게 구현이 가능하다

    #include <array>
    #include <iostream>
    #include <string_view> // std::string_view 사용
    #include <cstddef> //std::size_t
    #include <string> // std::getline

    namespace MyNamespace
    {
        enum Colors
        {
            Red,
            Green,
            Blue,
            MaxCount
        };

        using namespace std::string_view_literals; //sv suffix를 사용하기 위함

        constexpr std::array ColorNames = { "Red"sv, "Green"sv, "Blue"sv };

        static_assert(ColorNames.size() == MaxCount, "ColorNames size must be equal to MaxCount");
    }

    constexpr std::string_view GetColorName(MyNamespace::Colors InColor)
    {
        return MyNamespace::ColorNames[static_cast<std::size_t>(InColor)];
    }

    std::ostream& operator<<(std::ostream& OutStream, MyNamespace::Colors color) {
        // getColorName 함수를 사용하여 색상 이름을 얻고 출력 스트림으로 보내기
        return OutStream << GetColorName(color);
    }

    std::istream& operator>>(std::istream& InStream, MyNamespace::Colors& OutColor)
    {
        std::string ColorName{};
        std::getline(InStream >> std::ws, ColorName); // std::ws는 공백을 무시함

        for (std::size_t i = 0; i < MyNamespace::MaxCount; ++i)
        {
            if (ColorName == MyNamespace::ColorNames[i])
            {
                OutColor = static_cast<MyNamespace::Colors>(i);
                return InStream;
            }
        }

        InStream.setstate(std::ios_base::failbit);

        return InStream;
    }

    int main() 
    {
        MyNamespace::Colors color{ MyNamespace::Colors::Red };

        std::cout << color << std::endl;

        std::cout << "Enter a new color: ";
        std::cin >> color; // 사용자 입력을 받기 위해 operator>> 오버로딩 사용

        if (!std::cin) // 스트림 상태 확인 (failbit가 설정되었는지)
        {
            std::cout << "Invalid input\n"; // 입력 실패 시
        }
        else
        {
            std::cout << "Your color is " << color << '\n'; // 입력 성공 시
        }

        return 0;
    }

for loop로 enumerator 순회하기

	for (int i = 0; i < MyNamespace::MaxCount; ++i)
	{
		std::cout << MyNamespace::Colors(i) << '\n';
	}

단순한 방법으로 enumerator를 순회하면 정수값으로 출력이 된다, 이를 문자열로 출력하려면 instream, outstream을 위와 같이 오버로딩해야 한다

    namespace MyNamespace
    {
        enum Colors
        {
            Red,
            Green,
            Blue,
            MaxCount
        };

        using namespace std::string_view_literals; //sv suffix를 사용하기 위함

        constexpr std::array ColorNames = { "Red"sv, "Green"sv, "Blue"sv };

        static_assert(ColorNames.size() == MaxCount, "ColorNames size must be equal to MaxCount");
    }

    constexpr std::string_view GetColorName(MyNamespace::Colors InColor)
    {
        return MyNamespace::ColorNames[static_cast<std::size_t>(InColor)];
    }

    std::ostream& operator<<(std::ostream& OutStream, MyNamespace::Colors color) {
        // getColorName 함수를 사용하여 색상 이름을 얻고 출력 스트림으로 보냅니다.
        return OutStream << GetColorName(color);
    }

    std::istream& operator>>(std::istream& InStream, MyNamespace::Colors& OutColor)
    {
        std::string ColorName{};
        std::getline(InStream >> std::ws, ColorName); // std::ws는 공백을 무시함

        for (std::size_t i = 0; i < MyNamespace::MaxCount; ++i)
        {
            if (ColorName == MyNamespace::ColorNames[i])
            {
                OutColor = static_cast<MyNamespace::Colors>(i);
                return InStream;
            }
        }

        InStream.setstate(std::ios_base::failbit);

        return InStream;
    }

    int main() 
    {
        for (int i = 0; i < MyNamespace::MaxCount; ++i)
        {
			std::cout << static_cast<MyNamespace::Colors>(i) << '\n';
        }
    }

for-each로 enumerator를 순회하는건 허용되지 않는다

for-each loop는 begin(), end()와 같은 멤버함수나 이러한 역할을 하는 함수를 가지는 컨테이너 타입에서만 작동한다, enum은 해당 조건을 만족하지 못하기 때문에 for-each loop 사용이 불가능하다

배열에서는 for-each loop가 동작하기 때문에 배열로 대체해서 사용하면 된다

    namespace MyNamespace
    {
        enum Colors
        {
            Red,
            Green,
            Blue,
            MaxCount
        };
        
        constexpr std::array CollorArr{ Red, Green, Blue };

        static_assert(CollorArr.size() == MaxCount, "CollorArr size must be equal to MaxCount");
    }

	for (auto color : MyNamespace::CollorArr)
	{
		std::cout << color << '\n';
	}
    
    //string_view로 출력하고 싶다면 위의 >>, << 연산자 오버로딩이 필요함
profile
GameDeveloper🎮 Dev C++, DataStructure, Algorithm, UE5, Assembly🛠, Git/Perforce🌏

0개의 댓글