将未知大小的 std: : 数组传递给函数

在 C + + 11中,如何编写一个接受已知类型但未知大小的 std: : 数组的函数(或方法) ?

// 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 样式数组那样?

89036 次浏览

有没有一种简单的方法可以实现这一点,就像使用普通的 C 样式数组那样?

没有。除非将函数设置为函数 模板(或者使用其他类型的容器,比如 std::vector,正如问题的注释中所建议的那样) ,否则真的无法做到这一点:

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

这是 < strong > 实例

array的大小是 类型的一部分,所以你不能完全做你想要的。

首选的方法是使用一对迭代器:

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

或者,使用 vector代替 array,后者允许您在运行时存储大小,而不是作为其类型的一部分:

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

这是可以做到的,但需要几个步骤才能干净利落地完成。首先,编写一个表示连续值范围的 template class。然后转发一个知道 array有多大的 template版本到采用这个连续范围的 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;
}
}

这样做的缺点是,遍历数组内容的代码(在编译时)不知道数组有多大,这可能会带来优化成本。它的优点是实现不必在标头中。

在显式构造一个 contig_range时要小心,就像传递给它一个 set一样,它会假设 set数据是连续的,这是假的,并且到处都是未定义行为。只有 vectorarray(以及 C 风格的数组,碰巧!)这两个 std容器可以保证正常工作.尽管 deque是随机访问,但它并不是连续的(危险的是,它是以小块的形式连续的!),list甚至不相近,而且关联的(有序和无序)容器同样是不连续的。

所以我实现了三个构造函数,其中 std::arraystd::vector和 C 风格的数组,基本上涵盖了基本的。

实现 []也很容易,在 for()[]之间,这就是你想要的 array的大部分内容,不是吗?

我尝试了下面的方法,它对我很有效。

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

产出:

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

剪辑

C + + 20初步包括 std::span

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

原始答案

你想要的是类似 gsl::span的东西,它可以在 C + + 核心指南中描述的指南支持库中找到:

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

很酷吧?

当然,在 C + + 11中有一种简单的方法来编写一个函数,该函数使用一个已知类型但大小未知的 std: : 数组。

如果无法将数组大小传递给函数,那么可以将数组开始位置的内存地址以及数组结束位置的第二个地址传递给函数。稍后,在函数内部,我们可以使用这两个内存地址来计算数组的大小!

#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{ 5, 10, 1, 2, 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