ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [32] - Copy and Filter
    Graphics 2021. 8. 9. 13:08

    기본적으로 D3D의 파이프라인을 구성은 했지만 내부 구조를 구성하는 디테일을 이해하려면 c++의 몇 가지 요소들을 이해하는 것이 필요합니다. 적어도 사용하려는 Engine이 어떤 구성으로 어떻게 돌아가는지,  C++어떠한 특징을 이용한 것인지 설명을 할 수 있어야 합니다. 가장 자주 사용하는 라이브러리인 <Algorithm>부터 차근차근 살펴보도록 합니다.

    <Algorithm>으로 검색을 하면 언제나 그렇듯 (https://en.cppreference.com/w/cpp/algorithm)여기로 연결 됩니다. 그러나 이걸 하나씩 읽는 건 거의 불가능에 가깝습니다. ChiliD3DFramework에 쓰인 핵심적인 내용들만 살펴봅니다. 그리고 이번에는 Copy에 대한 내용을 살펴봅니다.

    지금 C++20표준이 갱신되어 있으나 Chili의 Framework가 개발될 당시에는 없던 표준이기 때문에 당장은 살펴볼 필요가 없습니다.

    Copy는 직관적입니다. 어떤 Container의 일정한 구간 만큼을 새로운 공간에 복사합니다. '복사'에 대해서는 언젠가 정확히 알아본 경험이 있었습니다. C++에 대해서 '정확히' 알아본다는 것은 지옥과 같다는 것을 배웠습니다. 바닥이 없는 지식의 늪입니다.

    Copy

    두려워 말고 들여다 봅니다. 이 D3D를 배우면서 한 가지 체득한 것이 있다면 다음과 같은 문서를 두려움 없이 읽을 수 있게 됐다는 점입니다.

    많은 설명이 있습니다. 각 template paramter에 대한 설명부터 모든 것이 있습니다. 그래도 예제 코드를 보며 구체적으로 어떤 식으로 활용하는지 먼저 보는 것이 기쁩니다.

    이 함수는 단순히 콘솔에 Container인 데이터의 내용을 작성하는 역할을 수행합니다. 조금 더 자세한 설명을 보태면 std::ostream_iterator는 output stream에 순차적으로 무언가 작성하는 output iterator입니다. Delimiter를 명시할 수 있다고 하는데 Delimiter의 사전적인 정의는 연속된 문자, 데이터 등의 구분을 짓는 특정한 기호입니다. std::ostream_iteraotr<typename T, charT = char, class traits=char_trait<charT>>라는 식으로 정의가 되어 있고 두 개의 default template arguments는 볼일이 없고 iterator의 type을 정의만 해주면 됩니다.

    value_type은 Generic Programming을 위해 고안되었다는 느낌을 크게 받았습니다. Stackoverflow에 올라온 한 질문은 (https://stackoverflow.com/questions/44571362/what-is-the-use-of-value-type-in-stl-containers) 이 Value_type의 존재에 대해 의문을 갖고 있습니다. 저도 그렇게 생각은 하지만 value_type으로 대체해버리면 일일이 vector에 무엇이 있어야 하는지 명시하지 않아도 되기 때문에 Code duplication을 방지할 수도 있습니다. 결론적으로 위의 함수는 arguments의 값을 output stream에 복사하여 Console에 출력하는 역할을 합니다.

    다음 코드는 std::copy를 활용한 간단한 테스트 코드입니다.

    Result

    직관적인 코드입니다. container b의 index 0부터 3까지를 같은 Container의 index 3부터 시작해서 복사하라는 명령입니다. 한 가지 흥미로운 사실은 복사의 원본과 복사의 대상이 같은 Containter여도 문제가 없다는 것입니다. 그래서 이 결과가 더 흥미롭게 느껴지는데 세 번째 줄의 복사의 결과를 보면 1이 세 번 등장하는 것을 볼 수 있습니다. 이는 index0이 index 3에 먼저 복사가 되고, index 1이 index 4, index2는 index 5에 복사가 되고 index3을 보니 아까 index 0에서 복사된 1이 그 위치에 있어서 한 번 더 복사가 진행된 모습입니다. 그렇기 때문에 데이터의 원활한 복사를 위해서는 복사를 할 원본의 범위가 복사의 대상이 될 위치가 아무렇게나 설정해서는 안 됩니다. 가령, index 0 ~ index 9까지를 복사의 원본으로 삼는다면 복사의 대상이 될 범위는 이 안에 위치하지 않도록 하는 편이 좋습니다.

    std::copy_n

    그리고 또 한 가지 알고 넘어가면 좋은 것이 바로 std::copy_n입니다. C++11표준부터 제공이 되며 std::copy와는 다르게 어떤 범위[Range]를 받는 것이 아니라 input iterator와 원소[elements]의 개수를 받습니다.
    *InputIt = Input Iterator. OutputIt = Output Iterator의 Abbreviation입니다.

    std::string in = "1234567890";

     since c++11 until c++20에 해당하는 첫 번째 표준을 보면 복사의 원본 첫 번째 위치부터 복사할 개수를 parameter 1, 2에 놓고 마지막 Parameter에 복사할 위치에 해당하는 container의 iterator를 넣습니다. 다음과 같이 사용하면 될 것입니다.

    확실히 우리의 삶을 훨씬 간편하게 해줍니다. 그러면 이런 것도 가능할 겁니다. 키보드로부터 몇 개의 값을 받아서 콘솔이 출력하는 기능도 만들 수 있을 것입니다. 이미 위에서 무엇이 필요한지 다 살펴보았습니다. Copy와 input stream을 활용합니다. output stream이 콘솔 창에 값을 출력했으니 받기 위해서는 input stream인 std::istream을 이용하면 될 것입니다.

    보는 것처럼 10개의 단어가 Delimiter인 " "대로 잘려 나옵니다. 실제로 줄에 상관 없이 입력해도 잘 나옵니다.

    입력을 여러 줄에 걸쳐서 해도 결과는 같습니다.

    그런데 std::copy_backward라는 것이 존재합니다. 이는 복사의 원본 대상의 범위를 복사가 될 위치의 Index로부터 0을 향해 복사를 한다는 의미입니다. 아마 예제를 보면 이해가 빠르게 될 것입니다.

    이런 기능은 첫 번째로 들었던 예시의 문제를 해결하는데 도음울 줍니다.

    1이 반복해서 나옵니다.

    이 코드는 b의 index 0부터 index 3까지 총 네 개의 Elements를 index 3부터 작성하다가 overwrite된 원소를 다시 참조하며 1이 총 세 번 나타난 현상이었습니다. 하지만 반대로 목적지의 끝에서부터 복사를 시작한다면 겹치지 않고 원소를 인쇄할 수 있게 됩니다. 그런데 이 함수는 쓸일이 없을 것입니다. 아마 여기서 가장 자주 사용하교 유용한 함수는 std::copy_if일 것입니다.

    std::copy_if

    위에 사용법이 나와있습니다. std::copy와 묶여서 있기 때문입니다. C++20이 되며 반환형이 output iterator가 아니라 const expression이 되긴 했으나 크게 달라진 점은 없습니다. 첫 번째와 두 번 째 parameter에 복사를 할 원본의 범위를 집어 넣고 세 번째 parameter에는 복사의 값을 넣을 Container의 iterator, 마지막에 Predicate, 즉 서술어를 넣습니다. 이 predicate는 조건과 같은 내용이기 때문에 복사할 각 원본의 값과 대조하여 참인 값만 복사를 진행하게 할 수 있습니다. 따라서 이 함수는 Filtering에 굉장히 적합한 함수라고 할 수 있겠습니다. 심지어 정겨운 lambda expression이나 function pointer의 개념도 함께 사용할 수 있습니다.

    std::move

    이 함수는 전에 배웠습니다. rvalue reference, lvalue 등등 기초적인 개념을 다시 확실하게 다지는 계기가 되었습니다. '복사'가 있는 곳에는 std::move가 함께합니다. 위의 예에서 vector a의 값을 vector b에 복사할 때는 기본적으로 값을 임시로 하나 만들어 b의 알맞은 위치에 채워넣습니다. 하지만 a가 곧 사라질 녀석이라면 구태여 복사를 반복해야 할 필요가 없습니다. 이는 애초에 std::move의 탄생 배경에 있는 논리이기도 합니다. 그래서 move를 이용하여 한 container의 값을 다른 Container에 옮기면 어떤 일이 일어나는지 확인하도록 합니다.

    vector a 의 값이 아예 사라져버린 것을 확인할 수 있습니다. 이 외에도 unique pointer의 경우에는 복사가 불가능하지만 move는 가능하다는 점을 떠올렸으면 좋겠습니다. 한 가지 재미있는 건 std::move_if라는 함수는 찾아볼 수 없다는 것입니다. 만들어 줬으면 참 편리할 텐데 이유는 모르겠으나 해당 함수는 존재하지 않습니다. 그래서 어떤 특정한 조건에 따라 std::move를 수행하고 싶으면 직접 머리를 쓰는 수밖에는 없습니다. 예를 들어서, Vector a에서 길이가 4를 초과하는 단어들만 골라서 복사하는 함수를 만들어보겠습니다.

    예상한 대로 Sechs와 Seiben만 사라진 것을 확인할 수 있습니다.

Designed by Tistory.