เหตุใดจึงส่งคืน 'เวกเตอร์' จากฟังก์ชันได้


108

โปรดพิจารณารหัสนี้ ฉันเคยเห็นรหัสประเภทนี้หลายครั้ง wordsเป็นเวกเตอร์ท้องถิ่น เป็นไปได้อย่างไรที่จะส่งคืนจากฟังก์ชัน

เรารับประกันได้ไหมว่ามันจะไม่ตาย?

 std::vector<std::string> read_file(const std::string& path)
 {
    std::ifstream file("E:\\names.txt");

    if (!file.is_open())
    {
        std::cerr << "Unable to open file" << "\n";
        std::exit(-1);
    }

    std::vector<string> words;//this vector will be returned
    std::string token;

    while (std::getline(file, token, ','))
    {
        words.push_back(token);
    }

    return words;
}

18
จะถูกคัดลอกเมื่อส่งคืน
เพลงยาว

6
ไม่มีใครรับประกัน .. มันจะตาย แต่หลังจากลอกแล้ว
Maroun

7
คุณจะมีปัญหาก็ต่อเมื่อฟังก์ชันของคุณส่งคืนข้อมูลอ้างอิง:std::vector<std::string>&
Caduchon

14
@songyuanyao ไม่มันจะถูกย้าย
พับขวา

15
@songyuanyao ครับ. C ++ 11 เป็นมาตรฐานปัจจุบันดังนั้น C ++ 11 คือ C ++
พับขวา

คำตอบ:


68

เรารับประกันได้ไหมว่ามันจะไม่ตาย?

ตราบใดที่ไม่มีการอ้างอิงกลับมาก็ทำได้ดีมาก wordsจะถูกย้ายไปยังตัวแปรที่รับผลลัพธ์

ตัวแปรโลคัลจะอยู่นอกขอบเขต หลังจากถูกย้าย (หรือคัดลอก)


2
แต่มีประสิทธิภาพหรือมีข้อกังวลด้านประสิทธิภาพใด ๆ สำหรับเวกเตอร์ซึ่งอาจมี 1,000 รายการ
zar

@zadane สิ่งนี้มีปัญหาหรือไม่? นอกจากนี้ฉันยังกล่าวถึงการย้ายที่จะหลีกเลี่ยงการคัดลอกค่าส่งคืนจริง (อย่างน้อยก็มีให้ใช้กับมาตรฐานปัจจุบัน)
πάνταῥεῖ

2
ไม่ไม่ได้อยู่ในคำถามจริงๆ แต่ฉันกำลังมองหาคำตอบจากมุมมองนั้นอย่างอิสระ ฉันไม่รู้ว่าฉันโพสต์คำถามของฉันหรือไม่ฉันกลัวว่าพวกเขาจะทำเครื่องหมายว่าซ้ำกัน :)
zar

@zadane "ฉันกลัวว่าพวกเขาจะทำเครื่องหมายว่าซ้ำกับสิ่งนี้"น่าจะเป็น เพียงแค่มีลักษณะที่สูงขึ้นได้รับการโหวตคำตอบ แม้แต่การใช้งานที่เก่ากว่าคุณก็ไม่ควรกังวล แต่คอมไพเลอร์เหล่านั้นจะได้รับการปรับให้เหมาะสมเป็นส่วนใหญ่
πάνταῥεῖ

107

ก่อน C ++ 11:

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

ดูคำถามและคำตอบนี้สำหรับรายละเอียดเพิ่มเติม

C ++ 11:

ฟังก์ชันจะย้ายค่า ดูคำตอบนี้สำหรับรายละเอียดเพิ่มเติม


2
มันจะถูกย้ายไม่ได้คัดลอก นี้รับประกัน
พับขวา

1
สิ่งนี้ใช้กับ C ++ 10 ด้วยหรือไม่?
Tim Meyer

28
ไม่มีสิ่งที่เรียกว่า C ++ 10
พับขวา

C ++ 03 ไม่มีความหมายในการเคลื่อนย้าย (แต่อาจมีการคัดลอกออกไป) แต่ C ++ คือ C ++ 11 และคำถามเกี่ยวกับ C ++
พับขวา

19
มีแท็กแยกต่างหากสำหรับคำถามเฉพาะสำหรับ C ++ 11 พวกเราหลายคนโดยเฉพาะโปรแกรมเมอร์ใน บริษัท ขนาดใหญ่ยังคงติดอยู่กับคอมไพเลอร์ที่ยังไม่รองรับ C ++ 11 อย่างเต็มที่ ฉันอัปเดตคำถามให้ถูกต้องสำหรับทั้งสองมาตรฐาน
Tim Meyer

26

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

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

ตราบใดที่คุณไม่ส่งกลับตัวชี้หรือการอ้างอิงถึงสิ่งที่อยู่ในฟังก์ชันที่ส่งกลับมาคุณก็สบายดี


13

เพื่อให้เข้าใจถึงพฤติกรรมคุณสามารถเรียกใช้รหัสนี้:

#include <iostream>

class MyClass
{
  public:
    MyClass() { std::cout << "run constructor MyClass::MyClass()" << std::endl; }
    ~MyClass() { std::cout << "run destructor MyClass::~MyClass()" << std::endl; }
    MyClass(const MyClass& x) { std::cout << "run copy constructor MyClass::MyClass(const MyClass&)" << std::endl; }
    MyClass& operator = (const MyClass& x) { std::cout << "run assignation MyClass::operator=(const MyClass&)" << std::endl; }
};

MyClass my_function()
{
  std::cout << "run my_function()" << std::endl;
  MyClass a;
  std::cout << "my_function is going to return a..." << std::endl;
  return a;
}

int main(int argc, char** argv)
{
  MyClass b = my_function();

  MyClass c;
  c = my_function();

  return 0;
}

ผลลัพธ์มีดังต่อไปนี้:

run my_function()
run constructor MyClass::MyClass()
my_function is going to return a...
run constructor MyClass::MyClass()
run my_function()
run constructor MyClass::MyClass()
my_function is going to return a...
run assignation MyClass::operator=(const MyClass&)
run destructor MyClass::~MyClass()
run destructor MyClass::~MyClass()
run destructor MyClass::~MyClass()

โปรดทราบว่าตัวอย่างนี้มีให้ในบริบท C ++ 03 ซึ่งสามารถปรับปรุงได้สำหรับ C ++> = 11


1
ตัวอย่างนี้จะสมบูรณ์ยิ่งขึ้นหากรวมตัวสร้างการย้ายและตัวดำเนินการกำหนดการย้ายด้วยและไม่ใช่แค่คัดลอกตัวสร้างและตัวดำเนินการกำหนดสำเนา (หากไม่มีฟังก์ชั่นการย้ายระบบจะใช้ตัวคัดลอกแทน)
Some Guy

@SomeGuy ฉันเห็นด้วย แต่ฉันไม่ได้ใช้ C ++ 11 ฉันไม่สามารถให้ความรู้ที่ฉันไม่มี ฉันเพิ่มบันทึก อย่าลังเลที่จะเพิ่มคำตอบสำหรับ C ++> = 11 :-)
Caduchon

-5

ฉันไม่เห็นด้วยและไม่แนะนำให้ส่งคืน a vector:

vector <double> vectorial(vector <double> a, vector <double> b)
{
    vector <double> c{ a[1] * b[2] - b[1] * a[2], -a[0] * b[2] + b[0] * a[2], a[0] * b[1] - b[0] * a[1] };
    return c;
}

เร็วกว่ามาก:

void vectorial(vector <double> a, vector <double> b, vector <double> &c)
{
    c[0] = a[1] * b[2] - b[1] * a[2]; c[1] = -a[0] * b[2] + b[0] * a[2]; c[2] = a[0] * b[1] - b[0] * a[1];
}

ฉันทดสอบบน Visual Studio 2017 ด้วยผลลัพธ์ต่อไปนี้ในโหมดเผยแพร่:

8.01 MOP โดยการอ้างอิง
5.09 MOPs ส่งคืนเวกเตอร์

ในโหมดดีบักสิ่งต่างๆจะแย่กว่านั้นมาก:

0.053 MOPS โดยอ้างอิง
0.034 MOP โดยเวกเตอร์ส่งคืน


-10

นี่เป็นความล้มเหลวของการออกแบบจริงๆ คุณไม่ควรใช้ค่าตอบแทนสำหรับสิ่งที่ไม่ใช่แบบดั้งเดิมสำหรับสิ่งที่ไม่สำคัญ

วิธีการแก้ปัญหาที่ดีที่สุดควรดำเนินการผ่านพารามิเตอร์การส่งคืนพร้อมการตัดสินใจเกี่ยวกับการอ้างอิง / ตัวชี้และการใช้ "const \ 'y \' ness" เป็นตัวบอกอย่างเหมาะสม

นอกจากนี้คุณควรทราบว่าเลเบลบนอาร์เรย์ใน C และ C ++ เป็นตัวชี้ได้อย่างมีประสิทธิภาพและการสมัครสมาชิกนั้นเป็นสัญลักษณ์ออฟเซ็ตหรือสัญลักษณ์เพิ่มเติมได้อย่างมีประสิทธิภาพ

ดังนั้น label หรือ ptr array_ptr === เลเบลอาร์เรย์จึงส่งคืน foo [offset] จึงบอกว่า return element ที่ตำแหน่ง memory pointer foo + offset ของ type return type


5
..........อะไร. ดูเหมือนชัดเจนว่าคุณไม่มีคุณสมบัติพอที่จะโยนข้อกล่าวหาเช่น "ความล้มเหลวในการออกแบบ" และในความเป็นจริงการส่งเสริมความหมายเชิงคุณค่าโดย RVO และการดำเนินการย้ายเป็นหนึ่งในความสำเร็จหลักของสไตล์ C ++ สมัยใหม่ แต่ดูเหมือนคุณจะติดอยู่กับความคิดเกี่ยวกับอาร์เรย์และพอยน์เตอร์ดิบดังนั้นฉันไม่คาดหวังให้คุณเข้าใจ
underscore_d
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.