원시 메모리에 대한보기로 std :: vector 사용


71

어느 시점에서 정수 및 크기 배열에 대한 원시 포인터를 제공하는 외부 라이브러리를 사용하고 있습니다.

이제 std::vector원시 포인터로 값을 액세스하지 않고 해당 값에 액세스하고 수정하는 데 사용하고 싶습니다 .

요점을 설명하는 명확한 예는 다음과 같습니다.

size_t size = 0;
int * data = get_data_from_library(size);   // raw data from library {5,3,2,1,4}, size gets filled in

std::vector<int> v = ????;                  // pseudo vector to be used to access the raw data

std::sort(v.begin(), v.end());              // sort raw data in place

for (int i = 0; i < 5; i++)
{
  std::cout << data[i] << "\n";             // display sorted raw data 
}

예상 출력 :

1
2
3
4
5

그 이유는 <algorithm>해당 데이터에 알고리즘 (정렬, 스왑 요소 등) 을 적용해야하기 때문입니다 .

그 벡터의 크기를 변경 반면이 변경되지 않을 것이다에, 그래서 push_back, erase, insert그 벡터에 대한 작업에 필요하지 않습니다.

라이브러리의 데이터를 기반으로 벡터를 구성하고 해당 벡터를 수정하고 데이터를 라이브러리에 다시 복사하는 것이 가능하지만 데이터 세트가 실제로 클 수 있으므로 피하고 싶은 완전한 사본 두 개입니다.


16
당신이 찾고있는 것은 가상 std::vector_view이 아닌가?
眠 り ネ ロ ク

3
@ 眠 り ネ ロ ク 예, 아마
Jabberwocky

5
그것은 std::vector작동 하지 않습니다 .
Jesper Juhl


34
표준 알고리즘은 반복자에서 작동하며 포인터 반복자입니다. 당신이하는 것을 막을 것은 아무것도 없습니다 sort(arrayPointer, arrayPointer + elementCount);.
cmaster-

답변:


60

문제는 std::vector배열에 포함 된 개체의 소유권이 있으므로 초기화하는 배열에서 요소의 복사본을 만들어야한다는 것입니다.

이를 피하기 위해 배열에 슬라이스 객체를 사용할 수 있습니다 (예 :와 비슷 std::string_viewstd::string). array_view배열의 첫 번째 요소와 배열 길이에 대한 원시 포인터를 가져 와서 인스턴스가 구성된 고유 한 클래스 템플릿 구현을 작성할 수 있습니다 .

#include <cstdint>

template<typename T>
class array_view {
   T* ptr_;
   std::size_t len_;
public:
   array_view(T* ptr, std::size_t len) noexcept: ptr_{ptr}, len_{len} {}

   T& operator[](int i) noexcept { return ptr_[i]; }
   T const& operator[](int i) const noexcept { return ptr_[i]; }
   auto size() const noexcept { return len_; }

   auto begin() noexcept { return ptr_; }
   auto end() noexcept { return ptr_ + len_; }
};

array_view배열을 저장하지 않습니다. 그것은 단지 배열의 시작과 그 배열의 길이에 대한 포인터를 가지고 있습니다. 따라서 array_view객체를 구성하고 복사하는 것이 저렴합니다.

이후 array_view제공 begin()end()회원 기능을, 당신은 표준 라이브러리 알고리즘 (예를 들어, 사용할 수있는 std::sort, std::find, std::lower_bound, 등) 그것에를 :

#define LEN 5

auto main() -> int {
   int arr[LEN] = {4, 5, 1, 2, 3};

   array_view<int> av(arr, LEN);

   std::sort(av.begin(), av.end());

   for (auto const& val: av)
      std::cout << val << ' ';
   std::cout << '\n';
}

산출:

1 2 3 4 5

대신 std::span(또는 gsl::span)를 사용하십시오.

위의 구현은 슬라이스 객체 의 개념을 드러냅니다 . 그러나 C ++ 20부터는 std::span대신 직접 사용할 수 있습니다 . 어쨌든 gsl::spanC ++ 14부터 사용할 수 있습니다 .


왜 메소드를 noexcept로 표시 했습니까? 예외가 발생하지 않는다고 보장 할 수는 없습니까?
SonneXo


@moooeeeep 단순한 링크보다 설명을 남기는 것이 좋습니다. 이 문제가 많이 발생하는 것을 보았을 때 향후 링크가 만료 될 수 있습니다.
Jason Liu

63

C ++ 20 std::span

C ++ 20을 사용할 수 있다면 std::span, 사용자에게 연속 된 요소 시퀀스를 보여주는 포인터-길이 쌍을 사용할 수 있습니다 . 그것은 일종의이며 std::string_view, 모두 상태 std::spanstd::string_view비 소유 전망이며, std::string_view읽기 전용이다.

문서에서 :

클래스 템플릿 범위는 위치 0에서 시퀀스의 첫 번째 요소가있는 연속 된 객체 시퀀스를 참조 할 수있는 객체를 설명합니다. 스팬은 정적 범위를 가질 수 있으며,이 경우 시퀀스의 요소 수는 유형으로 알려지고 인코딩됩니다.

따라서 다음과 같이 작동합니다.

#include <span>
#include <iostream>
#include <algorithm>

int main() {
    int data[] = { 5, 3, 2, 1, 4 };
    std::span<int> s{data, 5};

    std::sort(s.begin(), s.end());

    for (auto const i : s) {
        std::cout << i << "\n";
    }

    return 0;
}

실시간 확인

때문에 std::span기본적으로 포인터 - 길이 쌍, 당신은 너무 다음과 같은 방식으로 사용할 수 있습니다 :

size_t size = 0;
int *data = get_data_from_library(size);
std::span<int> s{data, size};

참고 : 모든 컴파일러가 지원하지는 않습니다 std::span. 여기에서 컴파일러 지원을 확인 하십시오 .

최신 정보

C ++ 20을 사용할 수 없으면 gsl::span기본적으로 C ++ 표준의 기본 버전 인을 사용할 수 있습니다 std::span.

C ++ 11 솔루션

C ++ 11 표준으로 제한되어 있다면 자신 만의 간단한 span클래스를 구현해 볼 수 있습니다 .

template<typename T>
class span {
   T* ptr_;
   std::size_t len_;

public:
    span(T* ptr, std::size_t len) noexcept
        : ptr_{ptr}, len_{len}
    {}

    T& operator[](int i) noexcept {
        return *ptr_[i];
    }

    T const& operator[](int i) const noexcept {
        return *ptr_[i];
    }

    std::size_t size() const noexcept {
        return len_;
    }

    T* begin() noexcept {
        return ptr_;
    }

    T* end() noexcept {
        return ptr_ + len_;
    }
};

C ++ 11 버전 라이브 확인


4
gsl::span컴파일러가 구현하지 않으면 C ++ 14 이상에서 사용할 수 있습니다std::span
Artyer

2
@Artyer 나는 이것으로 대답을 업데이트 할 것입니다. 감사합니다
NutCracker

29

알고리즘 라이브러리는 반복자와 함께 작동하므로 배열을 유지할 수 있습니다.

포인터 및 알려진 배열 길이

여기에서 raw 포인터를 반복자로 사용할 수 있습니다. 그들은 반복자가 지원하는 모든 작업을 지원합니다 (증가, 평등 비교, 값 등).

#include <iostream>
#include <algorithm>

int *get_data_from_library(int &size) {
    static int data[] = {5,3,2,1,4}; 

    size = 5;

    return data;
}


int main()
{
    int size;
    int *data = get_data_from_library(size);

    std::sort(data, data + size);

    for (int i = 0; i < size; i++)
    {
        std::cout << data[i] << "\n";
    }
}

data리턴 한 반복자 같은 배열 dirst 부재 포인트 begin()data + size의해 반환 반복자 같은 배열의 마지막 요소 이후에 소자 포인트 end().

배열

여기에 사용할 수있는 std::begin()std::end()

#include <iostream>
#include <algorithm>

int main()
{
    int data[] = {5,3,2,1,4};         // raw data from library

    std::sort(std::begin(data), std::end(data));    // sort raw data in place

    for (int i = 0; i < 5; i++)
    {
        std::cout << data[i] << "\n";   // display sorted raw data 
    }
}

그러나 data포인터로 쇠퇴하지 않으면 길이 정보가 없어지기 때문에 작동합니다 .


7
이것이 정답입니다. 알고리즘은 범위에 적용됩니다 . 컨테이너 (예 : std :: vector)는 범위를 관리하는 한 가지 방법이지만 유일한 방법은 아닙니다.
피트 베커

13

원시 배열에서 반복자를 가져 와서 알고리즘에서 사용할 수 있습니다.

    int data[] = {5,3,2,1,4};
    std::sort(std::begin(data), std::end(data));
    for (auto i : data) {
        std::cout << i << std::endl;
    }

원시 포인터 (ptr + size)로 작업하는 경우 다음 기술을 사용할 수 있습니다.

    size_t size = 0;
    int * data = get_data_from_library(size);
    auto b = data;
    auto e = b + size;
    std::sort(b, e);
    for (auto it = b; it != e; ++it) {
        cout << *it << endl;
    }

UPD : 그러나 위의 예는 디자인이 잘못되었습니다. 라이브러리는 우리에게 원시 포인터를 반환하고 우리는 기본 버퍼가 할당 된 위치와 누가 버퍼를 해제해야하는지 모른다.

일반적으로 호출자는 함수가 데이터를 채우도록 버퍼링을 제공합니다. 이 경우 벡터를 미리 할당하고 기본 버퍼를 사용할 수 있습니다.

    std::vector<int> v;
    v.resize(256); // allocate a buffer for 256 integers
    size_t size = get_data_from_library(v.data(), v.size());
    // shrink down to actual data. Note that no memory realocations or copy is done here.
    v.resize(size);
    std::sort(v.begin(), v.end());
    for (auto i : v) {
        cout << i << endl;
    }

C ++ 11 이상을 사용하는 경우 get_data_from_library ()를 만들어 벡터를 반환 할 수도 있습니다. 이동 작업 덕분에 메모리 사본이 없습니다.


2
그런 다음 일반 포인터를 반복자로 사용할 수 있습니다.auto begin = data; auto end = data + size;
PooSH

그러나 문제는 반환 된 데이터가 어디에 get_data_from_library()할당됩니까? 어쩌면 우리는 그것을 전혀 바꾸지 않아야합니다. 버퍼를 라이브러리에 전달해야하는 경우 벡터를 할당하고 전달할 수 있습니다.v.data()
PooSH

1
@PooSH 데이터는 라이브러리가 소유하지만 제한없이 변경할 수 있습니다 (실제로 전체 질문의 요점). 데이터 크기 만 변경할 수 없습니다.
Jabberwocky

1
@Jabberwocky는 벡터의 기본 버퍼를 사용하여 데이터를 채우는 방법에 대한 더 나은 예를 추가했습니다.
PooSH

9

std::vector복사본을 만들지 않고 는이 작업을 수행 할 수 없습니다 . std::vector후드 아래에있는 포인터를 소유하고 제공된 할당자를 통해 공간을 할당합니다.

C ++ 20을 지원하는 컴파일러가 필요하다면 정확히이 목적으로 만들어진 std :: span 을 사용할 수 있습니다 . 포인터와 크기를 C ++ 컨테이너 인터페이스가있는 "컨테이너"로 래핑합니다.

그렇지 않은 경우 표준 버전의 기반 되는 gsl :: span 을 사용할 수 있습니다 .

다른 라이브러리를 가져 오지 않으려면 원하는 모든 기능에 따라이를 직접 구현할 수 있습니다.


9

이제 std :: vector를 사용하여 이러한 값에 액세스하고 수정하고 싶습니다.

당신은 할 수 없습니다. 그게 아닙니다 std::vector. std::vector할당 자로부터 항상 획득되는 자체 버퍼를 관리합니다. 다른 버퍼의 소유권을 가지지 않습니다 (같은 유형의 다른 벡터는 제외).

반면에, 당신은 또한 필요가 없습니다 ...

그 이유는 해당 데이터에 알고리즘 (정렬, 스왑 요소 등)을 적용해야하기 때문입니다.

이러한 알고리즘은 반복자에서 작동합니다. 포인터는 배열의 반복자입니다. 벡터가 필요하지 않습니다.

std::sort(data, data + size);

의 함수 템플릿과 달리 <algorithm>range-for, std::begin/ std::end및 C ++ 20 범위와 같은 일부 도구 는 한 쌍의 반복자와 함께 작동하지 않지만 벡터와 같은 컨테이너에서는 작동합니다. 범위로 작동하는 반복자 + 크기에 대한 랩퍼 클래스를 작성할 수 있으며 이러한 도구와 함께 작동합니다. C ++ 20은 이러한 래퍼를 표준 라이브러리에 도입 할 것 std::span입니다.


7

에 대한 다른 좋은 제안 게다가 std::span오는 gsl:span자신 (경량)를 포함하여, span다음 이미 (복사 주시기) 충분히 쉽게 때까지 클래스를 :

template<class T>
struct span {
    T* first;
    size_t length;
    span(T* first_, size_t length_) : first(first_), length(length_) {};
    using value_type = std::remove_cv_t<T>;//primarily needed if used with templates
    bool empty() const { return length == 0; }
    auto begin() const { return first; }
    auto end() const { return first + length; }
};

static_assert(_MSVC_LANG <= 201703L, "remember to switch to std::span");

특별 참고로 부스트 범위 라이브러리 또한 는보다 일반적인 범위의 개념에 관심이 있다면이 : https://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/reference /utilities/iterator_range.html .

범위 개념은


1
무엇입니까 using value_type = std::remove_cv_t<T>;?
Jabberwocky

1
... 생성자를 잊었습니다 span(T* first_, size_t length) : first(first), length(length) {};. 나는 당신의 대답을 편집했습니다.
Jabberwocky

@ Jabberwocky 방금 집계 초기화를 사용했습니다. 그러나 생성자는 괜찮습니다.
darune

1
@eerorika 나는 당신이 옳은 것 같아요, 나는 비 const 버전을 제거했습니다
darune

1
using value_type = std::remove_cv_t<T>;(A '범위'의 VALUE_TYPE을 얻기를 위해) 템플릿 프로그램과 함께 사용할 경우 주로 필요합니다. 반복자를 사용하려면 생략하거나 제거 할 수 있습니다.
darune

6

실제로 사용자 지정 할당 기 기능을 악용하여보고자하는 메모리에 대한 포인터를 반환함으로써 거의이를 사용할 std::vector 있습니다. 표준 (패딩, 정렬, 반환 값의 초기화)이 보장되지는 않습니다. 초기 크기를 지정할 때 어려움을 겪어야하며 기본이 아닌 경우에는 생성자를 해킹해야합니다. ), 그러나 실제로 충분한 조정이 이루어질 것으로 기대합니다.

절대 그렇게하지 마십시오. 추악하고, 놀랍고, 해 키고, 불필요합니다. 표준 라이브러리의 알고리즘은 이미 벡터와 마찬가지로 원시 배열에서도 작동하도록 설계되었습니다. 자세한 내용은 다른 답변을 참조하십시오.


1
흠, 그래 그것은 함께 일할 수있는 vector생성자 생성자 인수 (단지 템플릿 PARAM)와 같은 사용자 정의 할당 자 참조를 가지고. 템플릿 매개 변수가 아닌 런타임 포인터 값이있는 할당 자 객체가 필요하다고 생각합니다. 그렇지 않으면 constexpr 주소에서만 작동 할 수 있습니다. vector기본 구성 객체가 .resize()기존 데이터를 덮어 쓰지 않도록주의해야합니다 . .push_back 등을 사용하기 시작하면 벡터와 비 소유 범위와 같은 소유 컨테이너 간의 불일치가 엄청납니다.
Peter Cordes

1
@PeterCordes 내 말은, lede를 묻지 말자-당신 미쳐야 할 것이다 . 내 생각에 가장 이상한 것은 할당 자 인터페이스에 construct필요한 메소드가 포함되어 있다는 것입니다.
Sneftel

1
명백한 유스 케이스는 다른 방식으로 작성하려는 요소를 구성하는 데 시간을 낭비하지 않는 resize()것입니다. 예를 들어 순수한 출력으로 사용하려는 것에 대한 참조를 전달하기 전에 (예 : 시스템 호출 읽기). 실제로 컴파일러는 종종 memset 등을 최적화하지 않습니다. 또는 calloc을 사용하여 사전 영점 메모리를 확보하는 할당자가있는 std::vector<int>경우 기본적으로 영 (0) 비트 패턴이있는 오브젝트를 구성 할 때 바보 같은 방식으로 더티를 피할 수 있습니다 .
Peter Cordes의

4

다른 사람들이 지적했듯이 std::vector기본 메모리 (사용자 지정 할당자를 망칠 수 없음)를 소유해야하므로 사용할 수 없습니다.

다른 사람들은 c ++ 20의 범위를 권장했지만 분명히 c ++ 20이 필요합니다.

스팬 라이트 스팬을 권장합니다 . 자막을 인용하려면 :

span lite-단일 파일 헤더 전용 라이브러리의 C ++ 98, C ++ 11 이상에 대한 C ++ 20 유사 범위

소유하지 않고 변경할 수있는보기를 제공합니다 (요소와 그 순서를 변경할 수는 있지만 삽입 할 수는 없음). 인용에는 의존성이 없으며 대부분의 컴파일러에서 작동합니다.

귀하의 예 :

#include <algorithm>
#include <cstddef>
#include <iostream>

#include <nonstd/span.hpp>

static int data[] = {5, 1, 2, 4, 3};

// For example
int* get_data_from_library()
{
  return data;
}

int main ()
{
  const std::size_t size = 5;

  nonstd::span<int> v{get_data_from_library(), size};

  std::sort(v.begin(), v.end());

  for (auto i = 0UL; i < v.size(); ++i)
  {
    std::cout << v[i] << "\n";
  }
}

인쇄물

1
2
3
4
5

언젠가는 C ++ (20)에 스위치를 할 경우는 위쪽 추가했습니다, 당신은이를 대체 할 수 있어야 nonstd::spanstd::span.


3

std::reference_wrapperC ++ 11부터 사용할 수 있습니다 .

#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>

int main()
{
    int src_table[] = {5, 4, 3, 2, 1, 0};

    std::vector< std::reference_wrapper< int > > dest_vector;

    std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));
    // if you don't have the array defined just a pointer and size then:
    // std::copy(src_table_ptr, src_table_ptr + size, std::back_inserter(dest_vector));

    std::sort(std::begin(dest_vector), std::end(dest_vector));

    std::for_each(std::begin(src_table), std::end(src_table), [](int x) { std::cout << x << '\n'; });
    std::for_each(std::begin(dest_vector), std::end(dest_vector), [](int x) { std::cout << x << '\n'; });
}

2
이것은 데이터의 사본을 수행하므로 정확하게 피하고 싶습니다.
Jabberwocky

1
@Jabberwocky 데이터를 복사하지 않습니다. 그러나 그것은 당신이 질문에서 요구 한 것이 아닙니다.
eerorika

@eerorika 는 (IOW 데이터가 복사 됨 ) 에서 가져온 값으로 std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));확실히 채 웁니다. 그래서 귀하의 의견을 얻지 못했습니다. 설명해 주시겠습니까? dest_vectorsrc_tabledest_vector
Jabberwocky

@Jabberwocky는 값을 복사하지 않습니다. ithe 벡터를 참조 래퍼로 채 웁니다.
eerorika

3
@Jabberwocky 정수 값의 경우 비효율적입니다.
eerorika
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.