การคืนค่าหลายค่าจากฟังก์ชัน C ++


242

มีวิธีที่ต้องการคืนค่าหลายค่าจากฟังก์ชั่น C ++ หรือไม่? ตัวอย่างเช่นลองนึกภาพฟังก์ชันที่หารจำนวนเต็มสองจำนวนแล้วส่งกลับทั้งผลหารและส่วนที่เหลือ วิธีหนึ่งที่ฉันมักเห็นคือใช้พารามิเตอร์อ้างอิง:

void divide(int dividend, int divisor, int& quotient, int& remainder);

การเปลี่ยนแปลงคือการคืนค่าหนึ่งและส่งผ่านค่าพารามิเตอร์อ้างอิง:

int divide(int dividend, int divisor, int& remainder);

อีกวิธีหนึ่งคือการประกาศให้ struct มีผลลัพธ์ทั้งหมดและส่งคืนสิ่งนั้น:

struct divide_result {
    int quotient;
    int remainder;
};

divide_result divide(int dividend, int divisor);

เป็นวิธีหนึ่งในวิธีเหล่านี้ที่ต้องการโดยทั่วไปหรือมีข้อเสนอแนะอื่น ๆ หรือไม่

แก้ไข: ในรหัสโลกแห่งความจริงอาจมีผลลัพธ์มากกว่าสองรายการ พวกเขาอาจเป็นประเภทที่แตกต่างกัน

คำตอบ:


217

สำหรับการคืนค่าสองค่าฉันใช้std::pair(โดยปกติจะพิมพ์) คุณควรดูboost::tuple(ใน C ++ 11 และใหม่กว่านั้นstd::tuple) สำหรับผลลัพธ์ที่ส่งคืนมากกว่าสองรายการ

ด้วยการแนะนำโครงสร้างที่มีผลผูกพันใน C ++ 17 การส่งคืนstd::tupleอาจจะกลายเป็นมาตรฐานที่ยอมรับได้


12
+1 สำหรับ tuple โปรดจำไว้ว่าการขยายผลการทำงานของวัตถุขนาดใหญ่ที่ส่งคืนในโครงสร้างเทียบกับการส่งผ่านโดยการอ้างอิง
Marcin

12
หากคุณกำลังจะใช้สิ่งอันดับทำไมไม่ใช้พวกมันเป็นคู่เช่นกัน ทำไมมีกรณีพิเศษ
Ferruccio

4
Fred, yes boost :: tuple สามารถทำได้ :)
โยฮันเน Schaub - litb

46
ใน C ++ 11 std::tupleคุณสามารถใช้
Ferruccio

14
หากคุณต้องการยอมรับค่าหลายค่าจากฟังก์ชั่นวิธีที่สะดวกในการทำเช่นนี้คือการใช้std::tie stackoverflow.com/a/2573822/502144
fdermishin

176

ใน C ++ 11 คุณสามารถ:

#include <tuple>

std::tuple<int, int> divide(int dividend, int divisor) {
    return  std::make_tuple(dividend / divisor, dividend % divisor);
}

#include <iostream>

int main() {
    using namespace std;

    int quotient, remainder;

    tie(quotient, remainder) = divide(14, 3);

    cout << quotient << ',' << remainder << endl;
}

ใน C ++ 17:

#include <tuple>

std::tuple<int, int> divide(int dividend, int divisor) {
    return  {dividend / divisor, dividend % divisor};
}

#include <iostream>

int main() {
    using namespace std;

    auto [quotient, remainder] = divide(14, 3);

    cout << quotient << ',' << remainder << endl;
}

หรือด้วย structs:

auto divide(int dividend, int divisor) {
    struct result {int quotient; int remainder;};
    return result {dividend / divisor, dividend % divisor};
}

#include <iostream>

int main() {
    using namespace std;

    auto result = divide(14, 3);

    cout << result.quotient << ',' << result.remainder << endl;

    // or

    auto [quotient, remainder] = divide(14, 3);

    cout << quotient << ',' << remainder << endl;
}

4
ฉันมีความกังวลอย่างหนึ่งกับฟังก์ชั่นการคืนค่า tuples สมมติว่าฟังก์ชั่นต้นแบบด้านบนอยู่ในส่วนหัวจากนั้นฉันจะรู้ได้อย่างไรว่าค่าส่งคืนแรกและค่าที่สองหมายความว่าอย่างไรโดยไม่เข้าใจคำจำกัดความของฟังก์ชัน? ความฉลาดทางที่เหลือหรือความฉลาดทางที่เหลือ
Uchia Itachi

7
@UchiaItachi ความกังวลแบบเดียวกันสำหรับพารามิเตอร์ของฟังก์ชั่นคุณสามารถตั้งชื่อให้กับพวกเขาได้ แต่ภาษาไม่ได้บังคับใช้และชื่อของพารามิเตอร์จะไม่มีค่าที่ไซต์การโทรเมื่ออ่าน นอกจากนี้ในการกลับมาครั้งเดียวคุณมีประเภท แต่การมีชื่ออาจมีประโยชน์ด้วยสิ่งอันดับคุณจะเพิ่มปัญหาเป็นสองเท่าดังนั้น imo ภาษาที่ขาดการจัดทำเป็นเอกสารด้วยตนเองในหลายวิธีไม่ใช่แค่นี้
pepper_chico

1
ตัวอย่างสุดท้ายจะมีลักษณะอย่างไรหากฉันต้องการระบุประเภทการส่งคืน () อย่างชัดเจน ฉันควรจะกำหนดผลลัพธ์ที่อื่นหรือไม่หรือฉันสามารถกำหนดได้ในสเปคของผลตอบแทนประเภท?
Slava

1
@Slava คุณไม่สามารถกำหนดประเภทสิทธิในลายเซ็นของฟังก์ชั่นที่คุณจะต้องประกาศประเภทนอกและใช้เป็นชนิดกลับเหมือนที่มันทำตามปกติ (เพียงแค่ย้ายstructสายนอกร่างกายทำงานและเปลี่ยนautoกลับมาฟังก์ชั่นresult.
pepper_chico

3
@pepper_chico จะทำอย่างไรหากต้องการใส่คำจำกัดความของฟังก์ชั่นdivideในไฟล์ cpp แยกต่างหาก error: use of ‘auto divide(int, int)’ before deduction of ‘auto’ฉันได้รับข้อผิดพลาด ฉันจะแก้ปัญหานี้ได้อย่างไร
Adriaan

123

โดยส่วนตัวแล้วฉันไม่ชอบพารามิเตอร์ส่งคืนด้วยเหตุผลหลายประการ:

  • ไม่ชัดเจนเสมอไปในการเรียกใช้ว่าพารามิเตอร์ใดบ้าง
  • โดยทั่วไปคุณต้องสร้างตัวแปรท้องถิ่นเพื่อจับผลลัพธ์ในขณะที่ค่าตอบแทนสามารถใช้แบบอินไลน์ (ซึ่งอาจเป็นหรือไม่เป็นความคิดที่ดี แต่อย่างน้อยคุณก็มีตัวเลือก)
  • ดูเหมือนว่าฉันจะสะอาดกว่าที่จะมี "ในประตู" และ "ออกไปข้างนอก" สำหรับฟังก์ชั่น - อินพุตทั้งหมดเข้าที่นี่เอาท์พุททั้งหมดออกมาที่นั่น
  • ฉันต้องการให้รายการอาร์กิวเมนต์ของฉันสั้นที่สุด

ฉันยังมีการจองบางอย่างเกี่ยวกับเทคนิคคู่ / ทูเปิล ส่วนใหญ่มักจะไม่มีคำสั่งที่เป็นธรรมชาติให้กับค่าส่งคืน ผู้อ่านรหัสจะรู้ได้อย่างไรว่า result.first เป็นความฉลาดหรือส่วนที่เหลือ? และผู้ดำเนินการสามารถเปลี่ยนลำดับซึ่งจะทำลายรหัสที่มีอยู่ นี่เป็นร้ายกาจอย่างยิ่งหากค่าเป็นประเภทเดียวกันดังนั้นจะไม่มีการสร้างข้อผิดพลาดหรือคำเตือนของคอมไพเลอร์ ที่จริงแล้วข้อโต้แย้งเหล่านี้ใช้กับพารามิเตอร์ที่ส่งคืนเช่นกัน

นี่คืออีกตัวอย่างรหัสหนึ่งเล็กน้อยนี้เล็กน้อย:

pair<double,double> calculateResultingVelocity(double windSpeed, double windAzimuth,
                                               double planeAirspeed, double planeCourse);

pair<double,double> result = calculateResultingVelocity(25, 320, 280, 90);
cout << result.first << endl;
cout << result.second << endl;

พิมพ์ Groundspeed และ Course นี้หรือ Course และ Groundspeed หรือไม่ มันไม่ชัดเจน

เปรียบเทียบกับสิ่งนี้:

struct Velocity {
    double speed;
    double azimuth;
};
Velocity calculateResultingVelocity(double windSpeed, double windAzimuth,
                                    double planeAirspeed, double planeCourse);

Velocity result = calculateResultingVelocity(25, 320, 280, 90);
cout << result.speed << endl;
cout << result.azimuth << endl;

ฉันคิดว่านี่ชัดเจนกว่า

ดังนั้นฉันคิดว่าตัวเลือกแรกของฉันโดยทั่วไปคือเทคนิคการสร้าง แนวคิดคู่ / ทูเปิลน่าจะเป็นทางออกที่ดีในบางกรณี ฉันต้องการหลีกเลี่ยงพารามิเตอร์ส่งคืนเมื่อเป็นไปได้


1
ข้อเสนอแนะในการประกาศstructเช่นVelocityเป็นคนดีคนหนึ่ง อย่างไรก็ตามข้อกังวลอย่างหนึ่งคือมันสร้างมลภาวะในเนมสเปซ ผมคิดว่าด้วย C ++ 11 สามารถมีชื่อชนิดยาวและหนึ่งสามารถใช้struct auto result = calculateResultingVelocity(...)
Hugues

5
+1 ฟังก์ชั่นควรคืนค่า"สิ่ง" หนึ่งรายการไม่ใช่ "สิ่งอันดับ" อย่างใดอย่างหนึ่ง
DevSolar

1
ฉันชอบ structs มากกว่า std :: pairs / std :: tuples ด้วยเหตุผลที่อธิบายไว้ในคำตอบนี้ แต่ฉันไม่ชอบ "มลพิษ" ในเนมสเปซด้วย ทางออกที่ดีสำหรับผมที่จะกลับ struct struct { int a, b; } my_func();ที่ไม่ระบุชื่อเช่น auto result = my_func();นี้สามารถนำมาใช้เช่นนี้: แต่ C ++ ไม่อนุญาตสิ่งนี้: "ประเภทใหม่อาจไม่ถูกกำหนดในประเภทส่งคืน" ดังนั้นฉันต้องสร้าง structs เช่นstruct my_func_result_t...
anton_rh

2
@anton_rh: C ++ 14 อนุญาตให้ส่งคืนชนิดโลคัลด้วยautoดังนั้นauto result = my_func();หาได้เล็กน้อย
ildjarn

4
ประมาณ 15 ปีที่แล้วเมื่อเราค้นพบการกระตุ้นเราใช้สิ่งอันดับมากเพราะมันค่อนข้างมีประโยชน์ การทำงานล่วงเวลาเราพบข้อเสียในการอ่านโดยเฉพาะอย่างยิ่งสำหรับสิ่งอันดับที่มีประเภทเดียวกัน (เช่น tuple <double, double>; อันไหนเป็นแบบไหน) ดังนั้นเมื่อเร็ว ๆ นี้เราจึงมีนิสัยที่จะแนะนำโครงสร้าง POD ขนาดเล็กซึ่งอย่างน้อยชื่อของตัวแปรสมาชิกจะบ่งบอกถึงสิ่งที่สมเหตุสมผล
gast128

24
std::pair<int, int> divide(int dividend, int divisor)
{
   // :
   return std::make_pair(quotient, remainder);
}

std::pair<int, int> answer = divide(5,2);
 // answer.first == quotient
 // answer.second == remainder

std :: pair เป็นคำตอบสำหรับ struct ของคุณ แต่กำหนดไว้แล้วสำหรับคุณและพร้อมที่จะปรับให้เข้ากับชนิดข้อมูลสองชนิด


3
นั่นจะใช้ได้กับตัวอย่างง่ายๆของฉัน อย่างไรก็ตามโดยทั่วไปอาจมีการส่งคืนมากกว่าสองค่า
Fred Larson

5
ยังไม่ได้จัดทำเอกสารด้วยตนเอง คุณจำได้ไหมว่า x86 register ใดที่เหลืออยู่สำหรับ DIV?
ทำเครื่องหมาย

1
@ Mark - ฉันยอมรับว่าโซลูชันตำแหน่งสามารถบำรุงรักษาได้น้อยกว่า คุณสามารถพบปัญหา "permute and baffle"
Fred Larson

16

มันขึ้นอยู่กับฟังก์ชั่นที่เกิดขึ้นจริงและความหมายของหลายค่าและขนาด:

  • ถ้าพวกมันเกี่ยวข้องกันในตัวอย่างเศษส่วนของคุณฉันจะไปกับอินสแตนซ์ struct หรือคลาส
  • หากพวกเขาไม่ได้เกี่ยวข้องกันจริงๆและไม่สามารถจัดกลุ่มเป็นคลาส / โครงสร้างได้บางทีคุณควรปรับวิธีการของคุณใหม่เป็นสองวิธี
  • ขึ้นอยู่กับขนาดในหน่วยความจำของค่าที่คุณส่งคืนคุณอาจต้องการส่งกลับตัวชี้ไปยังอินสแตนซ์ของคลาสหรือโครงสร้างหรือใช้พารามิเตอร์อ้างอิง

1
ฉันชอบคำตอบของคุณและกระสุนนัดสุดท้ายของคุณทำให้ฉันนึกถึงบางสิ่งที่ฉันเพิ่งอ่านว่าการส่งผ่านค่าได้เร็วขึ้นมากขึ้นอยู่กับสถานการณ์ที่ทำให้สิ่งนี้ซับซ้อนมากขึ้น ... cpp-next.com/archive/2009/08/want-speed-pass -by-value
ปราชญ์

12

โซลูชัน OO สำหรับสิ่งนี้คือการสร้างคลาสอัตราส่วน มันจะไม่ใช้รหัสพิเศษใด ๆ (จะช่วยบางอย่าง) จะมีความสะอาด / ชัดเจนมากขึ้นและจะให้ refactorings พิเศษบางอย่างเพื่อให้คุณสามารถล้างโค้ดนอกคลาสนี้ได้เช่นกัน

จริง ๆ แล้วฉันคิดว่ามีคนแนะนำให้คืนโครงสร้างซึ่งอยู่ใกล้พอ แต่ซ่อนเจตนาที่จะต้องเป็นคลาสที่คิดเอาไว้อย่างเต็มที่กับคอนสตรัคเตอร์และวิธีการสองสามอันที่จริงแล้ว "เมธอด" ที่คุณกล่าวถึงในตอนแรก คู่) น่าจะเป็นสมาชิกของคลาสนี้ที่ส่งคืนอินสแตนซ์ของตัวเอง

ฉันรู้ว่าตัวอย่างของคุณเป็นเพียง "ตัวอย่าง" แต่ความจริงก็คือถ้าฟังก์ชั่นของคุณกำลังทำหน้าที่มากกว่าฟังก์ชั่นใด ๆ ที่ควรทำถ้าคุณต้องการให้มันคืนค่าหลายค่า

อย่ากลัวที่จะสร้างชั้นเรียนเล็ก ๆ เหล่านี้เพื่อทำงานชิ้นเล็ก ๆ - นั่นคือความมหัศจรรย์ของ OO - คุณจะต้องทำลายมันจนทุกวิธีมีขนาดเล็กและเรียบง่ายและทุกชั้นเรียนมีขนาดเล็กและเข้าใจง่าย

อีกสิ่งหนึ่งที่ควรเป็นตัวบ่งชี้ว่ามีสิ่งผิดปกติ: ใน OO คุณไม่มีข้อมูล - OO ไม่เกี่ยวกับการส่งข้อมูลคลาสต้องการจัดการและจัดการกับข้อมูลของตัวเองภายในข้อมูลใด ๆ (รวมถึง accessors) เป็นสัญญาณว่าคุณอาจต้องคิดใหม่บางสิ่งบางอย่าง ..


10

มีแบบอย่างสำหรับการส่งคืนโครงสร้างในมาตรฐาน C (และดังนั้น C ++) ที่มีฟังก์ชันdiv, ldiv(และ, ใน C99, lldiv) จาก<stdlib.h>(หรือ<cstdlib>)

'การรวมกันของค่าตอบแทนและพารามิเตอร์การส่งคืนสินค้า' มักจะสะอาดน้อยที่สุด

การมีฟังก์ชั่นส่งคืนสถานะและข้อมูลการส่งคืนผ่านพารามิเตอร์ส่งคืนมีเหตุผลใน C; มันสมเหตุสมผลน้อยกว่าใน C ++ ซึ่งคุณสามารถใช้ข้อยกเว้นเพื่อถ่ายทอดข้อมูลความล้มเหลวแทน

หากมีค่าส่งคืนมากกว่าสองค่ากลไกที่คล้ายโครงสร้างน่าจะดีที่สุด


10

ด้วย C ++ 17 คุณสามารถคืนค่าแร่ที่ไม่สามารถเคลื่อนย้ายได้และไม่สามารถคัดแยกได้อีกหนึ่งค่า (ในบางกรณี) ความเป็นไปได้ที่จะกลับมาประเภท unmovable ผ่านการเพิ่มประสิทธิภาพของค่าตอบแทนใหม่รับประกันและสำรวมอย่างกับ มวลรวมและสิ่งที่สามารถเรียกได้ว่าการก่อสร้างเทมเพลต

template<typename T1,typename T2,typename T3>
struct many {
  T1 a;
  T2 b;
  T3 c;
};

// guide:
template<class T1, class T2, class T3>
many(T1, T2, T3) -> many<T1, T2, T3>;

auto f(){ return many{string(),5.7, unmovable()}; }; 

int main(){
   // in place construct x,y,z with a string, 5.7 and unmovable.
   auto [x,y,z] = f();
}

สิ่งที่สวยเกี่ยวกับเรื่องนี้ก็คือว่ามันมีการประกันเพื่อไม่ก่อให้เกิดใด ๆ ที่คัดลอกหรือย้าย คุณสามารถสร้างตัวอย่างชุดย่อยmanyvariadic ได้เช่นกัน รายละเอียดเพิ่มเติม:

การส่งคืนการรวม Variadic (struct) และไวยากรณ์สำหรับเทมเพลต Variadic C ++ 17 'คู่มือการลดการก่อสร้าง'


6

มีหลายวิธีในการส่งคืนพารามิเตอร์หลายรายการ ฉันจะตื่นเต้น

ใช้พารามิเตอร์อ้างอิง:

void foo( int& result, int& other_result );

ใช้พารามิเตอร์ตัวชี้:

void foo( int* result, int* other_result );

ซึ่งมีข้อได้เปรียบที่คุณต้องทำ&ที่ไซต์การโทรอาจเป็นการแจ้งเตือนบุคคลว่าเป็นพารามิเตอร์ที่ไม่เหมาะสม

เขียนเทมเพลตและใช้งาน:

template<class T>
struct out {
  std::function<void(T)> target;
  out(T* t):target([t](T&& in){ if (t) *t = std::move(in); }) {}
  out(std::optional<T>* t):target([t](T&& in){ if (t) t->emplace(std::move(in)); }) {}
  out(std::aligned_storage_t<sizeof(T), alignof(T)>* t):
    target([t](T&& in){ ::new( (void*)t ) T(std::move(in)); } ) {}
  template<class...Args> // TODO: SFINAE enable_if test
  void emplace(Args&&...args) {
    target( T(std::forward<Args>(args)...) );
  }
  template<class X> // TODO: SFINAE enable_if test
  void operator=(X&&x){ emplace(std::forward<X>(x)); }
  template<class...Args> // TODO: SFINAE enable_if test
  void operator()(Args...&&args){ emplace(std::forward<Args>(args)...); }
};

จากนั้นเราสามารถทำได้:

void foo( out<int> result, out<int> other_result )

และทุกอย่างก็ดี fooไม่สามารถอ่านค่าใด ๆ ที่ส่งผ่านเป็นโบนัสได้อีกต่อไป

วิธีการอื่น ๆ outของการกำหนดจุดที่คุณสามารถใส่ข้อมูลที่สามารถนำมาใช้ในการสร้าง เช่นการโทรกลับเพื่อวางบางสิ่งบางอย่างเช่น

เราสามารถคืนโครงสร้าง:

struct foo_r { int result; int other_result; };
foo_r foo();

whick ทำงานได้ดีในทุกรุ่นของ C ++ และใน สิ่งนี้ยังอนุญาตให้:

auto&&[result, other_result]=foo();

ที่ศูนย์ค่าใช้จ่าย แม้จะไม่สามารถเคลื่อนย้ายพารามิเตอร์ได้ด้วยการรับประกันการรับประกัน

เราสามารถส่งคืนstd::tuple:

std::tuple<int, int> foo();

ซึ่งมีข้อเสียที่พารามิเตอร์ไม่ได้ตั้งชื่อ สิ่งนี้อนุญาตให้:

auto&&[result, other_result]=foo();

เช่นกัน ก่อนหน้า เราสามารถทำได้:

int result, other_result;
std::tie(result, other_result) = foo();

ซึ่งน่าอึดอัดใจนิดหน่อย อย่างไรก็ตามการรับประกันแบบไม่ทำงานที่นี่

ไปสู่ดินแดนคนแปลกหน้า (และนี่คือหลังจากout<>!) เราสามารถใช้รูปแบบการส่งต่อ:

void foo( std::function<void(int result, int other_result)> );

และตอนนี้ผู้โทรทำ:

foo( [&](int result, int other_result) {
  /* code */
} );

ประโยชน์ของสไตล์นี้คือคุณสามารถคืนค่าตามอำเภอใจได้ (โดยมีรูปแบบเหมือนกัน) โดยไม่ต้องจัดการหน่วยความจำ:

void get_all_values( std::function<void(int)> value )

valueโทรกลับอาจจะเรียกว่า 500 get_all_values( [&](int value){} )ครั้งเมื่อคุณ

เพื่อความวิกลจริตอย่างบริสุทธิ์คุณสามารถใช้ความต่อเนื่องในการสานต่อ

void foo( std::function<void(int, std::function<void(int)>)> result );

การใช้งานดูเหมือนว่า:

foo( [&](int result, auto&& other){ other([&](int other){
  /* code */
}) });

ซึ่งจะอนุญาตให้หลายหนึ่งความสัมพันธ์ระหว่างและresultother

อีกครั้งด้วยค่ายูนิคอร์นเราสามารถทำสิ่งนี้:

void foo( std::function< void(span<int>) > results )

ที่นี่เราเรียกการเรียกกลับด้วยผลลัพธ์เป็นระยะ ๆ เราสามารถทำสิ่งนี้ซ้ำ ๆ ได้

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

void foo( std::function< void(span<int>) > results ) {
  int local_buffer[1024];
  std::size_t used = 0;
  auto send_data=[&]{
    if (!used) return;
    results({ local_buffer, used });
    used = 0;
  };
  auto add_datum=[&](int x){
    local_buffer[used] = x;
    ++used;
    if (used == 1024) send_data();
  };
  auto add_data=[&](gsl::span<int const> xs) {
    for (auto x:xs) add_datum(x);
  };
  for (int i = 0; i < 7+(1<<20); ++i) {
    add_datum(i);
  }
  send_data(); // any leftover
}

ตอนนี้std::functionค่อนข้างหนักสำหรับเรื่องนี้เพราะเราจะทำสิ่งนี้ในสภาพแวดล้อมที่ไม่มีการจัดสรรศูนย์ เราอยากได้สิ่งfunction_viewที่ไม่เคยจัดสรร

ทางออกอื่นคือ:

std::function<void(std::function<void(int result, int other_result)>)> foo(int input);

โดยที่แทนที่จะใช้การติดต่อกลับและเรียกใช้งานfooแทนที่จะส่งกลับฟังก์ชันที่ใช้โทรกลับ

foo (7) ([&] (ผลลัพธ์ int, int other_result) {/ * code * /}); สิ่งนี้จะทำลายพารามิเตอร์เอาต์พุตจากพารามิเตอร์อินพุตโดยมีเครื่องหมายวงเล็บแยกกัน

ด้วยvariantและcoroutines คุณสามารถสร้างfooตัวสร้างตัวแปรของชนิดส่งคืน (หรือเพียงแค่ชนิดส่งคืน) ไวยากรณ์ยังไม่ได้รับการแก้ไขดังนั้นฉันจะไม่ให้ตัวอย่าง

ในโลกของสัญญาณและช่องเสียบฟังก์ชันที่แสดงชุดสัญญาณ:

template<class...Args>
struct broadcaster;

broadcaster<int, int> foo();

ช่วยให้คุณสร้างfooที่ทำงาน async และถ่ายทอดผลเมื่อเสร็จแล้ว

ในบรรทัดนี้เรามีเทคนิคไปป์ไลน์ที่หลากหลายที่ฟังก์ชั่นไม่ได้ทำอะไร แต่จัดเรียงข้อมูลที่จะเชื่อมต่อในบางวิธีและการทำนั้นค่อนข้างอิสระ

foo( int_source )( int_dest1, int_dest2 );

จากนั้นโค้ดนี้จะไม่ทำอะไรเลยจนกว่าจะint_sourceมีจำนวนเต็มที่ระบุ เมื่อไหร่int_dest1และint_dest2เริ่มได้รับผลลัพธ์


คำตอบนี้มีข้อมูลมากกว่าคำตอบอื่น ๆ ! โดยเฉพาะข้อมูลเกี่ยวกับauto&&[result, other_result]=foo();ฟังก์ชันที่ส่งคืนทั้ง tuples และโครงสร้าง ขอบคุณ!
jjmontes

ฉันขอขอบคุณคำตอบที่ละเอียดถี่ถ้วนนี้โดยเฉพาะอย่างยิ่งเมื่อฉันยังคงติดอยู่กับ C ++ 11 ดังนั้นจึงไม่สามารถใช้โซลูชั่นที่ทันสมัยกว่าที่คนอื่นเสนอ
GuyGizmo

5

ใช้ struct หรือคลาสสำหรับค่าส่งคืน การใช้std::pairอาจใช้งานได้ในตอนนี้ แต่

  1. ไม่ยืดหยุ่นหากคุณตัดสินใจภายหลังคุณต้องการข้อมูลเพิ่มเติมที่ส่งคืน
  2. มันไม่ชัดเจนมากจากการประกาศของฟังก์ชั่นในส่วนหัวสิ่งที่จะถูกส่งกลับและในลำดับใด

การส่งคืนโครงสร้างที่มีชื่อตัวแปรการบันทึกเอกสารแบบสมาชิกจะมีข้อบกพร่องน้อยกว่าสำหรับผู้ใช้ฟังก์ชันของคุณ วางหมวกผู้ร่วมงานของฉันไว้ครู่หนึ่งdivide_resultโครงสร้างของคุณนั้นง่ายสำหรับฉันซึ่งเป็นผู้ใช้ที่มีศักยภาพของฟังก์ชั่นของคุณเพื่อทำความเข้าใจทันทีหลังจาก 2 วินาที การยุ่งกับพารามิเตอร์ ouput หรือคู่ลึกลับและสิ่งอันดับจะใช้เวลาอ่านและอาจใช้อย่างไม่ถูกต้อง และเป็นไปได้มากว่าหลังจากใช้ฟังก์ชั่นไปแล้วสองสามครั้งฉันก็ยังจำคำสั่งไม่ทันได้


4

หากฟังก์ชั่นของคุณส่งคืนค่าผ่านการอ้างอิงคอมไพเลอร์ไม่สามารถเก็บไว้ในการลงทะเบียนเมื่อเรียกฟังก์ชั่นอื่น ๆ เพราะในทางทฤษฎีแล้วฟังก์ชั่นแรกสามารถบันทึกที่อยู่ของตัวแปรที่ส่งไปยังตัวแปรนั้น เปลี่ยนดังนั้นคอมไพเลอร์จะมี (1) บันทึกค่าจากการลงทะเบียนกลับไปที่หน่วยความจำก่อนที่จะเรียกฟังก์ชั่นอื่น ๆ และ (2) อ่านอีกครั้งเมื่อจำเป็นจากหน่วยความจำอีกครั้งหลังจากการโทรดังกล่าว

หากคุณกลับมาโดยอ้างอิงการเพิ่มประสิทธิภาพของโปรแกรมของคุณจะประสบ


4

นี่ฉันกำลังเขียนโปรแกรมที่คืนค่าหลายค่า (มากกว่าสองค่า) ใน c ++ โปรแกรมนี้สามารถทำงานได้ใน c ++ 14 (G ++ 4.9.2) โปรแกรมก็เหมือนเครื่องคิดเลข

#  include <tuple>
# include <iostream>

using namespace std; 

tuple < int,int,int,int,int >   cal(int n1, int n2)
{
    return  make_tuple(n1/n2,n1%n2,n1+n2,n1-n2,n1*n2);
}

int main()
{
    int qut,rer,add,sub,mul,a,b;
    cin>>a>>b;
    tie(qut,rer,add,sub,mul)=cal(a,b);
    cout << "quotient= "<<qut<<endl;
    cout << "remainder= "<<rer<<endl;
    cout << "addition= "<<add<<endl;
    cout << "subtraction= "<<sub<<endl;
    cout << "multiplication= "<<mul<<endl;
    return 0;
}

ดังนั้นคุณสามารถเข้าใจได้อย่างชัดเจนว่าด้วยวิธีนี้คุณสามารถคืนค่าหลายค่าจากฟังก์ชัน การใช้ std :: pair เพียง 2 ค่าสามารถส่งคืนได้ในขณะที่ std :: tuple สามารถส่งคืนได้มากกว่าสองค่า


4
ด้วย C ++ 14 คุณสามารถใช้autoประเภทการคืนสินค้าcalเพื่อทำให้สิ่งนี้สะอาดยิ่งขึ้น (IMO)
sfjac

3

ฉันมักจะใช้ out-vals ในฟังก์ชั่นเช่นนี้เพราะฉันยึดติดกับกรอบความคิดของฟังก์ชั่นที่ส่งคืนรหัสความสำเร็จ / ข้อผิดพลาดและฉันชอบที่จะทำให้สิ่งต่าง ๆ เหมือนกัน


2

ทางเลือกรวมถึงอาร์เรย์เครื่องกำเนิดไฟฟ้าและการผกผันของการควบคุมแต่ไม่มีความเหมาะสมที่นี่

บางคน (เช่น Microsoft ใน Win32 ในอดีต) มีแนวโน้มที่จะใช้พารามิเตอร์อ้างอิงเพื่อความเรียบง่ายเพราะเป็นที่ชัดเจนว่าใครเป็นผู้จัดสรรและวิธีที่จะดูบนสแต็กลดการแพร่กระจายของโครงสร้างและอนุญาตให้ส่งคืนค่าแยกต่างหาก

โปรแกรมเมอร์ "บริสุทธิ์" ชอบ struct โดยสมมติว่ามันเป็นค่าฟังก์ชัน (ตามที่เป็นในกรณีนี้) แทนที่จะเป็นสิ่งที่สัมผัสกับฟังก์ชันโดยบังเอิญ หากคุณมีขั้นตอนที่ซับซ้อนมากขึ้นหรือบางสิ่งบางอย่างกับรัฐคุณอาจใช้การอ้างอิง (สมมติว่าคุณมีเหตุผลที่ไม่ได้ใช้คลาส)


2

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

x = divide( x, y, z ) + divide( a, b, c );

ฉันมักจะเลือกที่จะผ่าน 'โครงสร้างออก' โดยการอ้างอิงในรายการพารามิเตอร์แทนที่จะมีการส่งผ่านโดยคัดลอกค่าใช้จ่ายในการส่งกลับโครงสร้างใหม่ (แต่นี่คือสิ่งที่เหงื่อออกเล็กน้อย)

void divide(int dividend, int divisor, Answer &ans)

พารามิเตอร์กำลังสับสนหรือไม่? พารามิเตอร์ที่ส่งเป็นการอ้างอิงแนะนำว่าค่ากำลังจะเปลี่ยนแปลง (ซึ่งต่างกับการอ้างอิง const) การตั้งชื่อที่เหมาะสมยังช่วยลดความสับสน


1
ฉันคิดว่ามันค่อนข้างสับสน ใครบางคนกำลังอ่านโค้ดที่เรียกว่า "divide (a, b, c);" ไม่มีข้อบ่งชี้ว่า c เป็นค่าเริ่มต้นจนกว่าพวกเขาจะค้นหาลายเซ็น แต่นั่นเป็นความกลัวโดยทั่วไปของ params อ้างอิงที่ไม่ใช่แบบ const ไม่ใช่เฉพาะคำถามนี้
Steve Jessop

2

ทำไมคุณถึงยืนยันในฟังก์ชั่นที่มีค่าส่งคืนหลายค่า? ด้วย OOP คุณสามารถใช้คลาสที่เสนอฟังก์ชั่นปกติด้วยค่าส่งคืนเดียวและจำนวน "ค่าส่งคืน" เพิ่มเติมด้านล่าง ข้อดีคือผู้โทรมีตัวเลือกในการดูข้อมูลสมาชิกเพิ่มเติม แต่ไม่จำเป็นต้องทำเช่นนี้ นี่เป็นวิธีที่ต้องการสำหรับฐานข้อมูลที่ซับซ้อนหรือการโทรผ่านเครือข่ายซึ่งอาจจำเป็นต้องใช้ข้อมูลส่งคืนเพิ่มเติมในกรณีที่เกิดข้อผิดพลาด

เพื่อตอบคำถามเดิมของคุณตัวอย่างนี้มีวิธีการคืนค่าความฉลาดซึ่งเป็นสิ่งที่ผู้โทรส่วนใหญ่อาจต้องการและนอกจากนี้หลังจากการเรียกวิธีการคุณจะได้รับส่วนที่เหลือเป็นสมาชิกข้อมูล

class div{
   public:
      int remainder;

      int quotient(int dividend, int divisor){
         remainder = ...;
         return ...;
      }
};

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

1
@jiggunjer คุณสามารถเรียกใช้การวนซ้ำหนึ่งครั้งและเก็บค่าส่งคืนหลายค่าในสมาชิกข้อมูลคลาสที่แยกต่างหาก สิ่งนี้ตอกย้ำความยืดหยุ่นของแนวคิด OOP
Roland

2

แทนที่จะส่งคืนค่าหลายค่าเพียงคืนค่าใดค่าหนึ่งและทำการอ้างอิงค่าอื่น ๆ ในฟังก์ชันที่ต้องการเช่น:

int divide(int a,int b,int quo,int &rem)

ฉันไม่ได้พูดถึงเรื่องนี้ในคำถามตัวเอง? ดูคำตอบของฉันด้วย
Fred Larson

1

Boost tuple น่าจะเป็นทางเลือกที่ฉันชอบสำหรับระบบทั่วไปที่คืนค่ามากกว่าหนึ่งค่าจากฟังก์ชั่น

ตัวอย่างที่เป็นไปได้:

include "boost/tuple/tuple.hpp"

tuple <int,int> divide( int dividend,int divisor ) 

{
  return make_tuple(dividend / divisor,dividend % divisor )
}

1

เราสามารถประกาศฟังก์ชั่นเช่นนั้นมันจะคืนค่าตัวแปรที่ผู้ใช้กำหนดประเภทโครงสร้างหรือตัวชี้ไปยังมัน และด้วยคุณสมบัติของโครงสร้างเรารู้ว่าโครงสร้างใน C สามารถเก็บค่าแบบอสมมาตรได้หลายค่า (เช่นตัวแปร int หนึ่งตัวแปรถ่านสี่ตัวแปรตัวแปรลอยสองตัวและอื่น ๆ ... )


1

ฉันจะทำโดยอ้างอิงถ้ามันเป็นเพียงค่าตอบแทนไม่กี่ แต่สำหรับประเภทที่ซับซ้อนมากขึ้นคุณก็สามารถทำเช่นนี้:

static struct SomeReturnType {int a,b,c; string str;} SomeFunction()
{
  return {1,2,3,string("hello world")}; // make sure you return values in the right order!
}

ใช้ "static" เพื่อ จำกัด ขอบเขตของประเภทการส่งคืนไปยังหน่วยการคอมไพล์นี้หากมันหมายถึงการเป็นประเภทการส่งคืนชั่วคราวเท่านั้น

 SomeReturnType st = SomeFunction();
 cout << "a "   << st.a << endl;
 cout << "b "   << st.b << endl;
 cout << "c "   << st.c << endl;
 cout << "str " << st.str << endl;

นี่ไม่ใช่วิธีที่ดีที่สุดที่จะทำ แต่จะได้ผลแน่นอน


-2

นี่คือตัวอย่างเต็มรูปแบบของการแก้ปัญหาดังกล่าว

#include <bits/stdc++.h>
using namespace std;
pair<int,int> solve(int brr[],int n)
{
    sort(brr,brr+n);

    return {brr[0],brr[n-1]};
}

int main()
{
    int n;
    cin >> n;
    int arr[n];
    for(int i=0; i<n; i++)
    {
        cin >> arr[i];
    }

    pair<int,int> o=solve(arr,n);
    cout << o.first << " " << o.second << endl;

    return 0;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.