Truyền một mảng std :: có kích thước không xác định vào một hàm


98

Trong C ++ 11, làm cách nào để viết một hàm (hoặc phương thức) nhận một mảng std :: có kiểu đã biết nhưng kích thước không xác định?

// 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);

Trong quá trình tìm kiếm, tôi chỉ tìm thấy các đề xuất để sử dụng các mẫu, nhưng chúng có vẻ lộn xộn (định nghĩa phương pháp trong tiêu đề) và quá mức đối với những gì tôi đang cố gắng hoàn thành.

Có cách nào đơn giản để thực hiện công việc này, như cách làm với mảng kiểu C đơn giản không?


1
Mảng không có giới hạn kiểm tra hoặc biết kích thước của chúng. Vì vậy, bạn phải bọc chúng trong một cái gì đó hoặc xem xét việc sử dụng std::vector.
Travis Pessetto

20
Nếu các mẫu có vẻ lộn xộn và quá mức đối với bạn, bạn nên vượt qua cảm giác đó. Chúng phổ biến trong C ++.
Benjamin Lindley

Có lý do gì để không sử dụng std::vectornhư @TravisPessetto khuyến nghị?
Cory Klein

2
Hiểu. Nếu đây là một hạn chế của bản chất của họ, tôi sẽ phải chấp nhận điều đó. Lý do tôi nghĩ về việc tránh std :: vector (hoạt động tốt cho tôi), là nó được phân bổ trên heap. Vì các mảng này sẽ rất nhỏ và được lặp lại trong mỗi lần lặp lại của chương trình, tôi nghĩ rằng một mảng std :: có thể hoạt động tốt hơn một chút. Tôi nghĩ rằng tôi sẽ sử dụng một mảng kiểu C sau đó, chương trình của tôi không phức tạp.
Adrian

15
@Adrian Cách nghĩ của bạn về hiệu suất là hoàn toàn sai. Đừng cố tối ưu hóa vi mô trước khi bạn có một chương trình chức năng. Và sau khi bạn có một chương trình, đừng đoán những gì nên được tối ưu hóa, thay vào đó hãy để một trình biên dịch cho bạn biết phần nào của chương trình nên được tối ưu hóa.
Paul Manta

Câu trả lời:


86

Có cách nào đơn giản để thực hiện công việc này, như cách làm với mảng kiểu C đơn giản không?

Không. Bạn thực sự không thể làm điều đó trừ khi bạn làm cho hàm của mình trở thành một mẫu hàm (hoặc sử dụng một loại vùng chứa khác, chẳng hạn như một std::vector, như được đề xuất trong các nhận xét cho câu hỏi):

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

Đây là một ví dụ trực tiếp .


9
OP hỏi liệu có giải pháp nào khác ngoài các mẫu không.
Novak

1
@Adrian: Thật không may không có giải pháp khác, nếu bạn muốn chức năng của bạn để làm việc quát về mảng của bất kỳ kích thước ...
Andy Prowl

1
Đúng: không còn cách nào khác. Vì mỗi std :: array có kích thước khác nhau là một kiểu khác nhau, bạn cần viết một hàm có thể hoạt động trên các kiểu khác nhau. Do đó, các mẫu là giải pháp cho std :: array.
bstamour

4
Phần đẹp về cách sử dụng mẫu ở đây là bạn có thể làm điều này ngay cả chung chung hơn, do đó nó hoạt động với bất kỳ thùng chứa chuỗi, cũng như mảng tiêu chuẩn:template<typename C, typename M> void mulArray(C & arr, M multiplier) { /* same body */ }
Benjamin Lindley

1
@BenjaminLindley: Tất nhiên, điều đó giả định rằng anh ấy có thể đưa mã vào tiêu đề.
Nicol Bolas

27

Kích thước của arraynó là một phần của loại hình , vì vậy bạn không thể làm những gì bạn muốn. Có một số lựa chọn thay thế.

Ưu tiên sẽ là sử dụng một cặp trình vòng lặp:

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

Ngoài ra, hãy sử dụng vectorthay vì mảng, cho phép bạn lưu trữ kích thước trong thời gian chạy thay vì là một phần của kiểu:

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

1
Tôi nghĩ đây là giải pháp ưu việt; nếu bạn gặp khó khăn khi tạo một mẫu, hãy làm cho nó hoàn toàn chung chung với các trình vòng lặp sẽ cho phép bạn sử dụng bất kỳ vùng chứa nào (mảng, danh sách, vectơ, thậm chí cả con trỏ C cũ, v.v.) mà không có nhược điểm. Cảm ơn cho gợi ý.
Mark Lakata

6

Tôi đã thử bên dưới và nó chỉ làm việc cho tôi.

#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;
}

ĐẦU RA:

1 2 3 4 5 6 7

2 4 6 8 10 12

1 1 1 1 1 1 1 1 1

3 6 9 12 15 18 21

10 20 30 40 50 60

2 2 2 2 2 2 2 2 2


3
Đây không phải là C ++ hợp lệ, mà là một phần mở rộng. Các chức năng này là các mẫu, thậm chí không có template.
HolyBlackCat

1
Tôi đã xem xét điều này và có vẻ như auto foo(auto bar) { return bar * 2; }hiện tại không phải là C ++ hợp lệ mặc dù nó biên dịch trong GCC7 với bộ cờ C ++ 17. Từ việc đọc ở đây , các tham số hàm được khai báo là tự động là một phần của TS Khái niệm mà cuối cùng sẽ là một phần của C ++ 20.
Fibbles

Cảnh báo C26485
metablaster

5

BIÊN TẬP

C ++ 20 dự kiến ​​bao gồm std::span

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

Câu trả lời gốc

Những gì bạn muốn là một cái gì đó giống như gsl::span, có sẵn trong Thư viện hỗ trợ hướng dẫn được mô tả trong Nguyên tắc cốt lõi của C ++:

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

Bạn có thể tìm thấy một triển khai chỉ tiêu đề mã nguồn mở của GSL tại đây:

https://github.com/Microsoft/GSL

Với gsl::span, bạn có thể làm điều này:

// 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);

Vấn đề std::arraylà kích thước của nó là một phần của kiểu của nó, vì vậy bạn phải sử dụng một mẫu để triển khai một hàm có std::arraykích thước tùy ý.

gsl::spanmặt khác lưu trữ kích thước của nó dưới dạng thông tin thời gian chạy. Điều này cho phép bạn sử dụng một hàm không phải mẫu để chấp nhận một mảng có kích thước tùy ý. Nó cũng sẽ chấp nhận các vùng chứa liền kề khác:

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

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

Khá tuyệt phải không?


3

Chắc chắn, có một cách đơn giản trong C ++ 11 để viết một hàm nhận một mảng std :: có kiểu đã biết, nhưng kích thước không xác định.

Nếu chúng ta không thể truyền kích thước mảng cho hàm, thì thay vào đó, chúng ta có thể chuyển địa chỉ bộ nhớ của nơi bắt đầu mảng cùng với địa chỉ thứ hai của nơi kết thúc mảng. Sau này, bên trong hàm, chúng ta có thể sử dụng 2 địa chỉ vùng nhớ này để tính kích thước của mảng!

#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;
}

Đầu ra tại Console: 10, 20, 2, 4, 8


1

Điều này có thể được thực hiện, nhưng cần một vài bước để thực hiện sạch sẽ. Đầu tiên, hãy viết một template classđại diện cho một loạt các giá trị liền nhau. Sau đó, chuyển tiếp một templatephiên bản biết mức độ lớn arrayđến Implphiên bản có phạm vi tiếp giáp này.

Cuối cùng, thực hiện contig_rangephiên bản. Lưu ý rằng điều đó for( int& x: range )hoạt động contig_rangevì tôi đã triển khai begin()end()và con trỏ là các trình vòng lặp.

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 );
}

(không được thử nghiệm, nhưng thiết kế sẽ hoạt động).

Sau đó, trong .cpptệp của bạn :

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

Điều này có nhược điểm là mã lặp qua nội dung của mảng không biết (tại thời điểm biên dịch) mảng lớn như thế nào, điều này có thể tốn chi phí tối ưu hóa. Nó có lợi thế là việc triển khai không cần phải có trong tiêu đề.

Hãy cẩn thận về việc xây dựng một cách rõ ràng a contig_range, vì nếu bạn chuyển nó a, setnó sẽ cho rằng setdữ liệu là liền kề, điều này là sai và thực hiện hành vi không xác định ở khắp nơi. Hai vùng stdchứa duy nhất mà điều này được đảm bảo hoạt động là vectorarray(và mảng kiểu C, khi nó xảy ra!). dequemặc dù là truy cập ngẫu nhiên không liền kề (nguy hiểm là nó tiếp giáp theo từng phần nhỏ!), listthậm chí không gần nhau và các vùng chứa kết hợp (có thứ tự và không có thứ tự) cũng không liền nhau.

Vì vậy, các nhà xây dựng ba tôi thực hiện ở đâu std::array, std::vectorvà C-style mảng, mà về cơ bản bao gồm các căn cứ.

Việc triển khai []cũng dễ dàng và giữa for()[]đó là hầu hết những gì bạn muốn array, phải không?


Đây không phải chỉ là chuyển mẫu sang một nơi khác?
GManNickG

@GManNickG đại loại. Tiêu đề có một templatechức năng thực sự ngắn mà không có chi tiết triển khai. Các Implchức năng không phải là một templatechức năng, và do đó bạn hạnh phúc có thể che giấu việc thực hiện ở các .cpptập tin bạn đã chọn. Đó là một kiểu xóa kiểu thực sự thô thiển, nơi tôi trích xuất khả năng lặp qua các vùng chứa liền kề thành một lớp đơn giản hơn, và sau đó chuyển nó qua ... (trong khi multArrayImpllấy a templatelàm đối số, nó không phải là templatechính nó).
Yakk - Adam Nevraumont

Tôi hiểu rằng chế độ xem mảng / lớp proxy mảng này đôi khi hữu ích. Đề xuất của tôi là chuyển phần bắt đầu / kết thúc của vùng chứa trong hàm tạo để bạn không phải viết hàm tạo cho mỗi vùng chứa. Ngoài ra, tôi sẽ không viết '& * std :: begin (arr)' là tham chiếu và lấy địa chỉ là không cần thiết ở đây vì std :: begin / std :: end đã trả về một trình lặp.
Ricky65

@ Ricky65 Nếu bạn sử dụng trình vòng lặp, bạn cần để lộ việc triển khai. Nếu bạn sử dụng con trỏ, bạn không. Tham &*chiếu đến trình lặp (có thể không phải là một con trỏ), sau đó tạo một con trỏ đến địa chỉ. Đối với dữ liệu bộ nhớ liền kề, con trỏ tới beginvà con trỏ tới một quá khứ endcũng là các trình vòng lặp truy cập ngẫu nhiên và chúng là cùng một kiểu cho mọi phạm vi liền kề trên một kiểu T.
Yakk - Adam Nevraumont
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.