알 수없는 크기의 std :: array를 함수에 전달


98

C ++ 11에서 std :: array의 알려진 유형이지만 크기를 알 수없는 함수 (또는 메서드)를 작성하려면 어떻게해야합니까?

// made up example
void mulArray(std::array<int, ?>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6>  arr2;
std::array<int, 95> arr3;

mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);

검색하는 동안 템플릿 사용에 대한 제안 만 찾았지만, 그것들은 지저분하고 (헤더의 메서드 정의) 내가 달성하려는 작업에 대해 과도하게 보입니다.

일반 C 스타일 배열 에서처럼이 작업을 수행하는 간단한 방법이 있습니까?


1
배열에는 경계 검사가 없거나 크기를 알 수 없습니다. 따라서 어떤 것으로 포장하거나 std::vector.
Travis Pessetto 2013 년

20
템플릿이 지저분하고 과도하게 보이면 그 느낌을 극복해야합니다. C ++에서는 일반적입니다.
벤자민 린들리

std::vector@TravisPessetto가 권장 하는 대로 사용하지 않을 이유가 있습니까?
Cory Klein

2
알겠습니다. 이것이 그들의 성격의 한계라면 나는 그것을 받아 들여야 할 것입니다. std :: vector (나에게 잘 작동 함)를 피하려고 생각한 이유는 힙에 할당되기 때문입니다. 이 배열은 프로그램의 모든 반복에서 작고 반복되므로 std :: array가 조금 더 잘 수행 될 것이라고 생각했습니다. 나는 C 스타일 배열을 사용할 것이라고 생각하지만 내 프로그램은 복잡하지 않습니다.
Adrian

15
@Adrian 성능에 대해 생각하는 방식은 완전히 잘못되었습니다. 기능적인 프로그램이 있기 전에 마이크로 최적화를 시도하지 마십시오. 그리고 프로그램을 만든 후에 는 무엇을 최적화해야하는지 추측 하지 말고 프로파일 러가 프로그램의 어떤 부분을 최적화해야하는지 알려줍니다.
Paul Manta

답변:


86

일반 C 스타일 배열 에서처럼이 작업을 수행하는 간단한 방법이 있습니까?

아니요. 함수를 함수로 만들지 않으면 그렇게 할 수 없습니다. 템플릿으로 (또는 std::vector질문에 대한 주석에서 제안한대로 , 같은 다른 종류의 컨테이너를 사용 ).

template<std::size_t SIZE>
void mulArray(std::array<int, SIZE>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

다음은 실제 예입니다. 입니다.


9
OP는 템플릿 외에 다른 솔루션이 있는지 묻습니다.
Novak

1
@Adrian : 당신은 일반적으로 어떤 크기의 배열에 대한 작업에 함수를 원하는 경우 불행하게도 다른 해결책은 ..., 없다
앤디 배회

1
정답 : 다른 방법은 없습니다. 크기가 다른 각 std :: array는 유형이 다르기 때문에 다른 유형에서 작동 할 수있는 함수를 작성해야합니다. 따라서 템플릿은 std :: array의 솔루션입니다.
bstamour

4
여기에서 템플릿 사용에 대한 아름다운 부분은이를 더욱 일반화하여 모든 시퀀스 컨테이너 및 표준 배열에서 작동하도록 할 수 있다는 것입니다.template<typename C, typename M> void mulArray(C & arr, M multiplier) { /* same body */ }
Benjamin Lindley

1
@BenjaminLindley : 물론 헤더에 코드를 넣을 수 있다고 가정합니다.
Nicol Bolas 2013-06-17

27

의 크기 array 유형의 일부 당신이 원하는 아주 일을 할 수 없도록. 몇 가지 대안이 있습니다.

이터레이터 쌍을 사용하는 것이 좋습니다.

template <typename Iter>
void mulArray(Iter first, Iter last, const int multiplier) {
    for(; first != last; ++first) {
        *first *= multiplier;
    }
}

또는 vector배열 대신 사용 하여 유형의 일부가 아닌 런타임에 크기를 저장할 수 있습니다.

void mulArray(std::vector<int>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

1
저는 이것이 우수한 솔루션이라고 생각합니다. 템플릿을 만드는 데 어려움을 겪고 있다면, 모든 컨테이너 (배열, 목록, 벡터, 심지어 구식 C 포인터 등)를 단점없이 사용할 수있는 반복자를 사용하여 완전히 일반화하십시오 . 힌트 주셔서 감사합니다.
Mark Lakata 2015-06-29

6

나는 아래에서 시도했고 그것은 나를 위해 일했습니다.

#include <iostream>
#include <array>

using namespace std;

// made up example
void mulArray(auto &arr, const int multiplier) 
{
    for(auto& e : arr) 
    {
        e *= multiplier;
    }
}

void dispArray(auto &arr)
{
    for(auto& e : arr) 
    {
        std::cout << e << " ";
    }
    std::cout << endl;
}

int main()
{

    // lets imagine these being full of numbers
    std::array<int, 7> arr1 = {1, 2, 3, 4, 5, 6, 7};
    std::array<int, 6> arr2 = {2, 4, 6, 8, 10, 12};
    std::array<int, 9> arr3 = {1, 1, 1, 1, 1, 1, 1, 1, 1};

    dispArray(arr1);
    dispArray(arr2);
    dispArray(arr3);

    mulArray(arr1, 3);
    mulArray(arr2, 5);
    mulArray(arr3, 2);

    dispArray(arr1);
    dispArray(arr2);
    dispArray(arr3);

    return 0;
}

출력 :

12 34 5 6 7

24 6 8 10 12

111111 1111 1

3 6 9 12 15 18 21

10 20 30 40 50 60

2222222222


3
이것은 유효한 C ++가 아니라 확장입니다. 이러한 함수는 template.
HolyBlackCat

1
나는 이것을 조사 auto foo(auto bar) { return bar * 2; }했으며 C ++ 17 플래그가 설정된 GCC7에서 컴파일하더라도 현재 유효한 C ++가 아닌 것으로 보입니다 . 읽기에서 여기에 , 함수의 매개 변수는 결국 20 ++ C의 일부가되어야하는 개념 TS의 일부가 자동으로 선언했다.
Fibbles


5

편집하다

C ++ 20은 잠정적으로 다음을 포함합니다. std::span

https://en.cppreference.com/w/cpp/container/span

원래 답변

원하는 것은 gsl::spanC ++ 핵심 가이드 라인에 설명 된 가이드 라인 지원 라이브러리에서 사용할 수 있는와 같은 것입니다 .

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#SS-views

여기에서 GSL의 오픈 소스 헤더 전용 구현을 찾을 수 있습니다.

https://github.com/Microsoft/GSL

을 사용 gsl::span하면 다음을 수행 할 수 있습니다.

// made up example
void mulArray(gsl::span<int>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6>  arr2;
std::array<int, 95> arr3;

mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);

문제 std::array는 크기가 유형의 일부이므로 std::array임의의 크기 를 취하는 함수를 구현하려면 템플릿을 사용해야한다는 것 입니다.

gsl::span반면에 크기는 런타임 정보로 저장됩니다. 이것은 하나의 비 템플릿 함수를 사용하여 임의의 크기의 배열을 받아 들일 수 있도록합니다. 다른 연속 컨테이너도 허용합니다.

std::vector<int> vec = {1, 2, 3, 4};
int carr[] = {5, 6, 7, 8};

mulArray(vec, 6);
mulArray(carr, 7);

꽤 멋지죠?


3

물론 C ++ 11에는 알려진 유형이지만 크기는 알 수없는 std :: array를 사용하는 함수를 작성하는 간단한 방법이 있습니다.

배열 크기를 함수에 전달할 수없는 경우 대신 배열이 시작되는 메모리 주소와 배열이 끝나는 두 번째 주소를 전달할 수 있습니다. 나중에 함수 내에서이 2 개의 메모리 주소를 사용하여 배열의 크기를 계산할 수 있습니다!

#include <iostream>
#include <array>

// The function that can take a std::array of any size!
void mulArray(int* piStart, int* piLast, int multiplier){

     // Calculate the size of the array (how many values it holds)
     unsigned int uiArraySize = piLast - piStart;

     // print each value held in the array
     for (unsigned int uiCount = 0; uiCount < uiArraySize; uiCount++)     
          std::cout << *(piStart + uiCount) * multiplier << std::endl;
}

int main(){   

     // initialize an array that can can hold 5 values
     std::array<int, 5> iValues;

     iValues[0] = 5;
     iValues[1] = 10;
     iValues[2] = 1;
     iValues[3] = 2;
     iValues[4] = 4;

     // Provide a pointer to both the beginning and end addresses of 
     // the array.
     mulArray(iValues.begin(), iValues.end(), 2);

     return 0;
}

콘솔 출력 : 10, 20, 2, 4, 8


1

이 작업을 수행 할 수 있지만 깔끔하게 수행하려면 몇 단계를 거쳐야합니다. 먼저 template class연속 값의 범위를 나타내는 a를 작성하십시오 . 그런 다음 앞으로 template(가) 얼마나 큰 알고 버전을 array받는 사람입니다Impl 이 연속 범위 버전에 .

마지막으로 contig_range버전을 구현하십시오 . 참고 for( int& x: range )작동 contig_range내가 구현하기 때문에, begin()그리고 end()포인터가 반복자입니다.

template<typename T>
struct contig_range {
  T* _begin, _end;
  contig_range( T* b, T* e ):_begin(b), _end(e) {}
  T const* begin() const { return _begin; }
  T const* end() const { return _end; }
  T* begin() { return _begin; }
  T* end() { return _end; }
  contig_range( contig_range const& ) = default;
  contig_range( contig_range && ) = default;
  contig_range():_begin(nullptr), _end(nullptr) {}

  // maybe block `operator=`?  contig_range follows reference semantics
  // and there really isn't a run time safe `operator=` for reference semantics on
  // a range when the RHS is of unknown width...
  // I guess I could make it follow pointer semantics and rebase?  Dunno
  // this being tricky, I am tempted to =delete operator=

  template<typename T, std::size_t N>
  contig_range( std::array<T, N>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
  template<typename T, std::size_t N>
  contig_range( T(&arr)[N] ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
  template<typename T, typename A>
  contig_range( std::vector<T, A>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
};

void mulArrayImpl( contig_range<int> arr, const int multiplier );

template<std::size_t N>
void mulArray( std::array<int, N>& arr, const int multiplier ) {
  mulArrayImpl( contig_range<int>(arr), multiplier );
}

(테스트되지 않았지만 디자인이 작동해야 함).

그런 다음 .cpp파일에서 :

void mulArrayImpl(contig_range<int> rng, const int multiplier) {
  for(auto& e : rng) {
    e *= multiplier;
  }
}

이것은 배열의 내용을 반복하는 코드가 (컴파일 시간에) 배열의 크기를 알지 못하기 때문에 최적화 비용이 발생할 수 있다는 단점이 있습니다. 구현이 헤더에있을 필요가 없다는 장점이 있습니다.

a contig_range를 전달 set하면 set데이터가 연속적 (거짓) 이라고 가정하고 모든 곳에서 정의되지 않은 동작을 수행 한다고 가정 하므로 를 명시 적으로 구성 할 때는주의해야합니다 . std이것이 작동하도록 보장 되는 유일한 두 개의 컨테이너는 vectorarray(및 C 스타일 배열입니다!). deque랜덤 액세스 임에도 불구하고 연속적이지 않습니다 (위험하게도 작은 청크로 연속적입니다!).list 가깝지 않으며, 연관 (정렬 및 비 정렬) 컨테이너는 똑같이 연속적이지 않습니다.

세 가지 생성자 그래서 나는 곳 구현 std::array, std::vector기본적으로 기지를 커버하는, 그리고 C 스타일 배열을.

구현 []도 쉽습니다. for()그리고 []그 사이에 원하는 것이 대부분입니다 array.


이것은 템플릿을 다른 곳으로 상쇄하지 않습니까?
GManNickG

@GManNickG 일종의. 헤더는 template구현 세부 사항이없는 매우 짧은 기능을 얻습니다 . 이 Impl함수는 함수가 아니므 template로 원하는 .cpp파일 에서 구현을 기꺼이 숨길 수 있습니다 . 이것은 연속 된 컨테이너를 더 간단한 클래스로 반복하는 기능을 추출한 다음 통과하는 기능을 추출하는 정말 조잡한 유형의 삭제입니다. ( multArrayImpla template를 인수로 사용하지만 template그 자체 가 아닙니다 ).
Yakk-Adam Nevraumont 2013 년

이 배열보기 / 배열 프록시 클래스가 때때로 유용하다는 것을 이해합니다. 내 제안은 생성자에서 컨테이너의 시작 / 끝을 전달하여 각 컨테이너에 대해 생성자를 작성할 필요가 없도록하는 것입니다. 또한 나는 '& * std :: begin (arr)'을 역 참조로 쓰지 않을 것이며 std :: begin / std :: end가 이미 반복자를 반환하기 때문에 주소를 취하는 것은 불필요합니다.
Ricky65 2014

@ Ricky65 반복자를 사용하는 경우 구현을 노출해야합니다. 포인터를 사용하면 사용하지 않습니다. &*역 참조 (포인터가 아니더라도 좋다) 반복자 후 어드레스 포인터를 만든다. 연속 메모리 데이터의 경우 beginone-past-the에 대한 포인터와 포인터 end도 임의 액세스 반복기이며 유형에 대한 모든 연속 범위에 대해 동일한 유형입니다 T.
Yakk-Adam Nevraumont 2014
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.