ส่งผ่านอาร์เรย์ std :: ที่ไม่ทราบขนาดไปยังฟังก์ชัน


102

ใน 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

20
หากแม่แบบดูยุ่งและเกินไปสำหรับคุณคุณควรหลีกเลี่ยงความรู้สึกนั้น เป็นเรื่องธรรมดาใน C ++
Benjamin Lindley

เหตุผลใดที่ไม่ควรใช้std::vectorตามที่ @TravisPessetto แนะนำ?
Cory Klein

2
เข้าใจแล้ว. หากนี่เป็นข้อ จำกัด ของธรรมชาติฉันจะต้องยอมรับสิ่งนั้น เหตุผลที่ฉันคิดจะหลีกเลี่ยง std :: vector (ซึ่งเหมาะกับฉันมาก) คือมันถูกจัดสรรบนฮีป เนื่องจากอาร์เรย์เหล่านี้จะมีขนาดเล็กและวนซ้ำในทุกๆการวนซ้ำของโปรแกรมฉันคิดว่าอาร์เรย์ std :: อาจทำงานได้ดีกว่าเล็กน้อย ฉันคิดว่าฉันจะใช้อาร์เรย์สไตล์ C แล้วโปรแกรมของฉันไม่ซับซ้อน
Adrian

15
@ เอเดรียนวิธีคิดเกี่ยวกับการแสดงของคุณผิดทั้งหมด อย่าพยายามทำการเพิ่มประสิทธิภาพขนาดเล็กก่อนที่คุณจะมีโปรแกรมที่ใช้งานได้ และหลังจากที่คุณมีโปรแกรมแล้วอย่าคาดเดาสิ่งที่ควรปรับให้เหมาะสม แต่ให้ผู้สร้างโปรไฟล์บอกคุณว่าควรปรับแต่งส่วนใดของโปรแกรมให้เหมาะสม
Paul Manta

คำตอบ:


90

มีวิธีง่ายๆในการทำให้งานนี้เหมือนกับอาร์เรย์สไตล์ C ธรรมดาหรือไม่?

ไม่คุณไม่สามารถทำได้จริง ๆ เว้นแต่คุณจะทำให้ฟังก์ชันของคุณเป็นเทมเพลตฟังก์ชัน(หรือใช้คอนเทนเนอร์ประเภทอื่นเช่น an 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 ถามว่ามีวิธีแก้ปัญหาอื่นหรือไม่นอกจากเทมเพลต
โนวัค

1
@ เอเดรียน: น่าเสียดายที่ไม่มีวิธีแก้ปัญหาอื่นหากคุณต้องการให้ฟังก์ชันของคุณทำงานโดยทั่วไปในอาร์เรย์ทุกขนาด ...
Andy Prowl

1
ถูกต้อง: ไม่มีวิธีอื่น เนื่องจากอาร์เรย์ std :: แต่ละรายการที่มีขนาดแตกต่างกันเป็นประเภทที่แตกต่างกันคุณจึงต้องเขียนฟังก์ชันที่สามารถทำงานกับประเภทต่างๆได้ ดังนั้นเทมเพลตจึงเป็นทางออกสำหรับ std :: array
bstamour

4
ส่วนที่สวยงามเกี่ยวกับการใช้เทมเพลตที่นี่คือคุณสามารถทำให้มันเป็นแบบทั่วไปได้มากขึ้นเพื่อให้ใช้งานได้กับคอนเทนเนอร์ลำดับใด ๆ รวมถึงอาร์เรย์มาตรฐาน:template<typename C, typename M> void mulArray(C & arr, M multiplier) { /* same body */ }
Benjamin Lindley

2
@AndyProwl ลิงก์ตัวอย่างสดใช้งานไม่ได้อีกต่อไป
Ahmed Hussein

28

ขนาดของชิ้นส่วน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

9

แก้ไข

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

สวยดีใช่มั้ย?


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

เอาท์พุท:

1 2 3 4 5 6 7

2 4 6 8 10 12

1 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
นี่ไม่ใช่ C ++ ที่ถูกต้อง แต่เป็นส่วนขยาย templateฟังก์ชั่นเหล่านี้เป็นแม่แบบได้โดยไม่ต้อง
HolyBlackCat

1
ฉันได้ตรวจสอบสิ่งนี้แล้วและดูเหมือนว่าauto foo(auto bar) { return bar * 2; }C ++ ไม่ถูกต้องในขณะนี้แม้ว่าจะรวบรวมใน GCC7 ด้วยชุดค่าสถานะ C ++ 17 จากการอ่านที่นี่พารามิเตอร์ฟังก์ชันที่ประกาศเป็นอัตโนมัติเป็นส่วนหนึ่งของ Concepts TS ซึ่งในที่สุดควรเป็นส่วนหนึ่งของ C ++ 20
Fibbles

คำเตือนC26485
metablaster

3

แน่นอนมีวิธีง่ายๆใน C ++ 11 ในการเขียนฟังก์ชันที่รับ std :: array ของชนิดที่รู้จัก แต่ไม่ทราบขนาด

หากเราไม่สามารถส่งผ่านขนาดอาร์เรย์ไปยังฟังก์ชันได้เราสามารถส่งที่อยู่หน่วยความจำของตำแหน่งที่อาร์เรย์เริ่มต้นพร้อมกับแอดเดรสที่ 2 ของตำแหน่งที่อาร์เรย์สิ้นสุดลง ต่อมาภายในฟังก์ชันเราสามารถใช้ที่อยู่หน่วยความจำ 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

ซึ่งสามารถทำได้ แต่ต้องใช้ขั้นตอนไม่กี่ขั้นตอนในการทำ ขั้นแรกเขียน a template classที่แสดงถึงช่วงของค่าที่ต่อเนื่องกัน จากนั้นส่งต่อ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คอนเทนเนอร์เท่านั้นที่รับประกันว่าจะใช้งานได้คือvectorและarray(และอาร์เรย์สไตล์ C เมื่อเกิดขึ้น!) dequeแม้ว่าจะเป็นการเข้าถึงแบบสุ่มก็ไม่ได้อยู่ติดกัน (อันตรายมันอยู่ติดกันเป็นชิ้นเล็ก ๆ !) listไม่ได้อยู่ใกล้กันและคอนเทนเนอร์ที่เชื่อมโยงกัน (เรียงลำดับและไม่เรียงลำดับ) ก็ไม่ติดกัน

ดังนั้นสามก่อสร้างผมดำเนินการที่std::array, std::vectorและ C สไตล์อาร์เรย์ซึ่งโดยทั่วไปครอบคลุมฐาน

การดำเนินการ[]เป็นเรื่องง่ายเช่นกันและระหว่างfor()และ[]ที่เป็นที่สุดของสิ่งที่คุณต้องการarrayสำหรับไม่ได้หรือไม่


นี่ไม่ใช่แค่การหักล้างเทมเพลตไปที่อื่นหรือ
GManNickG

@GManNickG เรียงลำดับ. ส่วนหัวมีtemplateฟังก์ชันสั้น ๆโดยไม่มีรายละเอียดการใช้งาน Implฟังก์ชั่นไม่ได้เป็นtemplateฟังก์ชั่นและเพื่อให้คุณมีความสุขสามารถซ่อนการดำเนินการในส่วน.cppไฟล์ที่คุณเลือก มันเป็นประเภทการลบที่หยาบคายจริงๆโดยที่ฉันดึงความสามารถในการทำซ้ำบนคอนเทนเนอร์ที่ต่อเนื่องกันเป็นคลาสที่ง่ายกว่าแล้วส่งผ่าน ... (ในขณะที่multArrayImplใช้templateเป็นอาร์กิวเมนต์ แต่มันไม่ใช่templateตัวเอง)
Yakk - Adam Nevraumont

ฉันเข้าใจว่าคลาสพร็อกซีมุมมองอาร์เรย์ / อาร์เรย์นี้มีประโยชน์ในบางครั้ง คำแนะนำของฉันคือส่งต่อจุดเริ่มต้น / จุดสิ้นสุดของคอนเทนเนอร์ในตัวสร้างดังนั้นคุณจะไม่ต้องเขียนตัวสร้างสำหรับแต่ละคอนเทนเนอร์ นอกจากนี้ฉันจะไม่เขียน '& * std :: begin (arr)' เนื่องจากการอ้างอิงและการรับที่อยู่นั้นไม่จำเป็นที่นี่เนื่องจาก std :: begin / std :: end ส่งคืนตัววนซ้ำแล้ว
Ricky65

@ Ricky65 หากคุณใช้ตัวทำซ้ำคุณจำเป็นต้องเปิดเผยการใช้งาน หากคุณใช้พอยน์เตอร์คุณจะไม่ทำ &*dereferences iterator (ซึ่งอาจจะไม่ชี้) แล้วทำให้ตัวชี้ไปยังที่อยู่ที่ สำหรับข้อมูลของหน่วยความจำที่ต่อเนื่องกันชี้ไปยังbeginและตัวชี้ไปที่หนึ่งผ่านไปendนอกจากนี้ยังมี iterators เข้าถึงแบบสุ่มและพวกเขาเป็นชนิดเดียวกันสำหรับทุกTช่วงต่อเนื่องกันมากกว่าประเภท
Yakk - Adam Nevraumont
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.