std::span
C++ 20부터 추가된 연속된 메모리 공간을 추상화한 컨테이너다. 이러한 연속적인 공간을 갖는 컨테이너로는 vector, array, string 등이 있다.
std::vector<int> vecNums{ 1,2,3 };
std::array<int, 4> arrayNums{ 1,2,3,4 };
int cNums[6] = { 1,2,3,4,5,6 }; // c style
이와 같은 array들이 있다고할 때 하나씩 Print하기 위해서는 각 인터페이스에 맞는 함수를 만들어야한다.
// vector 출력
void printVec(std::vector<int> const& nums)
{
for (int num : nums)
{
cout << num << endl;
}
}
// array 출력
void printArray(std::array<int> const& nums)
{
for (int num : nums)
{
cout << num << endl;
}
}
여기서 array는 vector와는 다른 타입이기 때문에 또 다른 인터페이스를 갖는 함수를 만들어야한다. 그런데 3개의 데이터 타입 모두 연속적인 공간에 데이터가 있다는 공통적인 특성이 있다. 이를 이용해 인터페이스를 만들면 하나의 함수로 모두 대응할 수 있다.
그것이 바로 C++ 20부터 추가된 std::span 이다. span의 내부정보에는 array의 시작 주소 정보와 사이즈 정보가 들어있다. 타입이 다른 array여도 시작점과 사이즈 정보만 알고 있으면 하나의 함수 인터페이스만으로도 대응 가능하다는 것이다.
void printSpan(std::span<int> nums)
{
for (int num : nums)
{
cout << num << endl;
}
}
int main()
{
std::span<int> sp1(vecNums);
std::span<int> sp2(arrayNums);
std::span<int> sp3(cNums);
printSpan(sp1);
printSpan(sp2);
printSpan(sp3);
}
span은 내부에 시작점과 사이즈 정보만 가지고 있기 때문에 Deep Copy를 해도 성능에 영향이 없다. 또한, find 같은 standard 알고리즘도 적용가능하다.
void findSpan(std::span<int> nums)
{
auto ret = std::ranges::find(nums, 3); // 여기서 ranges는 C++ 20 부터 추가된 기능이다.
if (ret != nums.end())
{
std::cout << "Found 3" << std::endl;
}
}
주의 사항
span은 시작점과 사이즈만 알고 있기 때문에 만약 원본이 바뀌면 잘못된 데이터 공간에 접근하게 된다.
std::vector<int> nums{ 1,2,3 };
std::span<int> sp(nums);
nums.push_back(4);
printSpan(sp);
처음에 1,2,3이 들어있는 vector를 만들었다. 이후 4를 추가하면서 vector 메커니즘에 의해 새로운 공간에 마이그레이션이 되어 1,2,3,4 가 들어가게되고 기존의 메모리 공간은 의미 없는 공간이 된다. 그렇기 때문에 쓰레기 값이 출력되는 것이다. 즉, 원본이 바뀌었다면 조심해야한다.
span은 시작점과 사이즈를 알기 때문에 원본의 수정도 가능하다.
std::vector<int> nums{ 1,2,3 };
std::span<int> sp(nums);
sort(sp.begin(), sp.end()); // Sort가 된다.
이러한 원본 코드의 수정을 막기위해 const를 붙일 수 있는데 span은 const 를 앞에 붙이는 것이 아니라 내부 element를 유지하려면 다음과 같이 붙여줘야한다. span 그 자체를 유지할 때는 앞에 붙이면 된다.
int main()
{
std::vector<int> nums{ 1,2,3 };
std::span<const int> sp(nums); // 내부 Element 유지
const std::span<int> sp(nums); // span 그 자체 유지
}
span extent
span에는 2가지 모드가 있다.
- dynamic_extent
- static_extent
위에서 알아본 것은 default 값으로 지정된 dynamic_extent이다.
span의 사이즈를 컴파일 시간에 알 수 있다면 최적화가 가능할 것인데 그것이 static_extent이다.
static_extent는 내부에 시작점 정보만 가지고 있고, 사이즈는 데이터로 들어가는 것이 아니라 컴파일 시간에 그 숫자 자체로 들어가게 된다.
int main()
{
std::vector<int> nums{ 1,2,3 };
std::span<int, 3> fixedSpan(nums);
}
컴파일러는 바이너리가 실행될 때 span의 사이즈를 매번 확인하지 않고 숫자가 지정되서 컴파일되므로 최적화가 될 수 있다. span은 subspan과 같은 유용한 기능들이 있으므로 cppreference를 참고하면 되겠다.
'C++ > Modern C++' 카테고리의 다른 글
[C++] Perfect forwarding (0) | 2022.10.05 |
---|---|
[C++] constexpr (0) | 2022.10.05 |
스마트 포인터 문제 - 순환 참조 (0) | 2022.10.03 |
[C++] 문자열 정리 (0) | 2022.09.29 |
[C++17] string_view (0) | 2022.09.07 |